using AutoMapper; 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.Activities; using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Services.Hubs; using Marco.Pms.Services.Service; using Marco.Pms.Services.Service.ServiceInterfaces; 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; using System.Data; using System.Net; namespace MarcoBMS.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class EmployeeController : ControllerBase { private readonly ApplicationDbContext _context; private readonly UserManager _userManager; private readonly IEmailSender _emailSender; private readonly EmployeeHelper _employeeHelper; private readonly UserHelper _userHelper; private readonly IConfiguration _configuration; private readonly ILoggingService _logger; private readonly IHubContext _signalR; private readonly PermissionServices _permission; private readonly IMapper _mapper; private readonly IProjectServices _projectServices; private readonly Guid tenantId; public EmployeeController(UserManager userManager, IEmailSender emailSender, ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger, IHubContext signalR, PermissionServices permission, IProjectServices projectServices, IMapper mapper) { _context = context; _userManager = userManager; _emailSender = emailSender; _employeeHelper = employeeHelper; _userHelper = userHelper; _configuration = configuration; _logger = logger; _signalR = signalR; _permission = permission; _projectServices = projectServices; _mapper = mapper; tenantId = _userHelper.GetTenantId(); } [HttpGet] [Route("roles/{employeeId?}")] public async Task GetRoles(Guid employeeId) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); return BadRequest(ApiResponse.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 roles = new List(); 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.SuccessResponse(roles, "Success.", 200)); } else { return BadRequest(ApiResponse.ErrorResponse("This employee has no assigned permissions.", "This employee has no assigned permissions.", 400)); } } [HttpGet] [Route("list/{projectid?}")] public async Task 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.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 var projectIds = await _projectServices.GetMyProjectIdsAsync(tenantId, loggedInEmployee); var hasViewAllEmployeesPermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); var hasViewTeamMembersPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id); List 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.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.SuccessResponse(result, "Filter applied.", 200)); } [HttpGet("basic")] public async Task GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString) { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var employeeQuery = _context.Employees.Where(e => e.TenantId == tenantId); if (projectId != null && projectId == Guid.Empty) { var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); if (!hasProjectPermission) { _logger.LogWarning("User {EmployeeId} attempts to get employee for project {ProjectId}, but not have access to the project", loggedInEmployee.Id, projectId); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "User do not have access to view the list for this project", 403)); } var employeeIds = await _context.ProjectAllocations.Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.TenantId == tenantId).Select(p => p.EmployeeId).ToListAsync(); employeeQuery = employeeQuery.Where(e => employeeIds.Contains(e.Id)); } if (!string.IsNullOrWhiteSpace(searchString)) { var searchStringLower = searchString.ToLower(); employeeQuery = employeeQuery.Where(e => (e.FirstName + " " + e.LastName).ToLower().Contains(searchStringLower)); } var response = await employeeQuery.Select(e => _mapper.Map(e)).ToListAsync(); return Ok(ApiResponse.SuccessResponse(response, $"{response.Count} records of employees fetched successfully", 200)); } [HttpGet] [Route("search/{name}/{projectid?}")] public async Task SearchEmployee(string name, Guid? projectid) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } var result = await _employeeHelper.SearchEmployeeByProjectId(GetTenantId(), name.ToLower(), projectid); return Ok(ApiResponse.SuccessResponse(result, "Filter applied.", 200)); } [HttpGet] [Route("profile/get/{employeeId}")] public async Task GetEmployeeProfileById(Guid employeeId) { if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Employee emp = await _employeeHelper.GetEmployeeByID(employeeId); EmployeeVM employeeVM = EmployeeMapper.ToEmployeeVMFromEmployee(emp); return Ok(ApiResponse.SuccessResponse(employeeVM, "Employee Profile.", 200)); } private Guid GetTenantId() { return _userHelper.GetTenantId(); } //[HttpPost("manage/quick")] //public async Task CreateQuickUser([FromBody] CreateQuickUserDto model) //{ // return Ok("Pending implementation"); //} [HttpPost("manage")] public async Task CreateUser([FromBody] CreateUserDto model) { Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); Guid employeeId = Guid.Empty; if (model == null) return BadRequest(ApiResponse.ErrorResponse("Invalid data", "Invaild Data", 400)); if (model.FirstName == null && model.PhoneNumber == null) return BadRequest(ApiResponse.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.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.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.SuccessResponse("Success.", responsemessage, 200)); } [HttpPost("manage-mobile")] public async Task 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.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.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.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.SuccessResponse(employeeVM, "Employee created successfully", 200)); } else { Employee? existingEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == model.Id.Value); if (existingEmployee == null) { _logger.LogWarning("User tries to update employee {EmployeeId} but not found in database", model.Id); return NotFound(ApiResponse.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.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.SuccessResponse(employeeVM, "Employee updated successfully", 200)); } } [HttpDelete("{id}")] public async Task 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.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 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.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.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 allocations = new List(); 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.LogWarning("Employee with ID {EmploueeId} not found in database", id); } return Ok(ApiResponse.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 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; } } }