diff --git a/Marco.Pms.Model/ViewModels/Projects/ProjectHisteryVM.cs b/Marco.Pms.Model/ViewModels/Projects/ProjectHisteryVM.cs new file mode 100644 index 0000000..4df0480 --- /dev/null +++ b/Marco.Pms.Model/ViewModels/Projects/ProjectHisteryVM.cs @@ -0,0 +1,11 @@ +namespace Marco.Pms.Model.ViewModels.Projects +{ + public class ProjectHisteryVM + { + public string? ProjectName { get; set; } + public string? ProjectShortName { get; set; } + public DateTime AssignedDate { get; set; } + public DateTime? RemovedDate { get; set; } + public string? Designation { get; set; } + } +} \ No newline at end of file diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 2c03d69..436dff9 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -276,6 +276,23 @@ namespace MarcoBMS.Services.Controllers return StatusCode(response.StatusCode, response); } + [HttpGet("allocation-histery/{employeeId}")] + public async Task GetProjectByEmployeeBasic([FromRoute] Guid employeeId) + { + // --- Step 1: Input Validation --- + if (!ModelState.IsValid) + { + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + _logger.LogWarning("Get project list by employee Id called with invalid model state \n Errors: {Errors}", string.Join(", ", errors)); + return BadRequest(ApiResponse.ErrorResponse("Invalid request data provided.", errors, 400)); + } + + // --- Step 2: Prepare data without I/O --- + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _projectServices.GetProjectByEmployeeBasicAsync(employeeId, tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); + } + [HttpPost("assign-projects/{employeeId}")] public async Task AssigneProjectsToEmployee([FromBody] List projectAllocationDtos, [FromRoute] Guid employeeId) { @@ -338,6 +355,25 @@ namespace MarcoBMS.Services.Controllers var response = await _projectServices.GetWorkItemsAsync(workAreaId, tenantId, loggedInEmployee); return StatusCode(response.StatusCode, response); } + [HttpGet("tasks-employee/{employeeId}")] + public async Task GetTasksByEmployee(Guid employeeId, [FromQuery] DateTime? fromDate, DateTime? toDate) + { + // --- Step 1: Input Validation --- + if (!ModelState.IsValid) + { + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + _logger.LogWarning("Get Work Items by employeeId called with invalid model state \n Errors: {Errors}", string.Join(", ", errors)); + return BadRequest(ApiResponse.ErrorResponse("Invalid request data provided.", errors, 400)); + } + + if (!toDate.HasValue) toDate = DateTime.UtcNow; + if (!fromDate.HasValue) fromDate = toDate.Value.AddDays(-7); + + // --- Step 2: Prepare data without I/O --- + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _projectServices.GetTasksByEmployeeAsync(employeeId, fromDate.Value, toDate.Value, tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); + } #endregion diff --git a/Marco.Pms.Services/Helpers/EmployeeHelper.cs b/Marco.Pms.Services/Helpers/EmployeeHelper.cs index 09dcbe2..a23460b 100644 --- a/Marco.Pms.Services/Helpers/EmployeeHelper.cs +++ b/Marco.Pms.Services/Helpers/EmployeeHelper.cs @@ -20,7 +20,7 @@ namespace MarcoBMS.Services.Helpers public async Task GetEmployeeByID(Guid EmployeeID) { - return await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == EmployeeID && e.IsActive == true) ?? new Employee { }; + return await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == EmployeeID) ?? new Employee { }; } public async Task GetEmployeeByApplicationUserID(string ApplicationUserID) diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index 894ba81..6cb7e68 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -895,6 +895,73 @@ namespace Marco.Pms.Services.Service return ApiResponse>.SuccessResponse(resultVm, "Assignments managed successfully.", 200); } + public async Task> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee) + { + // Log the start of the method execution with key input parameters + _logger.LogInfo("Fetching projects for EmployeeId: {EmployeeId}, TenantId: {TenantId} by User: {UserId}", + employeeId, tenantId, loggedInEmployee.Id); + + try + { + // Retrieve project allocations linked to the specified employee and tenant + var projectAllocation = await _context.ProjectAllocations + .AsNoTracking() // Optimization: no tracking since entities are not updated + .Include(pa => pa.Project) // Include related Project data + .Include(pa => pa.Employee).ThenInclude(e => e!.JobRole) // Include related Employee and their JobRole + .Where(pa => pa.EmployeeId == employeeId + && pa.TenantId == tenantId + && pa.Project != null + && pa.Employee != null) + .Select(pa => new + { + ProjectName = pa.Project!.Name, + ProjectShortName = pa.Project.ShortName, + AssignedDate = pa.AllocationDate, + RemovedDate = pa.ReAllocationDate, + Designation = pa.Employee!.JobRole!.Name, + DesignationId = pa.JobRoleId + }) + .ToListAsync(); + + var designationIds = projectAllocation.Select(pa => pa.DesignationId).ToList(); + + var designations = await _context.JobRoles.Where(jr => designationIds.Contains(jr.Id)).ToListAsync(); + + var response = projectAllocation.Select(pa => + { + var designation = designations.FirstOrDefault(jr => jr.Id == pa.DesignationId); + return new ProjectHisteryVM + { + ProjectName = pa.ProjectName, + ProjectShortName = pa.ProjectShortName, + AssignedDate = pa.AssignedDate, + RemovedDate = pa.RemovedDate, + Designation = designation?.Name + }; + }).ToList(); + + // Log successful retrieval including count of records + _logger.LogInfo("Successfully fetched {Count} projects for EmployeeId: {EmployeeId}", + projectAllocation.Count, employeeId); + + return ApiResponse.SuccessResponse( + response, + $"{response.Count} project assignments fetched for employee.", + 200); + } + catch (Exception ex) + { + // Log the exception with stack trace for debugging + _logger.LogError(ex, "Error occurred while fetching projects for EmployeeId: {EmployeeId}, TenantId: {TenantId}", + employeeId, tenantId); + + return ApiResponse.ErrorResponse( + "An error occurred while fetching project assignments.", + 500); + } + } + + #endregion #region =================================================================== Project InfraStructure Get APIs =================================================================== @@ -1025,6 +1092,83 @@ namespace Marco.Pms.Services.Service } } + /// + /// Retrieves tasks assigned to a specific employee within a date range for a tenant. + /// + /// The ID of the employee to filter tasks. + /// The start date to filter task assignments. + /// The end date to filter task assignments. + /// The tenant ID to filter tasks. + /// The employee requesting the data (for authorization/logging). + /// An ApiResponse containing the task details. + public async Task> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee) + { + _logger.LogInfo("Fetching tasks for EmployeeId: {EmployeeId} from {FromDate} to {ToDate} for TenantId: {TenantId}", + employeeId, fromDate, toDate, tenantId); + + try + { + // Query TaskMembers with related necessary fields in one projection to minimize DB calls and data size + var taskData = await _context.TaskMembers + .Where(tm => tm.EmployeeId == employeeId && + tm.TenantId == tenantId && + tm.TaskAllocation != null && + tm.TaskAllocation.AssignmentDate.Date >= fromDate.Date && + tm.TaskAllocation.AssignmentDate.Date <= toDate.Date) + .Select(tm => new + { + AssignmentDate = tm.TaskAllocation!.AssignmentDate, + PlannedTask = tm.TaskAllocation.PlannedTask, + CompletedTask = tm.TaskAllocation.CompletedTask, + ProjectId = tm.TaskAllocation.WorkItem!.WorkArea!.Floor!.Building!.ProjectId, + BuildingName = tm.TaskAllocation.WorkItem.WorkArea.Floor.Building!.Name, + FloorName = tm.TaskAllocation.WorkItem.WorkArea.Floor.FloorName, + AreaName = tm.TaskAllocation.WorkItem.WorkArea.AreaName, + ActivityName = tm.TaskAllocation.WorkItem.ActivityMaster!.ActivityName, + ActivityUnit = tm.TaskAllocation.WorkItem.ActivityMaster.UnitOfMeasurement + }) + .OrderByDescending(t => t.AssignmentDate) + .ToListAsync(); + + _logger.LogInfo("Retrieved {TaskCount} tasks for EmployeeId: {EmployeeId}", taskData.Count, employeeId); + + // Extract distinct project IDs to fetch project details efficiently + var distinctProjectIds = taskData.Select(t => t.ProjectId).Distinct().ToList(); + + var projects = await _context.Projects + .Where(p => distinctProjectIds.Contains(p.Id)) + .Select(p => new { p.Id, p.Name }) + .ToListAsync(); + + // Prepare the response + var response = taskData.Select(t => + { + var project = projects.FirstOrDefault(p => p.Id == t.ProjectId); + + return new + { + ProjectName = project?.Name ?? "Unknown Project", + t.AssignmentDate, + t.PlannedTask, + t.CompletedTask, + Location = $"{t.BuildingName} > {t.FloorName} > {t.AreaName}", + ActivityName = t.ActivityName, + ActivityUnit = t.ActivityUnit + }; + }).ToList(); + + _logger.LogInfo("Successfully prepared task response for EmployeeId: {EmployeeId}", employeeId); + + return ApiResponse.SuccessResponse(response, "Task fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while fetching tasks for EmployeeId: {EmployeeId}", employeeId); + return ApiResponse.ErrorResponse("An error occurred while fetching the tasks.", 500); + } + } + + #endregion #region =================================================================== Project Infrastructre Manage APIs =================================================================== diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index b5acccc..a1f78f8 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -20,8 +20,11 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task>> ManageAllocationAsync(List projectAllocationDots, Guid tenantId, Employee loggedInEmployee); Task> GetProjectsByEmployeeAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); Task>> AssigneProjectsToEmployeeAsync(List projectAllocationDtos, Guid employeeId, Guid tenantId, Employee loggedInEmployee); + Task> GetProjectByEmployeeBasicAsync(Guid employeeId, Guid tenantId, Employee loggedInEmployee); + Task> GetInfraDetailsAsync(Guid projectId, Guid tenantId, Employee loggedInEmployee); Task> GetWorkItemsAsync(Guid workAreaId, Guid tenantId, Employee loggedInEmployee); + Task> GetTasksByEmployeeAsync(Guid employeeId, DateTime fromDate, DateTime toDate, Guid tenantId, Employee loggedInEmployee); Task ManageProjectInfraAsync(List infraDtos, Guid tenantId, Employee loggedInEmployee); Task>> CreateProjectTaskAsync(List workItemDtos, Guid tenantId, Employee loggedInEmployee); Task DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee); diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index 579b059..4bb9519 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -49,6 +49,6 @@ "MongoDB": { "SerilogDatabaseUrl": "mongodb://localhost:27017/DotNetLogs", "ConnectionString": "mongodb://localhost:27017/MarcoBMS_Caches?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500", - "ModificationConnectionString": "mongodb://localhost:27017/ModificationLog?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500" + "ModificationConnectionString": "mongodb://localhost:27017/ModificationLog?socketTimeoutMS=500&serverSelectionTimeoutMS=500&connectTimeoutMS=500&replicaSet=rs01&directConnection=true" } }