1178 lines
59 KiB
C#
1178 lines
59 KiB
C#
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.Helpers;
|
|
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.CodeAnalysis;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Data;
|
|
using System.Net;
|
|
|
|
namespace MarcoBMS.Services.Controllers
|
|
{
|
|
[Route("api/[controller]")]
|
|
[ApiController]
|
|
[Authorize]
|
|
|
|
public class EmployeeController : ControllerBase
|
|
{
|
|
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
|
private readonly ApplicationDbContext _context;
|
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly IEmailSender _emailSender;
|
|
private readonly EmployeeHelper _employeeHelper;
|
|
private readonly UserHelper _userHelper;
|
|
private readonly GeneralHelper _generalHelper;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILoggingService _logger;
|
|
private readonly IHubContext<MarcoHub> _signalR;
|
|
private readonly PermissionServices _permission;
|
|
private readonly IMapper _mapper;
|
|
private readonly IProjectServices _projectServices;
|
|
private readonly Guid tenantId;
|
|
private readonly Guid organizationId;
|
|
|
|
|
|
public EmployeeController(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
|
IServiceScopeFactory serviceScopeFactory,
|
|
UserManager<ApplicationUser> userManager,
|
|
IEmailSender emailSender,
|
|
ApplicationDbContext context,
|
|
EmployeeHelper employeeHelper,
|
|
UserHelper userHelper,
|
|
IConfiguration configuration,
|
|
ILoggingService logger,
|
|
IHubContext<MarcoHub> signalR,
|
|
PermissionServices permission,
|
|
IProjectServices projectServices,
|
|
IMapper mapper,
|
|
GeneralHelper generalHelper)
|
|
{
|
|
_dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory));
|
|
_serviceScopeFactory = serviceScopeFactory;
|
|
_context = context;
|
|
_userManager = userManager;
|
|
_emailSender = emailSender;
|
|
_employeeHelper = employeeHelper;
|
|
_userHelper = userHelper;
|
|
_generalHelper = generalHelper;
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
_signalR = signalR;
|
|
_permission = permission;
|
|
_projectServices = projectServices;
|
|
_mapper = mapper;
|
|
tenantId = _userHelper.GetTenantId();
|
|
organizationId = _userHelper.GetCurrentOrganizationId();
|
|
}
|
|
|
|
[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("list/organizations/{projectId}")]
|
|
public async Task<IActionResult> GetEmployeesByProjectAsync(Guid projectId, [FromQuery] string searchString, [FromQuery] Guid? organizationId)
|
|
{
|
|
try
|
|
{
|
|
// Get the currently logged-in employee information
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
var projectTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.Projects.FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
|
|
});
|
|
|
|
var tenantTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
|
|
});
|
|
|
|
await Task.WhenAll(projectTask, tenantTask);
|
|
|
|
var project = projectTask.Result;
|
|
var tenant = tenantTask.Result;
|
|
|
|
if (project == null || tenant == null)
|
|
{
|
|
_logger.LogWarning("Project {ProjectId} not found in database for tenant {TenantId}", projectId, tenantId);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404));
|
|
}
|
|
// Check if the logged-in employee has permission for the requested project
|
|
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId);
|
|
if (!hasProjectPermission)
|
|
{
|
|
_logger.LogWarning("User {EmployeeId} attempts to get employees for project {ProjectId} without permission", loggedInEmployee.Id, projectId);
|
|
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access denied", "User does not have access to view the employees for this project", 403));
|
|
}
|
|
|
|
var organizationQuery = _context.ProjectOrgMappings
|
|
.Include(po => po.ProjectService)
|
|
.Where(po => po.ProjectService != null && po.ProjectService.ProjectId == projectId);
|
|
|
|
if (loggedInEmployee.OrganizationId != project.PMCId && loggedInEmployee.OrganizationId != project.PromoterId && loggedInEmployee.OrganizationId != tenant.OrganizationId)
|
|
{
|
|
organizationQuery = organizationQuery.Where(po => po.ParentOrganizationId == loggedInEmployee.OrganizationId || po.OrganizationId == loggedInEmployee.OrganizationId);
|
|
}
|
|
|
|
var organizationIds = await organizationQuery.Select(po => po.OrganizationId).ToListAsync();
|
|
|
|
if (loggedInEmployee.OrganizationId == project.PMCId || loggedInEmployee.OrganizationId == project.PromoterId || loggedInEmployee.OrganizationId == tenant.OrganizationId)
|
|
{
|
|
organizationIds.Add(project.PMCId);
|
|
organizationIds.Add(project.PromoterId);
|
|
organizationIds.Add(tenant.OrganizationId);
|
|
}
|
|
|
|
// Fetch employees allocated to the project matching the search criteria
|
|
var employeesQuery = _context.Employees
|
|
.AsNoTracking() // Improves performance by disabling change tracking for read-only query
|
|
.Include(e => e.JobRole)
|
|
.Where(e => (e.FirstName + " " + e.LastName).Contains(searchString) && organizationIds.Contains(e.OrganizationId));
|
|
|
|
if (organizationId.HasValue)
|
|
{
|
|
employeesQuery = employeesQuery.Where(e => e.OrganizationId == organizationId);
|
|
}
|
|
|
|
var employees = await employeesQuery
|
|
.ToListAsync();
|
|
|
|
var result = employees.Select(e => _mapper.Map<EmployeeVM>(e)).Distinct().ToList();
|
|
_logger.LogInfo("Employees fetched for project {ProjectId} by user {EmployeeId}. Count: {Count}", projectId, loggedInEmployee.Id, employees.Count);
|
|
|
|
// Return the employee list wrapped in a successful API response
|
|
return Ok(ApiResponse<object>.SuccessResponse(result, "Employee list fetched successfully", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log the exception and return a 500 status code with error message
|
|
_logger.LogError(ex, "Error occurred while fetching employees for project {ProjectId}", projectId);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal server error", "An unexpected error occurred", 500));
|
|
}
|
|
}
|
|
|
|
|
|
[HttpGet("list/{projectId?}")]
|
|
public async Task<IActionResult> GetEmployeesByProjectAsync(Guid? projectId, [FromQuery] bool showInactive = false)
|
|
{
|
|
// 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));
|
|
}
|
|
|
|
List<EmployeeVM> result = new List<EmployeeVM>();
|
|
try
|
|
{
|
|
// Dependency injection scope for services
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
|
|
// Step 2: Get logged-in employee details
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
_logger.LogInfo("GetEmployeesByProject called. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, showInactive: {ShowInactive}",
|
|
loggedInEmployee.Id, projectId ?? Guid.Empty, showInactive);
|
|
|
|
// Step 3: Fetch permissions concurrently
|
|
var viewAllTask = Task.Run(async () =>
|
|
{
|
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
return await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id);
|
|
});
|
|
var viewTeamTask = Task.Run(async () =>
|
|
{
|
|
var _permission = scope.ServiceProvider.GetRequiredService<PermissionServices>();
|
|
return await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id);
|
|
});
|
|
|
|
await Task.WhenAll(viewAllTask, viewTeamTask);
|
|
|
|
var hasViewAllEmployeesPermission = viewAllTask.Result;
|
|
var hasViewTeamMembersPermission = viewTeamTask.Result;
|
|
|
|
List<Employee> employees = new List<Employee>();
|
|
|
|
// Step 4: Query based on permission
|
|
if (hasViewAllEmployeesPermission && !projectId.HasValue)
|
|
{
|
|
// OrganizationId needs to be retrieved from loggedInEmployee or context based on your app's structure
|
|
var employeeQuery = _context.Employees
|
|
.AsNoTracking() // Optimize EF query for read-only operation[web:1][web:13][web:18]
|
|
.Include(e => e.JobRole)
|
|
.Where(e => e.OrganizationId == organizationId);
|
|
|
|
employeeQuery = showInactive
|
|
? employeeQuery.Where(e => !e.IsActive)
|
|
: employeeQuery.Where(e => e.IsActive);
|
|
|
|
employees = await employeeQuery.ToListAsync();
|
|
_logger.LogInfo("Employee list fetched with full access. Count: {Count}", employees.Count);
|
|
}
|
|
else if (hasViewTeamMembersPermission && !showInactive && !projectId.HasValue)
|
|
{
|
|
// Only active team members with limited permission
|
|
var projectIds = await _projectServices.GetMyProjectIdsAsync(tenantId, loggedInEmployee);
|
|
|
|
employees = await _context.ProjectAllocations
|
|
.AsNoTracking()
|
|
.Include(pa => pa.Employee)
|
|
.ThenInclude(e => e!.JobRole)
|
|
.Where(pa =>
|
|
projectIds.Contains(pa.ProjectId)
|
|
&& pa.IsActive
|
|
&& pa.Employee != null
|
|
&& pa.Employee.IsActive
|
|
&& pa.TenantId == tenantId)
|
|
.Select(pa => pa.Employee!)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
_logger.LogInfo("Employee list fetched with limited access (active only). Count: {Count}", employees.Count);
|
|
}
|
|
|
|
// If a specific projectId is provided, override employee fetching to ensure strict project context
|
|
if (projectId.HasValue)
|
|
{
|
|
employees = await _context.ProjectAllocations
|
|
.AsNoTracking()
|
|
.Include(pa => pa.Employee)
|
|
.ThenInclude(e => e!.JobRole)
|
|
.Where(pa =>
|
|
pa.ProjectId == projectId
|
|
&& pa.IsActive
|
|
&& pa.Employee != null
|
|
&& pa.Employee.IsActive
|
|
&& pa.TenantId == tenantId)
|
|
.Select(pa => pa.Employee!)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
_logger.LogInfo("Employee list fetched for specific project. ProjectId: {ProjectId}. Count: {Count}",
|
|
projectId, employees.Count);
|
|
}
|
|
|
|
// Step 5: Map to view model
|
|
result = employees.Select(e => _mapper.Map<EmployeeVM>(e)).Distinct().ToList();
|
|
|
|
_logger.LogInfo("Employees successfully fetched. EmployeeId: {EmployeeId} for ProjectId: {ProjectId}. Final Count: {Count}",
|
|
loggedInEmployee.Id, projectId ?? Guid.Empty, result.Count);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(result, "Filter applied.", 200));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Step 6: Error logging and response[web:6]
|
|
_logger.LogError(ex, "Exception occurred while getting the list of employees");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal server error. Please try again later.", null, 500));
|
|
}
|
|
}
|
|
|
|
|
|
[HttpGet("basic")]
|
|
public async Task<IActionResult> 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<object>.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.Take(10).Select(e => _mapper.Map<BasicEmployeeVM>(e)).ToListAsync();
|
|
return Ok(ApiResponse<object>.SuccessResponse(response, $"{response.Count} records of employees fetched successfully", 200));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a paginated list of employees assigned to a specified project (if provided),
|
|
/// with optional search functionality.
|
|
/// Ensures that the logged-in user has necessary permissions before accessing project employees.
|
|
/// </summary>
|
|
/// <param name="projectId">Optional project identifier to filter employees by project.</param>
|
|
/// <param name="searchString">Optional search string to filter employees by name.</param>
|
|
/// <param name="pageNumber">Page number for pagination (default = 1).</param>
|
|
/// <returns>Paginated list of employees in BasicEmployeeVM format wrapped in ApiResponse.</returns>
|
|
|
|
[HttpGet("search")]
|
|
public async Task<IActionResult> GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString,
|
|
[FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
|
|
{
|
|
// Log API entry with context
|
|
_logger.LogInfo("Fetching employees. ProjectId: {ProjectId}, SearchString: {SearchString}, PageNumber: {PageNumber}",
|
|
projectId ?? Guid.Empty, searchString ?? "", pageNumber);
|
|
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
_logger.LogDebug("Logged-in EmployeeId: {EmployeeId}", loggedInEmployee.Id);
|
|
|
|
// Initialize query scoped by tenant
|
|
var employeeQuery = _context.Employees.Where(e => e.TenantId == tenantId);
|
|
|
|
// Filter by project if projectId is supplied
|
|
if (projectId.HasValue && projectId.Value != Guid.Empty)
|
|
{
|
|
_logger.LogDebug("Project filter applied. Checking permission for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}",
|
|
loggedInEmployee.Id, projectId);
|
|
|
|
// Validate project access permission
|
|
var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value);
|
|
if (!hasProjectPermission)
|
|
{
|
|
_logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have permission for ProjectId: {ProjectId}",
|
|
loggedInEmployee.Id, projectId);
|
|
|
|
return StatusCode(403, ApiResponse<object>.ErrorResponse(
|
|
"Access denied",
|
|
"User does not have access to view employees for this project",
|
|
403));
|
|
}
|
|
|
|
// Employees allocated to the project
|
|
var employeeIds = await _context.ProjectAllocations
|
|
.Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.TenantId == tenantId)
|
|
.Select(pa => pa.EmployeeId)
|
|
.ToListAsync();
|
|
|
|
_logger.LogDebug("Project employees retrieved. Total linked employees found: {Count}", employeeIds.Count);
|
|
|
|
// Apply project allocation filter
|
|
employeeQuery = employeeQuery.Where(e => employeeIds.Contains(e.Id));
|
|
}
|
|
|
|
// Apply search filter if provided
|
|
if (!string.IsNullOrWhiteSpace(searchString))
|
|
{
|
|
var searchStringLower = searchString.ToLower();
|
|
_logger.LogDebug("Search filter applied. Search term: {SearchTerm}", searchStringLower);
|
|
|
|
employeeQuery = employeeQuery.Where(e =>
|
|
(e.FirstName + " " + e.LastName).ToLower().Contains(searchStringLower));
|
|
}
|
|
|
|
// Pagination and Projection (executed in DB)
|
|
var employees = await employeeQuery
|
|
.Skip((pageNumber - 1) * pageSize)
|
|
.Take(pageSize)
|
|
.Select(e => _mapper.Map<BasicEmployeeVM>(e))
|
|
.ToListAsync();
|
|
|
|
_logger.LogInfo("Employees fetched successfully. Records returned: {Count}", employees.Count);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(
|
|
employees,
|
|
$"{employees.Count} employee records fetched successfully",
|
|
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("old/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
|
|
|
|
};
|
|
var isSeatsAvaiable = await _generalHelper.CheckSeatsRemainingAsync(tenantId);
|
|
if (!isSeatsAvaiable)
|
|
{
|
|
_logger.LogWarning("Maximum number of users reached for Tenant {TenantId}", tenantId);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Maximum number of users reached. Cannot add new user", "Maximum number of users reached. Cannot add new user", 400));
|
|
}
|
|
// 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")]
|
|
public async Task<IActionResult> CreateEmployeeAsync([FromBody] CreateUserDto model)
|
|
{
|
|
// Correlation and context capture for logs
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
{
|
|
if (model == null)
|
|
{
|
|
_logger.LogWarning("Model is null in CreateEmployeeAsync");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid payload", "Request body is required", 400));
|
|
}
|
|
|
|
// Basic validation
|
|
if (model.HasApplicationAccess && string.IsNullOrWhiteSpace(model.Email))
|
|
{
|
|
_logger.LogWarning("Application access requested but email is missing");
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid email", "Application users must have a valid email", 400));
|
|
}
|
|
|
|
await using var transaction = await _context.Database.BeginTransactionAsync();
|
|
try
|
|
{
|
|
// Load existing employee if updating, constrained by organization scope
|
|
Employee? existingEmployee = null;
|
|
if (model.Id.HasValue && model.Id.Value != Guid.Empty)
|
|
{
|
|
existingEmployee = await _context.Employees
|
|
.FirstOrDefaultAsync(e => e.Id == model.Id && e.OrganizationId == model.OrganizationId);
|
|
if (existingEmployee == null)
|
|
{
|
|
_logger.LogInfo("Employee not found for update. Id={EmployeeId}, Org={OrgId}", model.Id, model.OrganizationId);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee not found in database", 404));
|
|
}
|
|
}
|
|
|
|
// Identity user creation path (only if needed)
|
|
ApplicationUser? identityUserToCreate = null;
|
|
ApplicationUser? createdIdentityUser = null;
|
|
|
|
if (model.HasApplicationAccess)
|
|
{
|
|
// Only attempt identity resolution/creation if email supplied and either:
|
|
// - Creating new employee, or
|
|
// - Updating but existing employee does not have ApplicationUserId
|
|
var needsIdentity = string.IsNullOrWhiteSpace(existingEmployee?.ApplicationUserId);
|
|
if (needsIdentity && !string.IsNullOrWhiteSpace(model.Email))
|
|
{
|
|
var existingUser = await _userManager.FindByEmailAsync(model.Email);
|
|
if (existingUser == null)
|
|
{
|
|
// Seat check only when provisioning a new identity user
|
|
var isSeatsAvailable = await _generalHelper.CheckSeatsRemainingAsync(tenantId);
|
|
if (!isSeatsAvailable)
|
|
{
|
|
_logger.LogWarning("Maximum users reached for Tenant {TenantId}. Cannot create identity user for {Email}", tenantId, model.Email);
|
|
return BadRequest(ApiResponse<object>.ErrorResponse(
|
|
"Maximum number of users reached. Cannot add new user",
|
|
"Maximum number of users reached. Cannot add new user", 400));
|
|
}
|
|
|
|
identityUserToCreate = new ApplicationUser
|
|
{
|
|
UserName = model.Email,
|
|
Email = model.Email,
|
|
EmailConfirmed = true
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// If identity exists, re-use it; do not re-create
|
|
createdIdentityUser = existingUser;
|
|
}
|
|
}
|
|
}
|
|
|
|
// For create path: enforce uniqueness of employee email if applicable to business rules
|
|
// Consider adding a unique filtered index: (OrganizationId, Email) WHERE Email IS NOT NULL
|
|
if (!model.Id.HasValue || model.Id == Guid.Empty)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(model.Email))
|
|
{
|
|
var emailExists = await _context.Employees
|
|
.AnyAsync(e => e.Email == model.Email && e.OrganizationId == model.OrganizationId);
|
|
if (emailExists)
|
|
{
|
|
_logger.LogInfo("Employee email already exists in org. Email={Email}, Org={OrgId}", model.Email, model.OrganizationId);
|
|
return StatusCode(403, ApiResponse<object>.ErrorResponse(
|
|
"Employee with email already exists",
|
|
"Employee with this email already exists", 403));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create identity user if needed
|
|
if (identityUserToCreate != null && !string.IsNullOrWhiteSpace(identityUserToCreate.Email))
|
|
{
|
|
var createResult = await _userManager.CreateAsync(identityUserToCreate, "User@123");
|
|
if (!createResult.Succeeded)
|
|
{
|
|
_logger.LogWarning("Failed to create identity user for {Email}. Errors={Errors}",
|
|
identityUserToCreate.Email,
|
|
string.Join(", ", createResult.Errors.Select(e => $"{e.Code}:{e.Description}")));
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to create user", createResult.Errors, 400));
|
|
}
|
|
|
|
createdIdentityUser = identityUserToCreate;
|
|
_logger.LogInfo("Identity user created. IdentityUserId={UserId}, Email={Email}",
|
|
createdIdentityUser.Id, createdIdentityUser.Email);
|
|
}
|
|
|
|
// Prepare reset link sender helper
|
|
async Task SendResetIfApplicableAsync(ApplicationUser u, string firstName)
|
|
{
|
|
var token = await _userManager.GeneratePasswordResetTokenAsync(u);
|
|
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
|
|
await _emailSender.SendResetPasswordEmailOnRegister(u.Email ?? "", firstName, resetLink);
|
|
_logger.LogInfo("Reset password email queued. Email={Email}", u.Email ?? "");
|
|
}
|
|
|
|
Guid employeeId;
|
|
EmployeeVM employeeVM;
|
|
string responseMessage;
|
|
|
|
if (existingEmployee != null)
|
|
{
|
|
// Update flow
|
|
_mapper.Map(model, existingEmployee);
|
|
|
|
if (createdIdentityUser != null && !string.IsNullOrWhiteSpace(createdIdentityUser.Email))
|
|
{
|
|
existingEmployee.ApplicationUserId = createdIdentityUser.Id;
|
|
await SendResetIfApplicableAsync(createdIdentityUser, existingEmployee.FirstName ?? "User");
|
|
}
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
employeeId = existingEmployee.Id;
|
|
employeeVM = _mapper.Map<EmployeeVM>(existingEmployee);
|
|
responseMessage = "Employee Updated Successfully";
|
|
|
|
_logger.LogInfo("Employee updated. EmployeeId={EmployeeId}, Org={OrgId}", employeeId, existingEmployee.OrganizationId);
|
|
}
|
|
else
|
|
{
|
|
// Create flow
|
|
var newEmployee = _mapper.Map<Employee>(model);
|
|
newEmployee.IsSystem = false;
|
|
newEmployee.IsActive = true;
|
|
newEmployee.IsPrimary = false;
|
|
|
|
if (createdIdentityUser != null && !string.IsNullOrWhiteSpace(createdIdentityUser.Email))
|
|
{
|
|
newEmployee.ApplicationUserId = createdIdentityUser.Id;
|
|
await SendResetIfApplicableAsync(createdIdentityUser, newEmployee.FirstName ?? "User");
|
|
}
|
|
|
|
await _context.Employees.AddAsync(newEmployee);
|
|
await _context.SaveChangesAsync();
|
|
|
|
employeeId = newEmployee.Id;
|
|
employeeVM = _mapper.Map<EmployeeVM>(newEmployee);
|
|
responseMessage = "Employee Created Successfully";
|
|
|
|
_logger.LogInfo("Employee created. EmployeeId={EmployeeId}, Org={OrgId}", employeeId, newEmployee.OrganizationId);
|
|
}
|
|
|
|
await transaction.CommitAsync();
|
|
|
|
// SignalR notification
|
|
var notification = new
|
|
{
|
|
LoggedInUserId = loggedInEmployee.Id,
|
|
Keyword = "Employee",
|
|
EmployeeId = employeeId
|
|
};
|
|
|
|
// Consider broadcasting to tenant/organization group instead of Clients.All to avoid cross-tenant leaks:
|
|
// await _signalR.Clients.Group($"org:{model.OrganizationId}").SendAsync("NotificationEventHandler", notification);
|
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
|
|
|
_logger.LogInfo("Notification broadcasted for EmployeeId={EmployeeId}", employeeId);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(employeeVM, responseMessage, 200));
|
|
}
|
|
catch (DbUpdateException dbEx)
|
|
{
|
|
await transaction.RollbackAsync();
|
|
_logger.LogError(dbEx, "Database exception occurred while managing employee");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse(
|
|
"Internal exception occurred",
|
|
"Internal database exception has occurred", 500));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await transaction.RollbackAsync();
|
|
_logger.LogError(ex, "Unhandled exception occurred while managing employee");
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse(
|
|
"Internal exception occurred",
|
|
"Internal exception has occurred", 500));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
[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.LogWarning("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.JoiningDate = model.JoiningDate;
|
|
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));
|
|
}
|
|
}
|
|
|
|
[HttpPost("app/manage")]
|
|
public async Task<IActionResult> CreateUserMobileAsync([FromBody] MobileUserManageDto model)
|
|
{
|
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
if (tenantId == Guid.Empty)
|
|
{
|
|
_logger.LogWarning("Tenant resolution failed in CreateUserMobile"); // structured warning
|
|
return StatusCode(403, ApiResponse<object>.ErrorResponse("Unauthorized tenant context", "Unauthorized", 403));
|
|
}
|
|
|
|
if (model is null)
|
|
{
|
|
_logger.LogWarning("Null payload in CreateUserMobile for Tenant {TenantId}", tenantId); // validation log
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invalid data", 400));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(model.FirstName) || string.IsNullOrWhiteSpace(model.PhoneNumber))
|
|
{
|
|
_logger.LogWarning("Missing required fields FirstName/Phone for Tenant {TenantId}", tenantId); // validation log
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("First name and phone number are required.", "Required fields missing", 400));
|
|
}
|
|
|
|
// Strict Base64 parse
|
|
byte[]? imageBytes = null;
|
|
if (!string.IsNullOrWhiteSpace(model.ProfileImage))
|
|
{
|
|
try
|
|
{
|
|
imageBytes = Convert.FromBase64String(model.ProfileImage);
|
|
}
|
|
catch (FormatException ex)
|
|
{
|
|
_logger.LogError(ex, "Invalid base64 image in CreateUserMobile for Tenant {TenantId}", tenantId); // input issue
|
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid image format.", "Invalid image", 400));
|
|
}
|
|
}
|
|
if (model.Id == null || model.Id == Guid.Empty)
|
|
{
|
|
// Create path: map only allowed fields
|
|
var employee = new Employee
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = tenantId,
|
|
FirstName = model.FirstName.Trim(),
|
|
LastName = model.LastName?.Trim(),
|
|
Gender = model.Gender,
|
|
PhoneNumber = model.PhoneNumber,
|
|
JoiningDate = model.JoiningDate,
|
|
JobRoleId = model.JobRoleId,
|
|
Photo = imageBytes,
|
|
OrganizationId = model.OrganizationId
|
|
};
|
|
|
|
await _context.Employees.AddAsync(employee);
|
|
await _context.SaveChangesAsync();
|
|
|
|
var employeeVM = _mapper.Map<EmployeeVM>(employee);
|
|
|
|
var notification = new
|
|
{
|
|
LoggedInUserId = loggedInEmployee?.Id,
|
|
Keyword = "Employee",
|
|
EmployeeId = employee.Id
|
|
};
|
|
|
|
// Consider broadcasting to tenant/organization group instead of Clients.All to avoid cross-tenant leaks:
|
|
// await _signalR.Clients.Group($"org:{model.OrganizationId}").SendAsync("NotificationEventHandler", notification);
|
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
|
|
|
_logger.LogInfo("Employee {EmployeeId} created in Tenant {TenantId}", employee.Id, tenantId); // success
|
|
return Ok(ApiResponse<object>.SuccessResponse(employeeVM, "Employee created successfully", 200));
|
|
}
|
|
else
|
|
{
|
|
// Update path: fetch scoped to tenant
|
|
var employeeId = model.Id.Value;
|
|
var existingEmployee = await _context.Employees
|
|
.FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == tenantId); // tenant-safe lookup
|
|
|
|
if (existingEmployee is null)
|
|
{
|
|
_logger.LogWarning("Update attempted for missing Employee {EmployeeId} in Tenant {TenantId}", employeeId, tenantId); // not found
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Not found", 404));
|
|
}
|
|
|
|
// Update allowed fields only
|
|
existingEmployee.FirstName = model.FirstName.Trim();
|
|
existingEmployee.LastName = model.LastName?.Trim();
|
|
existingEmployee.Gender = model.Gender;
|
|
existingEmployee.PhoneNumber = model.PhoneNumber;
|
|
existingEmployee.JoiningDate = model.JoiningDate;
|
|
existingEmployee.JobRoleId = model.JobRoleId;
|
|
existingEmployee.OrganizationId = model.OrganizationId;
|
|
|
|
if (imageBytes != null)
|
|
{
|
|
existingEmployee.Photo = imageBytes;
|
|
}
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
var employeeVM = _mapper.Map<EmployeeVM>(existingEmployee);
|
|
|
|
var notification = new
|
|
{
|
|
LoggedInUserId = loggedInEmployee?.Id,
|
|
Keyword = "Employee",
|
|
EmployeeId = employeeId
|
|
};
|
|
|
|
// Consider broadcasting to tenant/organization group instead of Clients.All to avoid cross-tenant leaks:
|
|
// await _signalR.Clients.Group($"org:{model.OrganizationId}").SendAsync("NotificationEventHandler", notification);
|
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
|
|
|
_logger.LogInfo("Employee {EmployeeId} updated in Tenant {TenantId}", existingEmployee.Id, tenantId); // success
|
|
return Ok(ApiResponse<object>.SuccessResponse(employeeVM, "Employee updated successfully", 200));
|
|
}
|
|
}
|
|
|
|
|
|
[HttpDelete("{id}")]
|
|
public async Task<IActionResult> SuspendEmployee(Guid id, [FromQuery] bool active = false)
|
|
{
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == id && e.TenantId == tenantId);
|
|
if (employee == null)
|
|
{
|
|
_logger.LogWarning("Employee with ID {EmploueeId} not found in database", id);
|
|
return NotFound(ApiResponse<object>.ErrorResponse("Employee Not found successfully", "Employee Not found successfully", 404));
|
|
}
|
|
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));
|
|
}
|
|
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));
|
|
}
|
|
if (active)
|
|
{
|
|
employee.IsActive = true;
|
|
var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Id == employee.ApplicationUserId);
|
|
if (user != null)
|
|
{
|
|
user.IsActive = true;
|
|
_logger.LogInfo("The application user associated with employee ID {EmployeeId} has been actived.", employee.Id);
|
|
|
|
}
|
|
_logger.LogInfo("Employee with ID {EmployeId} Actived successfully", employee.Id);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
}
|
|
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
|
|
|
|
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
|
|
|
|
_ = Task.Run(async () =>
|
|
{
|
|
// --- 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.
|
|
|
|
await _firebase.SendEmployeeSuspendMessageAsync(employee.Id, tenantId);
|
|
|
|
});
|
|
|
|
}
|
|
try
|
|
{
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
catch (DbUpdateException ex)
|
|
{
|
|
_logger.LogError(ex, "Exception Occured While activting/deactivting employee {EmployeeId}", employee.Id);
|
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal Error Occured", "Error occured while saving the entity", 500));
|
|
}
|
|
var notification = new { LoggedInUserId = LoggedEmployee.Id, Keyword = "Employee", EmployeeId = employee.Id };
|
|
|
|
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
|
|
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,
|
|
Gender = model.Gender,
|
|
MiddleName = model.MiddleName,
|
|
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.Gender = model.Gender;
|
|
existingEmployee.MiddleName = model.MiddleName;
|
|
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;
|
|
|
|
}
|
|
}
|
|
}
|