using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Activities; using Marco.Pms.Model.Dtos.Project; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Mapper; using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Services.Helpers; 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.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using MongoDB.Driver; namespace MarcoBMS.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class ProjectController : ControllerBase { private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; //private readonly RolesHelper _rolesHelper; private readonly ProjectsHelper _projectsHelper; private readonly IHubContext _signalR; private readonly PermissionServices _permission; private readonly CacheUpdateHelper _cache; private readonly IMapper _mapper; private readonly Guid tenantId; public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, IHubContext signalR, PermissionServices permission, CacheUpdateHelper cache, IMapper mapper) { _context = context; _userHelper = userHelper; _logger = logger; //_rolesHelper = rolesHelper; _projectsHelper = projectHelper; _signalR = signalR; _cache = cache; _permission = permission; _mapper = mapper; tenantId = _userHelper.GetTenantId(); } [HttpGet("list/basic1")] public async Task GetAllProjects1() { 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 = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Defensive check for null employee (important for robust APIs) if (LoggedInEmployee == null) { return Unauthorized(ApiResponse.ErrorResponse("Employee not found.", null, 401)); } List response = new List(); List projectIds = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee); List? projectsDetails = await _cache.GetProjectDetailsList(projectIds); if (projectsDetails == null) { List projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); //using (var scope = _serviceScopeFactory.CreateScope()) //{ // var cacheHelper = scope.ServiceProvider.GetRequiredService(); //} foreach (var project in projects) { await _cache.AddProjectDetails(project); } response = projects.Select(p => _mapper.Map(p)).ToList(); } else { response = projectsDetails.Select(p => _mapper.Map(p)).ToList(); } return Ok(ApiResponse.SuccessResponse(response, "Success.", 200)); } [HttpGet("list/basic")] public async Task GetAllProjects() // Renamed for clarity { // Step 1: Get the current user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (loggedInEmployee == null) { return Unauthorized(ApiResponse.ErrorResponse("Unauthorized", "User could not be identified.", 401)); } _logger.LogInfo("Basic project list requested by EmployeeId {EmployeeId}", loggedInEmployee.Id); // Step 2: Get the list of project IDs the user has access to Guid tenantId = _userHelper.GetTenantId(); // Assuming this is still needed by the helper List accessibleProjectIds = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee); if (accessibleProjectIds == null || !accessibleProjectIds.Any()) { _logger.LogInfo("No accessible projects found for EmployeeId {EmployeeId}", loggedInEmployee.Id); return Ok(ApiResponse>.SuccessResponse(new List(), "Success.", 200)); } // Step 3: Fetch project ViewModels using the optimized, cache-aware helper var projectVMs = await GetProjectInfosByIdsAsync(accessibleProjectIds); // Step 4: Return the final list _logger.LogInfo("Successfully returned {ProjectCount} projects for EmployeeId {EmployeeId}", projectVMs.Count, loggedInEmployee.Id); return Ok(ApiResponse>.SuccessResponse(projectVMs, $"{projectVMs.Count} records of project fetchd successfully", 200)); } /// /// Retrieves a list of ProjectInfoVMs by their IDs, using an efficient partial cache-hit strategy. /// It fetches what it can from the cache (as ProjectMongoDB), gets the rest from the /// database (as Project), updates the cache, and returns a unified list of ViewModels. /// /// The list of project IDs to retrieve. /// A list of ProjectInfoVMs. private async Task> GetProjectInfosByIdsAsync(List projectIds) { // --- Step 1: Fetch from Cache --- // The cache returns a list of MongoDB documents for the projects it found. var cachedMongoDocs = await _cache.GetProjectDetailsList(projectIds) ?? new List(); var finalViewModels = _mapper.Map>(cachedMongoDocs); _logger.LogDebug("Cache hit for {CacheCount} of {TotalCount} projects.", finalViewModels.Count, projectIds.Count); // --- Step 2: Identify Missing Projects --- // If we found everything in the cache, we can return early. if (finalViewModels.Count == projectIds.Count) { return finalViewModels; } var cachedIds = cachedMongoDocs.Select(p => p.Id).ToHashSet(); // Assuming ProjectMongoDB has an Id var missingIds = projectIds.Where(id => !cachedIds.Contains(id.ToString())).ToList(); // --- Step 3: Fetch Missing from Database --- if (missingIds.Any()) { _logger.LogDebug("Cache miss for {MissingCount} projects. Querying database.", missingIds.Count); var projectsFromDb = await _context.Projects .Where(p => missingIds.Contains(p.Id)) .AsNoTracking() // Use AsNoTracking for read-only query performance .ToListAsync(); if (projectsFromDb.Any()) { // Map the newly fetched projects (from SQL) to their ViewModel var vmsFromDb = _mapper.Map>(projectsFromDb); finalViewModels.AddRange(vmsFromDb); // --- Step 4: Update Cache with Missing Items in a new scope --- _logger.LogDebug("Updating cache with {DbCount} newly fetched projects.", projectsFromDb.Count); await _cache.AddProjectDetailsList(projectsFromDb); } } return finalViewModels; } [HttpGet("list")] public async Task GetAll() { 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 = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); //List featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(LoggedInEmployee.Id); //string[] projectsId = []; //List projects = new List(); ///* User with permission manage project can see all projects */ //if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614")) //{ // projects = await _projectsHelper.GetAllProjectByTanentID(LoggedInEmployee.TenantId); //} //else //{ // List allocation = await _projectsHelper.GetProjectByEmployeeID(LoggedInEmployee.Id); // projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray(); // projects = await _context.Projects.Where(c => projectsId.Contains(c.Id.ToString()) && c.TenantId == tenantId).ToListAsync(); //} //List projects = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee); ////List projects = new List(); /// List response = new List(); List projectIds = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee); var projectsDetails = await _cache.GetProjectDetailsList(projectIds); if (projectsDetails == null) { List projects = await _context.Projects.Where(p => projectIds.Contains(p.Id)).ToListAsync(); var teams = await _context.ProjectAllocations.Where(p => p.TenantId == tenantId && projectIds.Contains(p.ProjectId) && p.IsActive == true).ToListAsync(); List allBuildings = await _context.Buildings.Where(b => projectIds.Contains(b.ProjectId) && b.TenantId == tenantId).ToListAsync(); List idList = allBuildings.Select(b => b.Id).ToList(); List allFloors = await _context.Floor.Where(f => idList.Contains(f.BuildingId) && f.TenantId == tenantId).ToListAsync(); idList = allFloors.Select(f => f.Id).ToList(); List allWorkAreas = await _context.WorkAreas.Where(a => idList.Contains(a.FloorId) && a.TenantId == tenantId).ToListAsync(); idList = allWorkAreas.Select(a => a.Id).ToList(); List allWorkItems = await _context.WorkItems.Where(i => idList.Contains(i.WorkAreaId) && i.TenantId == tenantId).Include(i => i.ActivityMaster).ToListAsync(); foreach (var project in projects) { var result = _mapper.Map(project); var team = teams.Where(p => p.TenantId == tenantId && p.ProjectId == project.Id && p.IsActive == true).ToList(); result.TeamSize = team.Count(); List buildings = allBuildings.Where(b => b.ProjectId == project.Id && b.TenantId == tenantId).ToList(); idList = buildings.Select(b => b.Id).ToList(); List floors = allFloors.Where(f => idList.Contains(f.BuildingId) && f.TenantId == tenantId).ToList(); idList = floors.Select(f => f.Id).ToList(); List workAreas = allWorkAreas.Where(a => idList.Contains(a.FloorId) && a.TenantId == tenantId).ToList(); idList = workAreas.Select(a => a.Id).ToList(); List workItems = allWorkItems.Where(i => idList.Contains(i.WorkAreaId) && i.TenantId == tenantId).ToList(); double completedTask = 0; double plannedTask = 0; foreach (var workItem in workItems) { completedTask += workItem.CompletedWork; plannedTask += workItem.PlannedWork; } result.PlannedWork = plannedTask; result.CompletedWork = completedTask; response.Add(result); } } else { response = projectsDetails.Select(p => _mapper.Map(p)).ToList(); } return Ok(ApiResponse.SuccessResponse(response, "Success.", 200)); } [HttpGet("get/{id}")] public async Task Get([FromRoute] Guid id) { 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 project = await _context.Projects.Where(c => c.TenantId == _userHelper.GetTenantId() && c.Id == id).SingleOrDefaultAsync(); if (project == null) return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); return Ok(ApiResponse.SuccessResponse(project, "Success.", 200)); } [HttpGet("details/{id}")] public async Task Details([FromRoute] Guid id) { // Step 1: Validate model state if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); _logger.LogWarning("Invalid model state in Details endpoint. Errors: {@Errors}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } // Step 2: Get logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); _logger.LogInfo("Details requested by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}", loggedInEmployee.Id, id); // Step 3: Check global view project permission var hasViewProjectPermission = await _permission.HasPermission(PermissionsMaster.ViewProject, loggedInEmployee.Id); if (!hasViewProjectPermission) { _logger.LogWarning("ViewProjects permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have permission to view projects", 403)); } // Step 4: Check permission for this specific project var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, id); if (!hasProjectPermission) { _logger.LogWarning("Project-specific access denied. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}", loggedInEmployee.Id, id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to this project", 403)); } // Step 5: Fetch project with status var projectDetails = await _cache.GetProjectDetails(id); ProjectVM? projectVM = null; if (projectDetails == null) { var project = await _context.Projects .Include(c => c.ProjectStatus) .FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); projectVM = _mapper.Map(project); if (project != null) { await _cache.AddProjectDetails(project); } } else { projectVM = _mapper.Map(projectDetails); if (projectVM.ProjectStatus != null) { projectVM.ProjectStatus.TenantId = tenantId; } //projectVM = new ProjectVM //{ // Id = projectDetails.Id != null ? Guid.Parse(projectDetails.Id) : Guid.Empty, // Name = projectDetails.Name, // ShortName = projectDetails.ShortName, // ProjectAddress = projectDetails.ProjectAddress, // StartDate = projectDetails.StartDate, // EndDate = projectDetails.EndDate, // ContactPerson = projectDetails.ContactPerson, // ProjectStatus = new StatusMaster // { // Id = projectDetails.ProjectStatus?.Id != null ? Guid.Parse(projectDetails.ProjectStatus.Id) : Guid.Empty, // Status = projectDetails.ProjectStatus?.Status, // TenantId = tenantId // } // //ProjectStatusId = projectDetails.ProjectStatus?.Id != null ? Guid.Parse(projectDetails.ProjectStatus.Id) : Guid.Empty, //}; } if (projectVM == null) { _logger.LogWarning("Project not found. ProjectId: {ProjectId}", id); return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); } // Step 6: Return result _logger.LogInfo("Project details fetched successfully. ProjectId: {ProjectId}", id); return Ok(ApiResponse.SuccessResponse(projectVM, "Project details fetched successfully", 200)); } [HttpGet("details-old/{id}")] public async Task DetailsOld([FromRoute] Guid id) { // ProjectDetailsVM vm = new ProjectDetailsVM(); 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 project = await _context.Projects.Where(c => c.TenantId == _userHelper.GetTenantId() && c.Id == id).Include(c => c.ProjectStatus).SingleOrDefaultAsync(); // includeProperties: "ProjectStatus,Tenant"); //_context.Stock.FindAsync(id); if (project == null) { return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); } else { //var project = projects.Where(c => c.Id == id).SingleOrDefault(); ProjectDetailsVM vm = await GetProjectViewModel(id, project); OldProjectVM projectVM = new OldProjectVM(); if (vm.project != null) { projectVM.Id = vm.project.Id; projectVM.Name = vm.project.Name; projectVM.ShortName = vm.project.ShortName; projectVM.ProjectAddress = vm.project.ProjectAddress; projectVM.ContactPerson = vm.project.ContactPerson; projectVM.StartDate = vm.project.StartDate; projectVM.EndDate = vm.project.EndDate; projectVM.ProjectStatusId = vm.project.ProjectStatusId; } projectVM.Buildings = new List(); if (vm.buildings != null) { foreach (Building build in vm.buildings) { BuildingVM buildVM = new BuildingVM() { Id = build.Id, Description = build.Description, Name = build.Name }; buildVM.Floors = new List(); if (vm.floors != null) { foreach (Floor floorDto in vm.floors.Where(c => c.BuildingId == build.Id).ToList()) { FloorsVM floorVM = new FloorsVM() { FloorName = floorDto.FloorName, Id = floorDto.Id }; floorVM.WorkAreas = new List(); if (vm.workAreas != null) { foreach (WorkArea workAreaDto in vm.workAreas.Where(c => c.FloorId == floorVM.Id).ToList()) { WorkAreaVM workAreaVM = new WorkAreaVM() { Id = workAreaDto.Id, AreaName = workAreaDto.AreaName, WorkItems = new List() }; if (vm.workItems != null) { foreach (WorkItem workItemDto in vm.workItems.Where(c => c.WorkAreaId == workAreaDto.Id).ToList()) { WorkItemVM workItemVM = new WorkItemVM() { WorkItemId = workItemDto.Id, WorkItem = workItemDto }; workItemVM.WorkItem.WorkArea = new WorkArea(); if (workItemVM.WorkItem.ActivityMaster != null) { workItemVM.WorkItem.ActivityMaster.Tenant = new Tenant(); } workItemVM.WorkItem.Tenant = new Tenant(); double todaysAssigned = 0; if (vm.Tasks != null) { var tasks = vm.Tasks.Where(t => t.WorkItemId == workItemDto.Id).ToList(); foreach (TaskAllocation task in tasks) { todaysAssigned += task.PlannedTask; } } workItemVM.TodaysAssigned = todaysAssigned; workAreaVM.WorkItems.Add(workItemVM); } } floorVM.WorkAreas.Add(workAreaVM); } } buildVM.Floors.Add(floorVM); } } projectVM.Buildings.Add(buildVM); } } return Ok(ApiResponse.SuccessResponse(projectVM, "Success.", 200)); } } private async Task GetProjectViewModel(Guid? id, Project project) { ProjectDetailsVM vm = new ProjectDetailsVM(); // List buildings = _unitOfWork.Building.GetAll(c => c.ProjectId == id).ToList(); List buildings = await _context.Buildings.Where(c => c.ProjectId == id).ToListAsync(); List idList = buildings.Select(o => o.Id).ToList(); // List floors = _unitOfWork.Floor.GetAll(c => idList.Contains(c.Id)).ToList(); List floors = await _context.Floor.Where(c => idList.Contains(c.BuildingId)).ToListAsync(); idList = floors.Select(o => o.Id).ToList(); //List workAreas = _unitOfWork.WorkArea.GetAll(c => idList.Contains(c.Id), includeProperties: "WorkItems,WorkItems.ActivityMaster").ToList(); List workAreas = await _context.WorkAreas.Where(c => idList.Contains(c.FloorId)).ToListAsync(); idList = workAreas.Select(o => o.Id).ToList(); List workItems = await _context.WorkItems.Include(c => c.WorkCategoryMaster).Where(c => idList.Contains(c.WorkAreaId)).Include(c => c.ActivityMaster).ToListAsync(); // List workItems = _unitOfWork.WorkItem.GetAll(c => idList.Contains(c.WorkAreaId), includeProperties: "ActivityMaster").ToList(); idList = workItems.Select(t => t.Id).ToList(); List tasks = await _context.TaskAllocations.Where(t => idList.Contains(t.WorkItemId) && t.AssignmentDate.Date == DateTime.UtcNow.Date).ToListAsync(); vm.project = project; vm.buildings = buildings; vm.floors = floors; vm.workAreas = workAreas; vm.workItems = workItems; vm.Tasks = tasks; return vm; } private Guid GetTenantId() { return _userHelper.GetTenantId(); //var tenant = User.FindFirst("TenantId")?.Value; //return (tenant != null ? Convert.ToInt32(tenant) : 1); } [HttpPost] public async Task Create([FromBody] CreateProjectDto projectDto) { // 1. Validate input first (early exit) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } // 2. Prepare data without I/O Guid tenantId = _userHelper.GetTenantId(); // Assuming this is fast and from claims Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInUserId = loggedInEmployee.Id; var project = projectDto.ToProjectFromCreateProjectDto(tenantId); // 3. Store it to database try { _context.Projects.Add(project); await _context.SaveChangesAsync(); } catch (Exception ex) { // Log the detailed exception _logger.LogError("Failed to create project in database. Rolling back transaction. : {Error}", ex.Message); // Return a server error as the primary operation failed return StatusCode(500, ApiResponse.ErrorResponse("An error occurred while saving the project.", ex.Message, 500)); } // 4. Perform non-critical side-effects (caching, notifications) concurrently try { // These operations do not depend on each other, so they can run in parallel. Task cacheAddDetailsTask = _cache.AddProjectDetails(project); Task cacheClearListTask = _cache.ClearAllProjectIdsByPermissionId(PermissionsMaster.ManageProject); var notification = new { LoggedInUserId = loggedInUserId, Keyword = "Create_Project", Response = project.ToProjectDto() }; // Send notification only to the relevant group (e.g., users in the same tenant) Task notificationTask = _signalR.Clients.Group(tenantId.ToString()).SendAsync("NotificationEventHandler", notification); // Await all side-effect tasks to complete in parallel await Task.WhenAll(cacheAddDetailsTask, cacheClearListTask, notificationTask); } catch (Exception ex) { // The project was created successfully, but a side-effect failed. // Log this as a warning, as the primary operation succeeded. Don't return an error to the user. _logger.LogWarning("Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. : {Error}", project.Id, ex.Message); } // 5. Return a success response to the user as soon as the critical data is saved. return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Project created successfully.", 200)); } [HttpPut] [Route("update/{id}")] public async Task Update([FromRoute] Guid id, [FromBody] UpdateProjectDto updateProjectDto) { var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); if (!ModelState.IsValid) { var errors = ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } try { Guid TenantId = GetTenantId(); Project project = updateProjectDto.ToProjectFromUpdateProjectDto(TenantId, id); _context.Projects.Update(project); await _context.SaveChangesAsync(); // Cache functions bool isUpdated = await _cache.UpdateProjectDetailsOnly(project); if (!isUpdated) { await _cache.AddProjectDetails(project); } var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Update_Project", Response = project.ToProjectDto() }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(project.ToProjectDto(), "Success.", 200)); } catch (Exception ex) { return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); } } //[HttpPost("assign-employee")] //public async Task AssignEmployee(int? allocationid, int employeeId, int projectId) //{ // var employee = await _context.Employees.FindAsync(employeeId); // var project = _projectrepo.Get(c => c.Id == projectId); // if (employee == null || project == null) // { // return NotFound(); // } // // Logic to add the product to a new table (e.g., selected products) // if (allocationid == null) // { // // Add allocation // ProjectAllocation allocation = new ProjectAllocation() // { // EmployeeId = employeeId, // ProjectId = project.Id, // AllocationDate = DateTime.UtcNow, // //EmployeeRole = employee.Rol // TenantId = project.TenantId // }; // _unitOfWork.ProjectAllocation.CreateAsync(allocation); // } // else // { // //remove allocation // var allocation = await _context.ProjectAllocations.FindAsync(allocationid); // if (allocation != null) // { // allocation.ReAllocationDate = DateTime.UtcNow; // _unitOfWork.ProjectAllocation.UpdateAsync(allocation.Id, allocation); // } // else // { // return NotFound(); // } // } // return Ok(); //} [HttpGet] [Route("employees/get/{projectid?}/{includeInactive?}")] public async Task GetEmployeeByProjectID(Guid? projectid, bool includeInactive = false) { 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(); if (projectid != null) { // Fetch assigned project List result = new List(); if ((bool)includeInactive) { result = await (from rpm in _context.Employees.Include(c => c.JobRole) join fp in _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == projectid) on rpm.Id equals fp.EmployeeId select rpm).ToListAsync(); } else { result = await (from rpm in _context.Employees.Include(c => c.JobRole) join fp in _context.ProjectAllocations.Where(c => c.TenantId == TenantId && c.ProjectId == projectid && c.IsActive == true) on rpm.Id equals fp.EmployeeId select rpm).ToListAsync(); } List resultVM = new List(); foreach (Employee employee in result) { EmployeeVM vm = employee.ToEmployeeVMFromEmployee(); resultVM.Add(vm); } return Ok(ApiResponse.SuccessResponse(resultVM, "Success.", 200)); } else { return NotFound(ApiResponse.ErrorResponse("Invalid Input Parameter", 404)); } } [HttpGet] [Route("allocation/{projectId}")] public async Task GetProjectAllocation(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)); } Guid TenantId = GetTenantId(); var employees = await _context.ProjectAllocations .Where(c => c.TenantId == TenantId && c.ProjectId == projectId && c.Employee != null) .Include(e => e.Employee) .Select(e => new { ID = e.Id, EmployeeId = e.EmployeeId, ProjectId = e.ProjectId, AllocationDate = e.AllocationDate, ReAllocationDate = e.ReAllocationDate, FirstName = e.Employee != null ? e.Employee.FirstName : string.Empty, LastName = e.Employee != null ? e.Employee.LastName : string.Empty, MiddleName = e.Employee != null ? e.Employee.MiddleName : string.Empty, IsActive = e.IsActive, JobRoleId = (e.JobRoleId != null ? e.JobRoleId : e.Employee != null ? e.Employee.JobRoleId : null) }).ToListAsync(); return Ok(ApiResponse.SuccessResponse(employees, "Success.", 200)); } [HttpPost("allocation")] public async Task ManageAllocation(List projectAllocationDot) { if (projectAllocationDot != null) { Guid TenentID = GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); List? result = new List(); List employeeIds = new List(); List projectIds = new List(); foreach (var item in projectAllocationDot) { try { ProjectAllocation projectAllocation = item.ToProjectAllocationFromProjectAllocationDto(TenentID); ProjectAllocation? projectAllocationFromDb = await _context.ProjectAllocations.Where(c => c.EmployeeId == projectAllocation.EmployeeId && c.ProjectId == projectAllocation.ProjectId && c.ReAllocationDate == null && c.TenantId == TenentID).SingleOrDefaultAsync(); if (projectAllocationFromDb != null) { _context.ProjectAllocations.Attach(projectAllocationFromDb); if (item.Status) { projectAllocationFromDb.JobRoleId = projectAllocation.JobRoleId; ; projectAllocationFromDb.IsActive = true; _context.Entry(projectAllocationFromDb).Property(e => e.JobRoleId).IsModified = true; _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; } else { projectAllocationFromDb.ReAllocationDate = DateTime.Now; projectAllocationFromDb.IsActive = false; _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true; _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; employeeIds.Add(projectAllocation.EmployeeId); projectIds.Add(projectAllocation.ProjectId); } await _context.SaveChangesAsync(); var result1 = new { Id = projectAllocationFromDb.Id, EmployeeId = projectAllocation.EmployeeId, JobRoleId = projectAllocation.JobRoleId, IsActive = projectAllocation.IsActive, ProjectId = projectAllocation.ProjectId, AllocationDate = projectAllocation.AllocationDate, ReAllocationDate = projectAllocation.ReAllocationDate, TenantId = projectAllocation.TenantId }; result.Add(result1); } else { projectAllocation.AllocationDate = DateTime.Now; projectAllocation.IsActive = true; _context.ProjectAllocations.Add(projectAllocation); await _context.SaveChangesAsync(); employeeIds.Add(projectAllocation.EmployeeId); projectIds.Add(projectAllocation.ProjectId); } await _cache.ClearAllProjectIds(item.EmpID); } catch (Exception ex) { return Ok(ApiResponse.ErrorResponse(ex.Message, ex, 400)); } } var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeList = employeeIds }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(result, "Data saved successfully", 200)); } return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Work Item Details are not valid.", 400)); } [HttpGet("infra-details/{projectId}")] public async Task GetInfraDetails(Guid projectId) { _logger.LogInfo("GetInfraDetails called for ProjectId: {ProjectId}", projectId); // Step 1: Get logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Check project-specific permission var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId); if (!hasProjectPermission) { _logger.LogWarning("Project access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to this project", 403)); } // Step 3: Check 'ViewInfra' permission var hasViewInfraPermission = await _permission.HasPermission(PermissionsMaster.ViewProjectInfra, loggedInEmployee.Id); if (!hasViewInfraPermission) { _logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have access to view infra", 403)); } var result = await _cache.GetBuildingInfra(projectId); if (result == null) { // Step 4: Fetch buildings for the project var buildings = await _context.Buildings .Where(b => b.ProjectId == projectId) .ToListAsync(); var buildingIds = buildings.Select(b => b.Id).ToList(); // Step 5: Fetch floors associated with the buildings var floors = await _context.Floor .Where(f => buildingIds.Contains(f.BuildingId)) .ToListAsync(); var floorIds = floors.Select(f => f.Id).ToList(); // Step 6: Fetch work areas associated with the floors var workAreas = await _context.WorkAreas .Where(wa => floorIds.Contains(wa.FloorId)) .ToListAsync(); var workAreaIds = workAreas.Select(wa => wa.Id).ToList(); // Step 7: Fetch work items associated with the work area var workItems = await _context.WorkItems .Where(wi => workAreaIds.Contains(wi.WorkAreaId)) .ToListAsync(); // Step 8: Build the infra hierarchy (Building > Floors > Work Areas) List Buildings = new List(); foreach (var building in buildings) { double buildingPlannedWorks = 0; double buildingCompletedWorks = 0; var selectedFloors = floors.Where(f => f.BuildingId == building.Id).ToList(); List Floors = new List(); foreach (var floor in selectedFloors) { double floorPlannedWorks = 0; double floorCompletedWorks = 0; var selectedWorkAreas = workAreas.Where(wa => wa.FloorId == floor.Id).ToList(); List WorkAreas = new List(); foreach (var workArea in selectedWorkAreas) { double workAreaPlannedWorks = 0; double workAreaCompletedWorks = 0; var selectedWorkItems = workItems.Where(wi => wi.WorkAreaId == workArea.Id).ToList(); foreach (var workItem in selectedWorkItems) { workAreaPlannedWorks += workItem.PlannedWork; workAreaCompletedWorks += workItem.CompletedWork; } WorkAreaMongoDB workAreaMongo = new WorkAreaMongoDB { Id = workArea.Id.ToString(), AreaName = workArea.AreaName, PlannedWork = workAreaPlannedWorks, CompletedWork = workAreaCompletedWorks }; WorkAreas.Add(workAreaMongo); floorPlannedWorks += workAreaPlannedWorks; floorCompletedWorks += workAreaCompletedWorks; } FloorMongoDB floorMongoDB = new FloorMongoDB { Id = floor.Id.ToString(), FloorName = floor.FloorName, PlannedWork = floorPlannedWorks, CompletedWork = floorCompletedWorks, WorkAreas = WorkAreas }; Floors.Add(floorMongoDB); buildingPlannedWorks += floorPlannedWorks; buildingCompletedWorks += floorCompletedWorks; } var buildingMongo = new BuildingMongoDB { Id = building.Id.ToString(), BuildingName = building.Name, Description = building.Description, PlannedWork = buildingPlannedWorks, CompletedWork = buildingCompletedWorks, Floors = Floors }; Buildings.Add(buildingMongo); } result = Buildings; } _logger.LogInfo("Infra details fetched successfully for ProjectId: {ProjectId}, EmployeeId: {EmployeeId}, Buildings: {Count}", projectId, loggedInEmployee.Id, result.Count); return Ok(ApiResponse.SuccessResponse(result, "Infra details fetched successfully", 200)); } [HttpGet("tasks/{workAreaId}")] public async Task GetWorkItems(Guid workAreaId) { _logger.LogInfo("GetWorkItems called for WorkAreaId: {WorkAreaId}", workAreaId); // Step 1: Get the currently logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // Step 2: Check if the employee has ViewInfra permission var hasViewInfraPermission = await _permission.HasPermission(PermissionsMaster.ViewProjectInfra, loggedInEmployee.Id); if (!hasViewInfraPermission) { _logger.LogWarning("ViewInfra permission denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "You don't have permission to view infrastructure", 403)); } // Step 3: Check if the specified Work Area exists var isWorkAreaExist = await _context.WorkAreas.AnyAsync(wa => wa.Id == workAreaId); if (!isWorkAreaExist) { _logger.LogWarning("Work Area not found for WorkAreaId: {WorkAreaId}", workAreaId); return NotFound(ApiResponse.ErrorResponse("Work Area not found", "Work Area not found in database", 404)); } // Step 4: Fetch WorkItems with related Activity and Work Category data var workItemVMs = await _cache.GetWorkItemDetailsByWorkArea(workAreaId); if (workItemVMs == null) { var workItems = await _context.WorkItems .Include(wi => wi.ActivityMaster) .Include(wi => wi.WorkCategoryMaster) .Where(wi => wi.WorkAreaId == workAreaId) .ToListAsync(); workItemVMs = workItems.Select(wi => new WorkItemMongoDB { Id = wi.Id.ToString(), WorkAreaId = wi.WorkAreaId.ToString(), ParentTaskId = wi.ParentTaskId.ToString(), ActivityMaster = new ActivityMasterMongoDB { Id = wi.ActivityId.ToString(), ActivityName = wi.ActivityMaster != null ? wi.ActivityMaster.ActivityName : null, UnitOfMeasurement = wi.ActivityMaster != null ? wi.ActivityMaster.UnitOfMeasurement : null }, WorkCategoryMaster = new WorkCategoryMasterMongoDB { Id = wi.WorkCategoryId.ToString() ?? "", Name = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Name : "", Description = wi.WorkCategoryMaster != null ? wi.WorkCategoryMaster.Description : "" }, PlannedWork = wi.PlannedWork, CompletedWork = wi.CompletedWork, Description = wi.Description, TaskDate = wi.TaskDate, }).ToList(); await _cache.ManageWorkItemDetails(workItems); } _logger.LogInfo("{Count} work items fetched successfully for WorkAreaId: {WorkAreaId}", workItemVMs.Count, workAreaId); // Step 5: Return result return Ok(ApiResponse.SuccessResponse(workItemVMs, $"{workItemVMs.Count} records of tasks fetched successfully", 200)); } [HttpPost("task")] public async Task CreateProjectTask(List workItemDtos) { _logger.LogInfo("CreateProjectTask called with {Count} items", workItemDtos?.Count ?? 0); // Validate request if (workItemDtos == null || !workItemDtos.Any()) { _logger.LogWarning("No work items provided in the request."); return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Work Item details are not valid.", 400)); } Guid tenantId = GetTenantId(); var workItemsToCreate = new List(); var workItemsToUpdate = new List(); var responseList = new List(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); string message = ""; List workAreaIds = new List(); var workItemIds = workItemDtos.Where(wi => wi.Id != null && wi.Id != Guid.Empty).Select(wi => wi.Id).ToList(); var workItems = await _context.WorkItems.AsNoTracking().Where(wi => workItemIds.Contains(wi.Id)).ToListAsync(); foreach (var itemDto in workItemDtos) { var workItem = itemDto.ToWorkItemFromWorkItemDto(tenantId); var workArea = await _context.WorkAreas.Include(a => a.Floor).FirstOrDefaultAsync(a => a.Id == workItem.WorkAreaId) ?? new WorkArea(); Building building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == (workArea.Floor != null ? workArea.Floor.BuildingId : Guid.Empty)) ?? new Building(); if (itemDto.Id != null && itemDto.Id != Guid.Empty) { // Update existing workItemsToUpdate.Add(workItem); message = $"Task Updated in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}"; var existingWorkItem = workItems.FirstOrDefault(wi => wi.Id == workItem.Id); double plannedWork = 0; double completedWork = 0; if (existingWorkItem != null) { if (existingWorkItem.PlannedWork != workItem.PlannedWork && existingWorkItem.CompletedWork != workItem.CompletedWork) { plannedWork = workItem.PlannedWork - existingWorkItem.PlannedWork; completedWork = workItem.CompletedWork - existingWorkItem.CompletedWork; } else if (existingWorkItem.PlannedWork == workItem.PlannedWork && existingWorkItem.CompletedWork != workItem.CompletedWork) { plannedWork = 0; completedWork = workItem.CompletedWork - existingWorkItem.CompletedWork; } else if (existingWorkItem.PlannedWork != workItem.PlannedWork && existingWorkItem.CompletedWork == workItem.CompletedWork) { plannedWork = workItem.PlannedWork - existingWorkItem.PlannedWork; completedWork = 0; } await _cache.UpdatePlannedAndCompleteWorksInBuilding(workArea.Id, plannedWork, completedWork); } } else { // Create new workItem.Id = Guid.NewGuid(); workItemsToCreate.Add(workItem); message = $"Task Added in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}"; await _cache.UpdatePlannedAndCompleteWorksInBuilding(workArea.Id, workItem.PlannedWork, workItem.CompletedWork); } responseList.Add(new WorkItemVM { WorkItemId = workItem.Id, WorkItem = workItem }); workAreaIds.Add(workItem.WorkAreaId); } string responseMessage = ""; // Apply DB changes if (workItemsToCreate.Any()) { _logger.LogInfo("Adding {Count} new work items", workItemsToCreate.Count); await _context.WorkItems.AddRangeAsync(workItemsToCreate); responseMessage = "Task Added Successfully"; await _cache.ManageWorkItemDetails(workItemsToCreate); } if (workItemsToUpdate.Any()) { _logger.LogInfo("Updating {Count} existing work items", workItemsToUpdate.Count); _context.WorkItems.UpdateRange(workItemsToUpdate); responseMessage = "Task Updated Successfully"; await _cache.ManageWorkItemDetails(workItemsToUpdate); } await _context.SaveChangesAsync(); _logger.LogInfo("CreateProjectTask completed successfully. Created: {Created}, Updated: {Updated}", workItemsToCreate.Count, workItemsToUpdate.Count); var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = message }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(responseList, responseMessage, 200)); } [HttpDelete("task/{id}")] public async Task DeleteProjectTask(Guid id) { Guid tenantId = _userHelper.GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); List workAreaIds = new List(); WorkItem? task = await _context.WorkItems.AsNoTracking().Include(t => t.WorkArea).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId); if (task != null) { if (task.CompletedWork == 0) { var assignedTask = await _context.TaskAllocations.Where(t => t.WorkItemId == id).ToListAsync(); if (assignedTask.Count == 0) { _context.WorkItems.Remove(task); await _context.SaveChangesAsync(); _logger.LogInfo("Task with ID {WorkItemId} has been successfully deleted.", task.Id); var floorId = task.WorkArea?.FloorId; var floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == floorId); workAreaIds.Add(task.WorkAreaId); var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); } else { _logger.LogWarning("Task with ID {WorkItemId} is currently assigned and cannot be deleted.", task.Id); return BadRequest(ApiResponse.ErrorResponse("Task is currently assigned and cannot be deleted.", "Task is currently assigned and cannot be deleted.", 400)); } } else { double percentage = (task.CompletedWork / task.PlannedWork) * 100; percentage = Math.Round(percentage, 2); _logger.LogWarning("Task with ID {WorkItemId} is {CompletionPercentage}% complete and cannot be deleted", task.Id, percentage); return BadRequest(ApiResponse.ErrorResponse(System.String.Format("Task is {0}% complete and cannot be deleted", percentage), System.String.Format("Task is {0}% complete and cannot be deleted", percentage), 400)); } } else { _logger.LogError("Task with ID {WorkItemId} not found ID database", id); } return Ok(ApiResponse.SuccessResponse(new { }, "Task deleted successfully", 200)); } [HttpPost("manage-infra")] public async Task ManageProjectInfra(List infraDots) { Guid tenantId = GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var responseData = new InfraVM { }; string responseMessage = ""; string message = ""; List projectIds = new List(); if (infraDots != null) { foreach (var item in infraDots) { if (item.Building != null) { Building building = item.Building.ToBuildingFromBuildingDto(tenantId); building.TenantId = GetTenantId(); if (item.Building.Id == null) { //create _context.Buildings.Add(building); await _context.SaveChangesAsync(); responseData.building = building; responseMessage = "Buliding Added Successfully"; message = "Building Added"; await _cache.AddBuildngInfra(building.ProjectId, building); } else { //update _context.Buildings.Update(building); await _context.SaveChangesAsync(); responseData.building = building; responseMessage = "Buliding Updated Successfully"; message = "Building Updated"; await _cache.UpdateBuildngInfra(building.ProjectId, building); } projectIds.Add(building.ProjectId); } if (item.Floor != null) { Floor floor = item.Floor.ToFloorFromFloorDto(tenantId); floor.TenantId = GetTenantId(); bool isCreated = false; if (item.Floor.Id == null) { //create _context.Floor.Add(floor); await _context.SaveChangesAsync(); responseData.floor = floor; responseMessage = "Floor Added Successfully"; message = "Floor Added"; isCreated = true; } else { //update _context.Floor.Update(floor); await _context.SaveChangesAsync(); responseData.floor = floor; responseMessage = "Floor Updated Successfully"; message = "Floor Updated"; } Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId); var projectId = building?.ProjectId ?? Guid.Empty; projectIds.Add(projectId); message = $"{message} in Building: {building?.Name}"; if (isCreated) { await _cache.AddBuildngInfra(projectId, floor: floor); } else { await _cache.UpdateBuildngInfra(projectId, floor: floor); } } if (item.WorkArea != null) { WorkArea workArea = item.WorkArea.ToWorkAreaFromWorkAreaDto(tenantId); workArea.TenantId = GetTenantId(); bool isCreated = false; if (item.WorkArea.Id == null) { //create _context.WorkAreas.Add(workArea); await _context.SaveChangesAsync(); responseData.workArea = workArea; responseMessage = "Work Area Added Successfully"; message = "Work Area Added"; isCreated = true; } else { //update _context.WorkAreas.Update(workArea); await _context.SaveChangesAsync(); responseData.workArea = workArea; responseMessage = "Work Area Updated Successfully"; message = "Work Area Updated"; } Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId); var projectId = floor?.Building?.ProjectId ?? Guid.Empty; projectIds.Add(projectId); message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}"; if (isCreated) { await _cache.AddBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId); } else { await _cache.UpdateBuildngInfra(projectId, workArea: workArea, buildingId: floor?.BuildingId); } } } message = $"{message} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}"; var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(responseData, responseMessage, 200)); } return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400)); } [HttpGet("assigned-projects/{employeeId}")] public async Task GetProjectsByEmployee([FromRoute] Guid employeeId) { Guid tenantId = _userHelper.GetTenantId(); if (employeeId == Guid.Empty) { return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Employee id not valid.", 400)); } List projectList = await _context.ProjectAllocations .Where(c => c.TenantId == tenantId && c.EmployeeId == employeeId && c.IsActive) .Select(c => c.ProjectId).Distinct() .ToListAsync(); if (!projectList.Any()) { return NotFound(ApiResponse.SuccessResponse(new List(), "No projects found.", 200)); } List projectlist = await _context.Projects .Where(p => projectList.Contains(p.Id)) .ToListAsync(); List projects = new List(); foreach (var project in projectlist) { projects.Add(project.ToProjectListVMFromProject()); } return Ok(ApiResponse.SuccessResponse(projects, "Success.", 200)); } [HttpPost("assign-projects/{employeeId}")] public async Task AssigneProjectsToEmployee([FromBody] List projectAllocationDtos, [FromRoute] Guid employeeId) { if (projectAllocationDtos != null && employeeId != Guid.Empty) { Guid TenentID = GetTenantId(); var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); List? result = new List(); List projectIds = new List(); foreach (var projectAllocationDto in projectAllocationDtos) { try { ProjectAllocation projectAllocation = projectAllocationDto.ToProjectAllocationFromProjectsAllocationDto(TenentID, employeeId); ProjectAllocation? projectAllocationFromDb = await _context.ProjectAllocations.Where(c => c.EmployeeId == employeeId && c.ProjectId == projectAllocationDto.ProjectId && c.ReAllocationDate == null && c.TenantId == TenentID).SingleOrDefaultAsync(); if (projectAllocationFromDb != null) { _context.ProjectAllocations.Attach(projectAllocationFromDb); if (projectAllocationDto.Status) { projectAllocationFromDb.JobRoleId = projectAllocation.JobRoleId; ; projectAllocationFromDb.IsActive = true; _context.Entry(projectAllocationFromDb).Property(e => e.JobRoleId).IsModified = true; _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; } else { projectAllocationFromDb.ReAllocationDate = DateTime.UtcNow; projectAllocationFromDb.IsActive = false; _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true; _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true; projectIds.Add(projectAllocation.ProjectId); } await _context.SaveChangesAsync(); var result1 = new { Id = projectAllocationFromDb.Id, EmployeeId = projectAllocation.EmployeeId, JobRoleId = projectAllocation.JobRoleId, IsActive = projectAllocation.IsActive, ProjectId = projectAllocation.ProjectId, AllocationDate = projectAllocation.AllocationDate, ReAllocationDate = projectAllocation.ReAllocationDate, TenantId = projectAllocation.TenantId }; result.Add(result1); } else { projectAllocation.AllocationDate = DateTime.Now; projectAllocation.IsActive = true; _context.ProjectAllocations.Add(projectAllocation); await _context.SaveChangesAsync(); projectIds.Add(projectAllocation.ProjectId); } } catch (Exception ex) { return Ok(ApiResponse.ErrorResponse(ex.Message, ex, 400)); } } await _cache.ClearAllProjectIds(employeeId); var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeId = employeeId }; await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); return Ok(ApiResponse.SuccessResponse(result, "Data saved successfully", 200)); } else { return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "All Field is required", 400)); } } } }