From 73aa1d618150e1a48512cd975205e6b5abfddc6b Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Tue, 15 Jul 2025 12:44:38 +0530 Subject: [PATCH] adde functionality to delete workItems from cache --- .../Controllers/AttendanceController.cs | 30 +-- .../Controllers/AuthController.cs | 34 +-- .../Controllers/DashboardController.cs | 10 +- .../Controllers/DirectoryController.cs | 4 +- .../Controllers/EmployeeController.cs | 4 +- .../Controllers/ForumController.cs | 30 +-- .../Controllers/MasterController.cs | 48 ++-- .../Controllers/ProjectController.cs | 129 ++++------ .../Controllers/ReportController.cs | 16 +- .../Helpers/CacheUpdateHelper.cs | 8 +- Marco.Pms.Services/Helpers/DirectoryHelper.cs | 18 +- Marco.Pms.Services/Helpers/EmployeeHelper.cs | 6 +- Marco.Pms.Services/Helpers/MasterHelper.cs | 10 +- Marco.Pms.Services/Helpers/ReportHelper.cs | 10 +- Marco.Pms.Services/Helpers/RolesHelper.cs | 4 +- ...ectMappingProfile.cs => MappingProfile.cs} | 12 +- Marco.Pms.Services/Program.cs | 1 + Marco.Pms.Services/Service/ILoggingService.cs | 2 +- Marco.Pms.Services/Service/LoggingServices.cs | 6 +- Marco.Pms.Services/Service/ProjectServices.cs | 227 +++++++++++++++++- .../Service/RefreshTokenService.cs | 14 +- Marco.Pms.Services/Service/S3UploadService.cs | 14 +- .../ServiceInterfaces/IProjectServices.cs | 2 + .../ServiceInterfaces/ISignalRService.cs | 7 + Marco.Pms.Services/Service/SignalRService.cs | 29 +++ 25 files changed, 444 insertions(+), 231 deletions(-) rename Marco.Pms.Services/MappingProfiles/{ProjectMappingProfile.cs => MappingProfile.cs} (75%) create mode 100644 Marco.Pms.Services/Service/ServiceInterfaces/ISignalRService.cs create mode 100644 Marco.Pms.Services/Service/SignalRService.cs diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 4c2f2c1..1a5e4e7 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -90,18 +90,18 @@ namespace MarcoBMS.Services.Controllers if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false) { - _logger.LogError("User sent Invalid from Date while featching attendance logs"); + _logger.LogWarning("User sent Invalid from Date while featching attendance logs"); return BadRequest(ApiResponse.ErrorResponse("Invalid Date", "Invalid Date", 400)); } if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false) { - _logger.LogError("User sent Invalid to Date while featching attendance logs"); + _logger.LogWarning("User sent Invalid to Date while featching attendance logs"); return BadRequest(ApiResponse.ErrorResponse("Invalid Date", "Invalid Date", 400)); } if (employeeId == Guid.Empty) { - _logger.LogError("The employee Id sent by user is empty"); + _logger.LogWarning("The employee Id sent by user is empty"); return BadRequest(ApiResponse.ErrorResponse("Employee ID is required and must not be Empty.", "Employee ID is required and must not be empty.", 400)); } List attendances = await _context.Attendes.Where(c => c.EmployeeID == employeeId && c.TenantId == TenantId && c.AttendanceDate.Date >= fromDate && c.AttendanceDate.Date <= toDate).ToListAsync(); @@ -161,18 +161,18 @@ namespace MarcoBMS.Services.Controllers if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false) { - _logger.LogError("User sent Invalid fromDate while featching attendance logs"); + _logger.LogWarning("User sent Invalid fromDate while featching attendance logs"); return BadRequest(ApiResponse.ErrorResponse("Invalid Date", "Invalid Date", 400)); } if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false) { - _logger.LogError("User sent Invalid toDate while featching attendance logs"); + _logger.LogWarning("User sent Invalid toDate while featching attendance logs"); return BadRequest(ApiResponse.ErrorResponse("Invalid Date", "Invalid Date", 400)); } if (projectId == Guid.Empty) { - _logger.LogError("The project Id sent by user is less than or equal to zero"); + _logger.LogWarning("The project Id sent by user is less than or equal to zero"); return BadRequest(ApiResponse.ErrorResponse("Project ID is required and must be greater than zero.", "Project ID is required and must be greater than zero.", 400)); } @@ -276,13 +276,13 @@ namespace MarcoBMS.Services.Controllers if (date != null && DateTime.TryParse(date, out forDate) == false) { - _logger.LogError("User sent Invalid Date while featching attendance logs"); + _logger.LogWarning("User sent Invalid Date while featching attendance logs"); return BadRequest(ApiResponse.ErrorResponse("Invalid Date", "Invalid Date", 400)); } if (projectId == Guid.Empty) { - _logger.LogError("The project Id sent by user is less than or equal to zero"); + _logger.LogWarning("The project Id sent by user is less than or equal to zero"); return BadRequest(ApiResponse.ErrorResponse("Project ID is required and must be greater than zero.", "Project ID is required and must be greater than zero.", 400)); } @@ -425,7 +425,7 @@ namespace MarcoBMS.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("User sent Invalid Date while marking attendance"); + _logger.LogWarning("User sent Invalid Date while marking attendance \n {Error}", string.Join(",", errors)); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } @@ -439,14 +439,14 @@ namespace MarcoBMS.Services.Controllers if (recordAttendanceDot.MarkTime == null) { - _logger.LogError("User sent Invalid Mark Time while marking attendance"); + _logger.LogWarning("User sent Invalid Mark Time while marking attendance"); return BadRequest(ApiResponse.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400)); } DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime); if (recordAttendanceDot.Comment == null) { - _logger.LogError("User sent Invalid comment while marking attendance"); + _logger.LogWarning("User sent Invalid comment while marking attendance"); return BadRequest(ApiResponse.ErrorResponse("Invalid Comment", "Invalid Comment", 400)); } @@ -480,7 +480,7 @@ namespace MarcoBMS.Services.Controllers } else { - _logger.LogError("Employee {EmployeeId} sent regularization request but it check-out time is earlier than check-out"); + _logger.LogWarning("Employee {EmployeeId} sent regularization request but it check-out time is earlier than check-out"); return BadRequest(ApiResponse.ErrorResponse("Check-out time must be later than check-in time", "Check-out time must be later than check-in time", 400)); } // do nothing @@ -585,7 +585,7 @@ namespace MarcoBMS.Services.Controllers catch (Exception ex) { await transaction.RollbackAsync(); // Rollback on failure - _logger.LogError("{Error} while marking attendance", ex.Message); + _logger.LogError(ex, "An Error occured while marking attendance"); var response = new { message = ex.Message, @@ -604,7 +604,7 @@ namespace MarcoBMS.Services.Controllers if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); - _logger.LogError("Invalid attendance model received."); + _logger.LogWarning("Invalid attendance model received. \n {Error}", string.Join(",", errors)); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } @@ -780,7 +780,7 @@ namespace MarcoBMS.Services.Controllers catch (Exception ex) { await transaction.RollbackAsync(); - _logger.LogError("Error while recording attendance : {Error}", ex.Message); + _logger.LogError(ex, "Error while recording attendance"); return BadRequest(ApiResponse.ErrorResponse("Something went wrong", ex.Message, 500)); } } diff --git a/Marco.Pms.Services/Controllers/AuthController.cs b/Marco.Pms.Services/Controllers/AuthController.cs index 1b45eb7..429a38b 100644 --- a/Marco.Pms.Services/Controllers/AuthController.cs +++ b/Marco.Pms.Services/Controllers/AuthController.cs @@ -1,8 +1,4 @@ -using System.Net; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; -using Marco.Pms.DataAccess.Data; +using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Authentication; using Marco.Pms.Model.Dtos.Authentication; using Marco.Pms.Model.Dtos.Util; @@ -15,6 +11,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using System.Net; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; namespace MarcoBMS.Services.Controllers { @@ -110,7 +110,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("Unexpected error during login : {Error}", ex.Message); + _logger.LogError(ex, "Unexpected error during login"); return StatusCode(500, ApiResponse.ErrorResponse("Unexpected error", ex.Message, 500)); } } @@ -270,7 +270,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("Unexpected error occurred while verifying MPIN : {Error}", ex.Message); + _logger.LogError(ex, "Unexpected error occurred while verifying MPIN"); return StatusCode(500, ApiResponse.ErrorResponse("Unexpected error", ex.Message, 500)); } } @@ -307,7 +307,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("Unexpected error during logout : {Error}", ex.Message); + _logger.LogError(ex, "Unexpected error during logout"); return StatusCode(500, ApiResponse.ErrorResponse("Unexpected error occurred", ex.Message, 500)); } } @@ -351,7 +351,7 @@ namespace MarcoBMS.Services.Controllers if (string.IsNullOrWhiteSpace(user.UserName)) { - _logger.LogError("Username missing for user ID: {UserId}", user.Id); + _logger.LogWarning("Username missing for user ID: {UserId}", user.Id); return NotFound(ApiResponse.ErrorResponse("Username not found.", "Username not found.", 404)); } @@ -370,7 +370,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("An unexpected error occurred during token refresh. : {Error}", ex.Message); + _logger.LogError(ex, "An unexpected error occurred during token refresh."); return StatusCode(500, ApiResponse.ErrorResponse("Unexpected error occurred.", ex.Message, 500)); } } @@ -406,7 +406,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("Error while sending password reset email to: {Error}", ex.Message); + _logger.LogError(ex, "Error while sending password reset email to"); return StatusCode(500, ApiResponse.ErrorResponse("Error sending password reset email.", ex.Message, 500)); } } @@ -480,7 +480,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("Error while sending reset password success email to user: {Error}", ex.Message); + _logger.LogError(ex, "Error while sending reset password success email to user"); // Continue, do not fail because of email issue } @@ -547,7 +547,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("An unexpected error occurred while sending OTP to {Email} : {Error}", generateOTP.Email ?? "", ex.Message); + _logger.LogError(ex, "An unexpected error occurred while sending OTP to {Email}", generateOTP.Email ?? ""); return StatusCode(500, ApiResponse.ErrorResponse("An unexpected error occurred.", ex.Message, 500)); } } @@ -638,7 +638,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception ex) { - _logger.LogError("An unexpected error occurred during OTP login for email {Email} : {Error}", verifyOTP.Email ?? string.Empty, ex.Message); + _logger.LogError(ex, "An unexpected error occurred during OTP login for email {Email}", verifyOTP.Email ?? string.Empty); return StatusCode(500, ApiResponse.ErrorResponse("Unexpected error", ex.Message, 500)); } } @@ -719,7 +719,7 @@ namespace MarcoBMS.Services.Controllers if (!result.Succeeded) { var errors = result.Errors.Select(e => e.Description).ToList(); - _logger.LogError("Password reset failed for user {Email}. Errors: {Errors}", changePassword.Email, string.Join("; ", errors)); + _logger.LogWarning("Password reset failed for user {Email}. Errors: {Errors}", changePassword.Email, string.Join("; ", errors)); return BadRequest(ApiResponse.ErrorResponse("Failed to change password", errors, 400)); } @@ -732,7 +732,7 @@ namespace MarcoBMS.Services.Controllers } catch (Exception exp) { - _logger.LogError("An unexpected error occurred while changing password : {Error}", exp.Message); + _logger.LogError(exp, "An unexpected error occurred while changing password"); return StatusCode(500, ApiResponse.ErrorResponse("An unexpected error occurred.", exp.Message, 500)); } } @@ -752,7 +752,7 @@ namespace MarcoBMS.Services.Controllers // Validate employee and MPIN input if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN) || generateMPINDto.MPIN.Length != 6 || !generateMPINDto.MPIN.All(char.IsDigit)) { - _logger.LogError("Employee {EmployeeId} provided invalid information to generate MPIN", loggedInEmployee.Id); + _logger.LogWarning("Employee {EmployeeId} provided invalid information to generate MPIN", loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Provided invalid information", "Provided invalid information", 400)); } diff --git a/Marco.Pms.Services/Controllers/DashboardController.cs b/Marco.Pms.Services/Controllers/DashboardController.cs index bdb965c..934725a 100644 --- a/Marco.Pms.Services/Controllers/DashboardController.cs +++ b/Marco.Pms.Services/Controllers/DashboardController.cs @@ -221,7 +221,7 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Number of pending regularization and pending check-out are fetched successfully for employee {EmployeeId}", LoggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(response, "Pending regularization and pending check-out are fetched successfully", 200)); } - _logger.LogError("No attendance entry was found for employee {EmployeeId}", LoggedInEmployee.Id); + _logger.LogWarning("No attendance entry was found for employee {EmployeeId}", LoggedInEmployee.Id); return NotFound(ApiResponse.ErrorResponse("No attendance entry was found for this employee", "No attendance entry was found for this employee", 404)); } @@ -235,14 +235,14 @@ namespace Marco.Pms.Services.Controllers List? projectProgressionVMs = new List(); if (date != null && DateTime.TryParse(date, out currentDate) == false) { - _logger.LogError($"user send invalid date"); + _logger.LogWarning($"user send invalid date"); return BadRequest(ApiResponse.ErrorResponse("Invalid date.", "Invalid date.", 400)); } Project? project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == projectId); if (project == null) { - _logger.LogError("Employee {EmployeeId} was attempted to get project attendance for date {Date}, but project not found in database", LoggedInEmployee.Id, currentDate); + _logger.LogWarning("Employee {EmployeeId} was attempted to get project attendance for date {Date}, but project not found in database", LoggedInEmployee.Id, currentDate); return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); } List? projectAllocation = await _context.ProjectAllocations.Where(p => p.ProjectId == projectId && p.IsActive && p.TenantId == tenantId).ToListAsync(); @@ -288,14 +288,14 @@ namespace Marco.Pms.Services.Controllers DateTime currentDate = DateTime.UtcNow; if (date != null && DateTime.TryParse(date, out currentDate) == false) { - _logger.LogError($"user send invalid date"); + _logger.LogWarning($"user send invalid date"); return BadRequest(ApiResponse.ErrorResponse("Invalid date.", "Invalid date.", 400)); } Project? project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == projectId); if (project == null) { - _logger.LogError("Employee {EmployeeId} was attempted to get activities performed for date {Date}, but project not found in database", LoggedInEmployee.Id, currentDate); + _logger.LogWarning("Employee {EmployeeId} was attempted to get activities performed for date {Date}, but project not found in database", LoggedInEmployee.Id, currentDate); return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found", 404)); } diff --git a/Marco.Pms.Services/Controllers/DirectoryController.cs b/Marco.Pms.Services/Controllers/DirectoryController.cs index 4a0e41e..9eb06e0 100644 --- a/Marco.Pms.Services/Controllers/DirectoryController.cs +++ b/Marco.Pms.Services/Controllers/DirectoryController.cs @@ -77,7 +77,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("User sent Invalid Date while marking attendance"); + _logger.LogWarning("User sent Invalid Date while marking attendance"); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } var response = await _directoryHelper.CreateContact(createContact); @@ -256,7 +256,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("User sent Invalid Date while marking attendance"); + _logger.LogWarning("User sent Invalid Date while marking attendance"); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } var response = await _directoryHelper.CreateBucket(bucketDto); diff --git a/Marco.Pms.Services/Controllers/EmployeeController.cs b/Marco.Pms.Services/Controllers/EmployeeController.cs index 2f0ca5e..c9e19fa 100644 --- a/Marco.Pms.Services/Controllers/EmployeeController.cs +++ b/Marco.Pms.Services/Controllers/EmployeeController.cs @@ -382,7 +382,7 @@ namespace MarcoBMS.Services.Controllers Employee? existingEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == model.Id.Value); if (existingEmployee == null) { - _logger.LogError("User tries to update employee {EmployeeId} but not found in database", model.Id); + _logger.LogWarning("User tries to update employee {EmployeeId} but not found in database", model.Id); return NotFound(ApiResponse.ErrorResponse("Employee not found", "Employee not found", 404)); } byte[]? imageBytes = null; @@ -495,7 +495,7 @@ namespace MarcoBMS.Services.Controllers } else { - _logger.LogError("Employee with ID {EmploueeId} not found in database", id); + _logger.LogWarning("Employee with ID {EmploueeId} not found in database", id); } return Ok(ApiResponse.SuccessResponse(new { }, "Employee Suspended successfully", 200)); } diff --git a/Marco.Pms.Services/Controllers/ForumController.cs b/Marco.Pms.Services/Controllers/ForumController.cs index 769c08a..fb6d0e7 100644 --- a/Marco.Pms.Services/Controllers/ForumController.cs +++ b/Marco.Pms.Services/Controllers/ForumController.cs @@ -44,7 +44,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("{error}", errors); + _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); @@ -66,7 +66,7 @@ namespace Marco.Pms.Services.Controllers var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { - _logger.LogError("Base64 data is missing"); + _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } @@ -160,7 +160,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("{error}", errors); + _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } Guid tenantId = _userHelper.GetTenantId(); @@ -197,7 +197,7 @@ namespace Marco.Pms.Services.Controllers var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { - _logger.LogError("Base64 data is missing"); + _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } @@ -336,7 +336,7 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Ticket {TicketId} updated", updateTicketDto.Id); return Ok(ApiResponse.SuccessResponse(ticketVM, "Ticket Updated Successfully", 200)); } - _logger.LogError("Ticket {TicketId} not Found in database", updateTicketDto.Id); + _logger.LogWarning("Ticket {TicketId} not Found in database", updateTicketDto.Id); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } @@ -349,7 +349,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("{error}", errors); + _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } @@ -364,7 +364,7 @@ namespace Marco.Pms.Services.Controllers if (ticket == null) { - _logger.LogError("Ticket {TicketId} not Found in database", addCommentDto.TicketId); + _logger.LogWarning("Ticket {TicketId} not Found in database", addCommentDto.TicketId); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } @@ -379,7 +379,7 @@ namespace Marco.Pms.Services.Controllers var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { - _logger.LogError("Base64 data is missing"); + _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } @@ -437,7 +437,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("{error}", errors); + _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } @@ -451,7 +451,7 @@ namespace Marco.Pms.Services.Controllers if (ticket == null) { - _logger.LogError("Ticket {TicketId} not Found in database", updateCommentDto.TicketId); + _logger.LogWarning("Ticket {TicketId} not Found in database", updateCommentDto.TicketId); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } @@ -474,7 +474,7 @@ namespace Marco.Pms.Services.Controllers var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { - _logger.LogError("Base64 data is missing"); + _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } @@ -552,7 +552,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("{error}", errors); + _logger.LogWarning("{error}", errors); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } @@ -568,7 +568,7 @@ namespace Marco.Pms.Services.Controllers if (tickets == null || tickets.Count > 0) { - _logger.LogError("Tickets not Found in database"); + _logger.LogWarning("Tickets not Found in database"); return NotFound(ApiResponse.ErrorResponse("Ticket not Found", "Ticket not Found", 404)); } @@ -578,12 +578,12 @@ namespace Marco.Pms.Services.Controllers { if (string.IsNullOrEmpty(forumAttachmentDto.Base64Data)) { - _logger.LogError("Base64 data is missing"); + _logger.LogWarning("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } if (forumAttachmentDto.TicketId == null) { - _logger.LogError("ticket ID is missing"); + _logger.LogWarning("ticket ID is missing"); return BadRequest(ApiResponse.ErrorResponse("ticket ID is missing", "ticket ID is missing", 400)); } var ticket = tickets.FirstOrDefault(t => t.Id == forumAttachmentDto.TicketId); diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index ebd8998..9000cdf 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -168,7 +168,7 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("activity updated successfully from tenant {tenantId}", tenantId); return Ok(ApiResponse.SuccessResponse(activityVM, "activity updated successfully", 200)); } - _logger.LogError("Activity {ActivityId} not found", id); + _logger.LogWarning("Activity {ActivityId} not found", id); return NotFound(ApiResponse.ErrorResponse("Activity not found", "Activity not found", 404)); } @@ -230,7 +230,7 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Ticket Status master {TicketStatusId} added successfully from tenant {tenantId}", statusMaster.Id, tenantId); return Ok(ApiResponse.SuccessResponse(statusVM, "Ticket Status master added successfully", 200)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("Sent Empty payload", "Sent Empty payload", 400)); } @@ -251,10 +251,10 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Ticket Status master {TicketStatusId} updated successfully from tenant {tenantId}", statusMaster.Id, tenantId); return Ok(ApiResponse.SuccessResponse(statusVM, "Ticket Status master updated successfully", 200)); } - _logger.LogError("Ticket Status master {TicketStatusId} not found in database", statusMasterDto.Id != null ? statusMasterDto.Id.Value : Guid.Empty); + _logger.LogWarning("Ticket Status master {TicketStatusId} not found in database", statusMasterDto.Id != null ? statusMasterDto.Id.Value : Guid.Empty); return NotFound(ApiResponse.ErrorResponse("Ticket Status master not found", "Ticket Status master not found", 404)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("Sent Empty payload", "Sent Empty payload", 400)); } @@ -281,7 +281,7 @@ namespace Marco.Pms.Services.Controllers } else { - _logger.LogError("Ticket Status {TickeStatusId} not found in database", id); + _logger.LogWarning("Ticket Status {TickeStatusId} not found in database", id); return NotFound(ApiResponse.ErrorResponse("Ticket Status not found", "Ticket Status not found", 404)); } } @@ -318,7 +318,7 @@ namespace Marco.Pms.Services.Controllers return Ok(ApiResponse.SuccessResponse(typeVM, "Ticket type master added successfully", 200)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -339,10 +339,10 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Ticket Type master {TicketTypeId} updated successfully from tenant {tenantId}", typeMaster.Id, tenantId); return Ok(ApiResponse.SuccessResponse(typeVM, "Ticket type master updated successfully", 200)); } - _logger.LogError("Ticket type master {TicketTypeId} not found in database", typeMasterDto.Id != null ? typeMasterDto.Id.Value : Guid.Empty); + _logger.LogWarning("Ticket type master {TicketTypeId} not found in database", typeMasterDto.Id != null ? typeMasterDto.Id.Value : Guid.Empty); return NotFound(ApiResponse.ErrorResponse("Ticket type master not found", "Ticket type master not found", 404)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -369,7 +369,7 @@ namespace Marco.Pms.Services.Controllers } else { - _logger.LogError("Ticket Type {TickeTypeId} not found in database", id); + _logger.LogWarning("Ticket Type {TickeTypeId} not found in database", id); return NotFound(ApiResponse.ErrorResponse("Ticket Type not found", "Ticket Type not found", 404)); } } @@ -407,7 +407,7 @@ namespace Marco.Pms.Services.Controllers return Ok(ApiResponse.SuccessResponse(typeVM, "Ticket Priority master added successfully", 200)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } [HttpPost("ticket-priorities/edit/{id}")] @@ -427,10 +427,10 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Ticket Priority master {TicketPriorityId} updated successfully from tenant {tenantId}", typeMaster.Id, tenantId); return Ok(ApiResponse.SuccessResponse(typeVM, "Ticket Priority master updated successfully", 200)); } - _logger.LogError("Ticket Priority master {TicketPriorityId} not found in database", priorityMasterDto.Id != null ? priorityMasterDto.Id.Value : Guid.Empty); + _logger.LogWarning("Ticket Priority master {TicketPriorityId} not found in database", priorityMasterDto.Id != null ? priorityMasterDto.Id.Value : Guid.Empty); return NotFound(ApiResponse.ErrorResponse("Ticket Priority master not found", "Ticket Priority master not found", 404)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -457,7 +457,7 @@ namespace Marco.Pms.Services.Controllers } else { - _logger.LogError("Ticket Priority {TickePriorityId} not found in database", id); + _logger.LogWarning("Ticket Priority {TickePriorityId} not found in database", id); return NotFound(ApiResponse.ErrorResponse("Ticket Priority not found", "Ticket Priority not found", 404)); } } @@ -494,7 +494,7 @@ namespace Marco.Pms.Services.Controllers return Ok(ApiResponse.SuccessResponse(typeVM, "Ticket tag master added successfully", 200)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -515,10 +515,10 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Ticket Tag master {TicketTypeId} updated successfully from tenant {tenantId}", tagMaster.Id, tenantId); return Ok(ApiResponse.SuccessResponse(typeVM, "Ticket tag master updated successfully", 200)); } - _logger.LogError("Ticket tag master {TicketTypeId} not found in database", tagMasterDto.Id != null ? tagMasterDto.Id.Value : Guid.Empty); + _logger.LogWarning("Ticket tag master {TicketTypeId} not found in database", tagMasterDto.Id != null ? tagMasterDto.Id.Value : Guid.Empty); return NotFound(ApiResponse.ErrorResponse("Ticket tag master not found", "Ticket tag master not found", 404)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -545,7 +545,7 @@ namespace Marco.Pms.Services.Controllers } else { - _logger.LogError("Ticket Tag {TickeTagId} not found in database", id); + _logger.LogWarning("Ticket Tag {TickeTagId} not found in database", id); return NotFound(ApiResponse.ErrorResponse("Ticket tag not found", "Ticket tag not found", 404)); } } @@ -609,7 +609,7 @@ namespace Marco.Pms.Services.Controllers return Ok(ApiResponse.SuccessResponse(workCategoryMasterVM, "Work category master added successfully", 200)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -624,7 +624,7 @@ namespace Marco.Pms.Services.Controllers { if (workCategory.IsSystem) { - _logger.LogError("User tries to update system-defined work category"); + _logger.LogWarning("User tries to update system-defined work category"); return BadRequest(ApiResponse.ErrorResponse("Cannot update system-defined work", "Cannot update system-defined work", 400)); } workCategory = workCategoryMasterDto.ToWorkCategoryMasterFromWorkCategoryMasterDto(tenantId); @@ -635,10 +635,10 @@ namespace Marco.Pms.Services.Controllers _logger.LogInfo("Work category master {WorkCategoryId} updated successfully from tenant {tenantId}", workCategory.Id, tenantId); return Ok(ApiResponse.SuccessResponse(workCategoryMasterVM, "Work category master updated successfully", 200)); } - _logger.LogError("Work category master {WorkCategoryId} not found in database", workCategoryMasterDto.Id ?? Guid.Empty); + _logger.LogWarning("Work category master {WorkCategoryId} not found in database", workCategoryMasterDto.Id ?? Guid.Empty); return NotFound(ApiResponse.ErrorResponse("Work category master not found", "Work category master not found", 404)); } - _logger.LogError("User sent empyt payload"); + _logger.LogWarning("User sent empyt payload"); return BadRequest(ApiResponse.ErrorResponse("User sent Empty payload", "User sent Empty payload", 400)); } @@ -666,7 +666,7 @@ namespace Marco.Pms.Services.Controllers } else { - _logger.LogError("Work category {WorkCategoryId} not found in database", id); + _logger.LogWarning("Work category {WorkCategoryId} not found in database", id); return NotFound(ApiResponse.ErrorResponse("Work category not found", "Work category not found", 404)); } } @@ -689,7 +689,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("User sent Invalid Date while marking attendance"); + _logger.LogWarning("User sent Invalid Date while marking attendance"); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } var response = await _masterHelper.CreateWorkStatus(createWorkStatusDto); @@ -803,7 +803,7 @@ namespace Marco.Pms.Services.Controllers .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage) .ToList(); - _logger.LogError("User sent Invalid Date while marking attendance"); + _logger.LogWarning("User sent Invalid Date while marking attendance"); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } var response = await _masterHelper.CreateContactTag(contactTagDto); diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index e7d257f..236e0cb 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -6,19 +6,18 @@ 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 Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.SignalR; +using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; using MongoDB.Driver; +using Project = Marco.Pms.Model.Projects.Project; namespace MarcoBMS.Services.Controllers { @@ -31,14 +30,20 @@ namespace MarcoBMS.Services.Controllers private readonly ApplicationDbContext _context; private readonly UserHelper _userHelper; private readonly ILoggingService _logger; - private readonly IHubContext _signalR; + private readonly ISignalRService _signalR; private readonly PermissionServices _permission; private readonly CacheUpdateHelper _cache; private readonly Guid tenantId; - public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, - IHubContext signalR, CacheUpdateHelper cache, PermissionServices permission, IProjectServices projectServices) + public ProjectController( + ApplicationDbContext context, + UserHelper userHelper, + ILoggingService logger, + ISignalRService signalR, + CacheUpdateHelper cache, + PermissionServices permission, + IProjectServices projectServices) { _context = context; _userHelper = userHelper; @@ -174,7 +179,7 @@ namespace MarcoBMS.Services.Controllers if (response.Success) { var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Create_Project", Response = response.Data }; - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + await _signalR.SendNotificationAsync(notification); } return StatusCode(response.StatusCode, response); } @@ -204,7 +209,7 @@ namespace MarcoBMS.Services.Controllers if (response.Success) { var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Update_Project", Response = response.Data }; - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + await _signalR.SendNotificationAsync(notification); } return StatusCode(response.StatusCode, response); } @@ -213,90 +218,38 @@ namespace MarcoBMS.Services.Controllers #region =================================================================== Project Allocation APIs =================================================================== - [HttpGet] - [Route("employees/get/{projectid?}/{includeInactive?}")] - public async Task GetEmployeeByProjectID(Guid? projectid, bool includeInactive = false) + [HttpGet("employees/get/{projectid?}/{includeInactive?}")] + public async Task GetEmployeeByProjectId(Guid? projectId, bool includeInactive = false) { + // --- Step 1: Input Validation --- 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 errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + _logger.LogWarning("Get employee list by ProjectId called with invalid model state \n Errors: {Errors}", string.Join(", ", errors)); + return BadRequest(ApiResponse.ErrorResponse("Invalid request data provided.", errors, 400)); } - 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) - 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)); - } - - + // --- Step 2: Prepare data without I/O --- + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _projectServices.GetEmployeeByProjectIdAsync(projectId, includeInactive, tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); } - [HttpGet] - [Route("allocation/{projectId}")] + [HttpGet("allocation/{projectId}")] public async Task GetProjectAllocation(Guid? projectId) { + // --- Step 1: Input Validation --- 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 errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + _logger.LogWarning("Get employee list by ProjectId called with invalid model state \n Errors: {Errors}", string.Join(", ", errors)); + return BadRequest(ApiResponse.ErrorResponse("Invalid request data provided.", errors, 400)); } - 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)); + // --- Step 2: Prepare data without I/O --- + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _projectServices.GetProjectAllocationAsync(projectId, tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); } [HttpPost("allocation")] @@ -375,7 +328,7 @@ namespace MarcoBMS.Services.Controllers } var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeList = employeeIds }; - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + await _signalR.SendNotificationAsync(notification); return Ok(ApiResponse.SuccessResponse(result, "Data saved successfully", 200)); } @@ -494,7 +447,7 @@ namespace MarcoBMS.Services.Controllers await _cache.ClearAllProjectIds(employeeId); var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeId = employeeId }; - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + await _signalR.SendNotificationAsync(notification); return Ok(ApiResponse.SuccessResponse(result, "Data saved successfully", 200)); } @@ -799,7 +752,7 @@ namespace MarcoBMS.Services.Controllers var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "WorkItem", WorkAreaIds = workAreaIds, Message = message }; - await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + await _signalR.SendNotificationAsync(notification); return Ok(ApiResponse.SuccessResponse(responseList, responseMessage, 200)); } @@ -826,9 +779,15 @@ namespace MarcoBMS.Services.Controllers workAreaIds.Add(task.WorkAreaId); + var projectId = floor?.Building?.ProjectId; 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); + await _signalR.SendNotificationAsync(notification); + await _cache.DeleteWorkItemByIdAsync(task.Id); + if (projectId != null) + { + await _cache.DeleteProjectByIdAsync(projectId.Value); + } } else { @@ -847,7 +806,7 @@ namespace MarcoBMS.Services.Controllers } else { - _logger.LogError("Task with ID {WorkItemId} not found ID database", id); + _logger.LogWarning("Task with ID {WorkItemId} not found ID database", id); } return Ok(ApiResponse.SuccessResponse(new { }, "Task deleted successfully", 200)); } @@ -973,7 +932,7 @@ namespace MarcoBMS.Services.Controllers 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); + await _signalR.SendNotificationAsync(notification); return Ok(ApiResponse.SuccessResponse(responseData, responseMessage, 200)); } return BadRequest(ApiResponse.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400)); diff --git a/Marco.Pms.Services/Controllers/ReportController.cs b/Marco.Pms.Services/Controllers/ReportController.cs index 717a273..87382d7 100644 --- a/Marco.Pms.Services/Controllers/ReportController.cs +++ b/Marco.Pms.Services/Controllers/ReportController.cs @@ -106,7 +106,7 @@ namespace Marco.Pms.Services.Controllers } catch (Exception ex) { - _logger.LogError("Database Error: Failed to check existence of MailListId '{MailListId}' for TenantId: {TenantId}. : {Error}", mailDetailsDto.MailListId, tenantId, ex.Message); + _logger.LogError(ex, "Database Error: Failed to check existence of MailListId '{MailListId}' for TenantId: {TenantId}.", mailDetailsDto.MailListId, tenantId); return StatusCode(500, ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while validating mail template.", 500)); } @@ -143,13 +143,13 @@ namespace Marco.Pms.Services.Controllers } catch (DbUpdateException dbEx) { - _logger.LogError("Database Error: Failed to save new mail details for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}. : {Error}", newMailDetails.ProjectId, newMailDetails.Recipient, tenantId, dbEx.Message); + _logger.LogError(dbEx, "Database Error: Failed to save new mail details for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}.", newMailDetails.ProjectId, newMailDetails.Recipient, tenantId); // Check for specific constraint violations if applicable (e.g., duplicate recipient for a project) return StatusCode(500, ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while saving the mail details.", 500)); } catch (Exception ex) { - _logger.LogError("Unexpected Error: An unhandled exception occurred while adding mail details for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}. : {Error}", newMailDetails.ProjectId, newMailDetails.Recipient, tenantId, ex.Message); + _logger.LogError(ex, "Unexpected Error: An unhandled exception occurred while adding mail details for ProjectId: {ProjectId}, Recipient: '{Recipient}', TenantId: {TenantId}.", newMailDetails.ProjectId, newMailDetails.Recipient, tenantId); return StatusCode(500, ApiResponse.ErrorResponse("Internal Server Error", "An unexpected error occurred.", 500)); } } @@ -234,7 +234,7 @@ namespace Marco.Pms.Services.Controllers } catch (Exception ex) { - _logger.LogError("Database Error: Failed to check for existing mail template with title '{Title}' for TenantId: {TenantId}.: {Error}", mailTemplateDto.Title, tenantId, ex.Message); + _logger.LogError(ex, "Database Error: Failed to check for existing mail template with title '{Title}' for TenantId: {TenantId}.", mailTemplateDto.Title, tenantId); return StatusCode(500, ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while checking for existing templates.", 500)); } @@ -270,12 +270,12 @@ namespace Marco.Pms.Services.Controllers } catch (DbUpdateException dbEx) { - _logger.LogError("Database Error: Failed to save new mail template '{Title}' for TenantId: {TenantId}. : {Error}", mailTemplateDto.Title, tenantId, dbEx.Message); + _logger.LogError(dbEx, "Database Error: Failed to save new mail template '{Title}' for TenantId: {TenantId}. : {Error}", mailTemplateDto.Title, tenantId); return StatusCode(500, ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while saving the mail template.", 500)); } catch (Exception ex) { - _logger.LogError("Unexpected Error: An unhandled exception occurred while adding mail template '{Title}' for TenantId: {TenantId}. : {Error}", mailTemplateDto.Title, tenantId, ex.Message); + _logger.LogError(ex, "Unexpected Error: An unhandled exception occurred while adding mail template '{Title}' for TenantId: {TenantId}.", mailTemplateDto.Title, tenantId); return StatusCode(500, ApiResponse.ErrorResponse("Internal Server Error", "An unexpected error occurred.", 500)); } } @@ -350,7 +350,7 @@ namespace Marco.Pms.Services.Controllers { // 3. OPTIMIZATION: Make the process resilient. // If one task fails unexpectedly, log it and continue with others. - _logger.LogError("Failed to send report for project {ProjectId} : {Error}", mailGroup.ProjectId, ex.Message); + _logger.LogError(ex, "Failed to send report for project {ProjectId}", mailGroup.ProjectId); Interlocked.Increment(ref failureCount); } } @@ -527,7 +527,7 @@ namespace Marco.Pms.Services.Controllers catch (Exception ex) { // It's good practice to log any unexpected errors within a concurrent task. - _logger.LogError("Failed to process project report for ProjectId {ProjectId} : {Error}", group.ProjectId, ex.Message); + _logger.LogError(ex, "Failed to process project report for ProjectId {ProjectId}", group.ProjectId); } } }); diff --git a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs index 5bae90f..aca439b 100644 --- a/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs +++ b/Marco.Pms.Services/Helpers/CacheUpdateHelper.cs @@ -118,8 +118,8 @@ namespace Marco.Pms.Services.Helpers projectDetails.ProjectStatus = new StatusMasterMongoDB { - Id = status?.Id.ToString(), - Status = status?.Status + Id = status!.Id.ToString(), + Status = status.Status }; // Use fast in-memory lookups instead of .Where() in loops. @@ -797,7 +797,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Error occured while fetching project report mail bodys: {Error}", ex.Message); + _logger.LogError(ex, "Error occured while fetching project report mail bodys"); return null; } } @@ -809,7 +809,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Error occured while adding project report mail bodys: {Error}", ex.Message); + _logger.LogError(ex, "Error occured while adding project report mail bodys"); } } } diff --git a/Marco.Pms.Services/Helpers/DirectoryHelper.cs b/Marco.Pms.Services/Helpers/DirectoryHelper.cs index 37f58cf..f8e1b07 100644 --- a/Marco.Pms.Services/Helpers/DirectoryHelper.cs +++ b/Marco.Pms.Services/Helpers/DirectoryHelper.cs @@ -52,7 +52,7 @@ namespace Marco.Pms.Services.Helpers } else { - _logger.LogError("Employee {EmployeeId} attemped to access a contacts, but do not have permission", LoggedInEmployee.Id); + _logger.LogWarning("Employee {EmployeeId} attemped to access a contacts, but do not have permission", LoggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); } @@ -202,7 +202,7 @@ namespace Marco.Pms.Services.Helpers } else { - _logger.LogError("Employee {EmployeeId} attemped to access a contacts with in bucket {BucketId}, but do not have permission", LoggedInEmployee.Id, id); + _logger.LogWarning("Employee {EmployeeId} attemped to access a contacts with in bucket {BucketId}, but do not have permission", LoggedInEmployee.Id, id); return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); } @@ -490,7 +490,7 @@ namespace Marco.Pms.Services.Helpers } else { - _logger.LogError("Employee {EmployeeId} attemped to update a contact, but do not have permission", LoggedInEmployee.Id); + _logger.LogWarning("Employee {EmployeeId} attemped to update a contact, but do not have permission", LoggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); } @@ -1169,7 +1169,7 @@ namespace Marco.Pms.Services.Helpers } else { - _logger.LogError("Employee {EmployeeId} attemped to access a buckets list, but do not have permission", LoggedInEmployee.Id); + _logger.LogWarning("Employee {EmployeeId} attemped to access a buckets list, but do not have permission", LoggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); } @@ -1204,7 +1204,7 @@ namespace Marco.Pms.Services.Helpers var demo = !permissionIds.Contains(PermissionsMaster.DirectoryUser); if (!permissionIds.Contains(PermissionsMaster.DirectoryAdmin) && !permissionIds.Contains(PermissionsMaster.DirectoryAdmin) && !permissionIds.Contains(PermissionsMaster.DirectoryUser)) { - _logger.LogError("Employee {EmployeeId} attemped to create a bucket, but do not have permission", LoggedInEmployee.Id); + _logger.LogWarning("Employee {EmployeeId} attemped to create a bucket, but do not have permission", LoggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have permission", "You don't have permission", 401); } @@ -1276,7 +1276,7 @@ namespace Marco.Pms.Services.Helpers } if (accessableBucket == null) { - _logger.LogError("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); + _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 401); } @@ -1342,7 +1342,7 @@ namespace Marco.Pms.Services.Helpers } if (accessableBucket == null) { - _logger.LogError("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); + _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 401); } var employeeIds = await _context.Employees.Where(e => e.TenantId == tenantId && e.IsActive).Select(e => e.Id).ToListAsync(); @@ -1396,7 +1396,7 @@ namespace Marco.Pms.Services.Helpers } if (removededEmployee > 0) { - _logger.LogError("Employee {EmployeeId} removed {conut} number of employees from bucket {BucketId}", LoggedInEmployee.Id, removededEmployee, bucketId); + _logger.LogWarning("Employee {EmployeeId} removed {conut} number of employees from bucket {BucketId}", LoggedInEmployee.Id, removededEmployee, bucketId); } return ApiResponse.SuccessResponse(bucketVM, "Details updated successfully", 200); } @@ -1443,7 +1443,7 @@ namespace Marco.Pms.Services.Helpers } if (accessableBucket == null) { - _logger.LogError("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); + _logger.LogWarning("Employee {EmployeeId} attempted to access bucket {BucketId} without the necessary permissions.", LoggedInEmployee.Id, bucket.Id); return ApiResponse.ErrorResponse("You don't have permission to access this bucket", "You don't have permission to access this bucket", 401); } diff --git a/Marco.Pms.Services/Helpers/EmployeeHelper.cs b/Marco.Pms.Services/Helpers/EmployeeHelper.cs index 03184e5..17e5746 100644 --- a/Marco.Pms.Services/Helpers/EmployeeHelper.cs +++ b/Marco.Pms.Services/Helpers/EmployeeHelper.cs @@ -33,7 +33,7 @@ namespace MarcoBMS.Services.Helpers } catch (Exception ex) { - _logger.LogError("{Error}", ex.Message); + _logger.LogError(ex, "Error occured while fetching employee by application user ID {ApplicationUserId}", ApplicationUserID); return new Employee(); } } @@ -66,7 +66,7 @@ namespace MarcoBMS.Services.Helpers } catch (Exception ex) { - _logger.LogError("{Error}", ex.Message); + _logger.LogError(ex, "Error occoured while filtering employees by string {SearchString} or project {ProjectId}", searchString, ProjectId ?? Guid.Empty); return new List(); } } @@ -102,7 +102,7 @@ namespace MarcoBMS.Services.Helpers } catch (Exception ex) { - _logger.LogError("{Error}", ex.Message); + _logger.LogError(ex, "Error occured while featching list of employee by project ID {ProjectId}", ProjectId ?? Guid.Empty); return new List(); } } diff --git a/Marco.Pms.Services/Helpers/MasterHelper.cs b/Marco.Pms.Services/Helpers/MasterHelper.cs index f994639..83bc007 100644 --- a/Marco.Pms.Services/Helpers/MasterHelper.cs +++ b/Marco.Pms.Services/Helpers/MasterHelper.cs @@ -218,7 +218,7 @@ namespace Marco.Pms.Services.Helpers _logger.LogInfo("Contact tag master {ConatctTagId} updated successfully by employee {EmployeeId}", contactTagVm.Id, LoggedInEmployee.Id); return ApiResponse.SuccessResponse(contactTagVm, "Contact Tag master updated successfully", 200); } - _logger.LogError("Contact Tag master {ContactTagId} not found in database", id); + _logger.LogWarning("Contact Tag master {ContactTagId} not found in database", id); return ApiResponse.ErrorResponse("Contact Tag master not found", "Contact tag master not found", 404); } _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id); @@ -294,7 +294,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Error occurred while fetching work status list : {Error}", ex.Message); + _logger.LogWarning("Error occurred while fetching work status list : {Error}", ex.Message); return ApiResponse.ErrorResponse("An error occurred", "Unable to fetch work status list", 500); } } @@ -343,7 +343,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Error occurred while creating work status : {Error}", ex.Message); + _logger.LogWarning("Error occurred while creating work status : {Error}", ex.Message); return ApiResponse.ErrorResponse("An error occurred", "Unable to create work status", 500); } } @@ -403,7 +403,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Error occurred while updating work status ID: {Id} : {Error}", id, ex.Message); + _logger.LogError(ex, "Error occurred while updating work status ID: {Id}", id); return ApiResponse.ErrorResponse("An error occurred", "Unable to update the work status at this time", 500); } } @@ -458,7 +458,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Error occurred while deleting WorkStatus Id: {Id} : {Error}", id, ex.Message); + _logger.LogError(ex, "Error occurred while deleting WorkStatus Id: {Id}", id); return ApiResponse.ErrorResponse("An error occurred", "Unable to delete work status", 500); } } diff --git a/Marco.Pms.Services/Helpers/ReportHelper.cs b/Marco.Pms.Services/Helpers/ReportHelper.cs index 4ec0978..4ec9453 100644 --- a/Marco.Pms.Services/Helpers/ReportHelper.cs +++ b/Marco.Pms.Services/Helpers/ReportHelper.cs @@ -289,13 +289,13 @@ namespace Marco.Pms.Services.Helpers // --- Input Validation --- if (projectId == Guid.Empty) { - _logger.LogError("Validation Error: Provided empty project ID while fetching project report."); + _logger.LogWarning("Validation Error: Provided empty project ID while fetching project report."); return ApiResponse.ErrorResponse("Provided empty Project ID.", "Provided empty Project ID.", 400); } if (recipientEmails == null || !recipientEmails.Any()) { - _logger.LogError("Validation Error: No recipient emails provided for project ID {ProjectId}.", projectId); + _logger.LogWarning("Validation Error: No recipient emails provided for project ID {ProjectId}.", projectId); return ApiResponse.ErrorResponse("No recipient emails provided.", "No recipient emails provided.", 400); } @@ -316,7 +316,7 @@ namespace Marco.Pms.Services.Helpers } catch (Exception ex) { - _logger.LogError("Email Sending Error: Failed to send project statistics email for project ID {ProjectId}. : {Error}", projectId, ex.Message); + _logger.LogError(ex, "Email Sending Error: Failed to send project statistics email for project ID {ProjectId}.", projectId); return ApiResponse.ErrorResponse("Failed to send email.", "An error occurred while sending the email.", 500); } @@ -350,14 +350,14 @@ namespace Marco.Pms.Services.Helpers } catch (DbUpdateException dbEx) { - _logger.LogError("Database Error: Failed to save mail logs for project ID {ProjectId}. : {Error}", projectId, dbEx.Message); + _logger.LogError(dbEx, "Database Error: Failed to save mail logs for project ID {ProjectId}.", projectId); // Depending on your requirements, you might still return success here as the email was sent. // Or return an error indicating the logging failed. return ApiResponse.ErrorResponse("Email sent, but failed to log activity.", "Email sent, but an error occurred while logging.", 500); } catch (Exception ex) { - _logger.LogError("Unexpected Error: An unhandled exception occurred while processing project statistics for project ID {ProjectId}. : {Error}", projectId, ex.Message); + _logger.LogError(ex, "Unexpected Error: An unhandled exception occurred while processing project statistics for project ID {ProjectId}.", projectId); return ApiResponse.ErrorResponse("An unexpected error occurred.", "An unexpected error occurred.", 500); } } diff --git a/Marco.Pms.Services/Helpers/RolesHelper.cs b/Marco.Pms.Services/Helpers/RolesHelper.cs index cd73c0f..ef9f824 100644 --- a/Marco.Pms.Services/Helpers/RolesHelper.cs +++ b/Marco.Pms.Services/Helpers/RolesHelper.cs @@ -84,7 +84,7 @@ namespace MarcoBMS.Services.Helpers } catch (Exception ex) { - _logger.LogError("An error occurred while fetching permissions for EmployeeId {EmployeeId} : {Error}", EmployeeId, ex.Message); + _logger.LogError(ex, "An error occurred while fetching permissions for EmployeeId {EmployeeId}", EmployeeId); return new List(); } } @@ -144,7 +144,7 @@ namespace MarcoBMS.Services.Helpers } catch (Exception ex) { - _logger.LogError("An error occurred while fetching permissions for RoleId {RoleId}: {Error}", roleId, ex.Message); + _logger.LogError(ex, "An error occurred while fetching permissions for RoleId {RoleId}", roleId); // Return an empty list as a safe default to prevent downstream failures. return new List(); } diff --git a/Marco.Pms.Services/MappingProfiles/ProjectMappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs similarity index 75% rename from Marco.Pms.Services/MappingProfiles/ProjectMappingProfile.cs rename to Marco.Pms.Services/MappingProfiles/MappingProfile.cs index b811056..7d627bc 100644 --- a/Marco.Pms.Services/MappingProfiles/ProjectMappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -1,16 +1,19 @@ using AutoMapper; using Marco.Pms.Model.Dtos.Project; +using Marco.Pms.Model.Employees; using Marco.Pms.Model.Master; using Marco.Pms.Model.MongoDBModels; using Marco.Pms.Model.Projects; +using Marco.Pms.Model.ViewModels.Employee; using Marco.Pms.Model.ViewModels.Projects; namespace Marco.Pms.Services.MappingProfiles { - public class ProjectMappingProfile : Profile + public class MappingProfile : Profile { - public ProjectMappingProfile() + public MappingProfile() { + #region ======================================================= Projects ======================================================= // Your mappings CreateMap(); CreateMap(); @@ -40,6 +43,11 @@ namespace Marco.Pms.Services.MappingProfiles CreateMap(); CreateMap(); CreateMap(); + #endregion + + #region ======================================================= Projects ======================================================= + CreateMap(); + #endregion } } } diff --git a/Marco.Pms.Services/Program.cs b/Marco.Pms.Services/Program.cs index 6553745..26d8eba 100644 --- a/Marco.Pms.Services/Program.cs +++ b/Marco.Pms.Services/Program.cs @@ -158,6 +158,7 @@ builder.Services.AddTransient(); #region Customs Services builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); #endregion diff --git a/Marco.Pms.Services/Service/ILoggingService.cs b/Marco.Pms.Services/Service/ILoggingService.cs index b835d0c..6d795cd 100644 --- a/Marco.Pms.Services/Service/ILoggingService.cs +++ b/Marco.Pms.Services/Service/ILoggingService.cs @@ -5,7 +5,7 @@ void LogInfo(string? message, params object[]? args); void LogDebug(string? message, params object[]? args); void LogWarning(string? message, params object[]? args); - void LogError(string? message, params object[]? args); + void LogError(Exception? ex, string? message, params object[]? args); } } diff --git a/Marco.Pms.Services/Service/LoggingServices.cs b/Marco.Pms.Services/Service/LoggingServices.cs index 5a016de..751f22c 100644 --- a/Marco.Pms.Services/Service/LoggingServices.cs +++ b/Marco.Pms.Services/Service/LoggingServices.cs @@ -11,16 +11,16 @@ namespace MarcoBMS.Services.Service _logger = logger; } - public void LogError(string? message, params object[]? args) + public void LogError(Exception? ex, string? message, params object[]? args) { using (LogContext.PushProperty("LogLevel", "Error")) if (args != null) { - _logger.LogError(message, args); + _logger.LogError(ex, message, args); } else { - _logger.LogError(message); + _logger.LogError(ex, message); } } diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index 3280558..dcaf20e 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -1,4 +1,5 @@ using AutoMapper; +using AutoMapper.QueryableExtensions; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Activities; using Marco.Pms.Model.Dtos.Project; @@ -7,12 +8,15 @@ using Marco.Pms.Model.Entitlements; 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.Service.ServiceInterfaces; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; +using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; +using Project = Marco.Pms.Model.Projects.Project; namespace Marco.Pms.Services.Service { @@ -75,7 +79,7 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { // --- Step 5: Graceful Error Handling --- - _logger.LogError("An unexpected error occurred in GetAllProjectsBasic for tenant {TenantId}. \n {Error}", tenantId, ex.Message); + _logger.LogError(ex, "An unexpected error occurred in GetAllProjectsBasic for tenant {TenantId}.", tenantId); return ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); } } @@ -134,7 +138,7 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { // --- Step 5: Graceful Error Handling --- - _logger.LogError("An unexpected error occurred in GetAllProjects for tenant {TenantId}. \n {Error}", tenantId, ex.Message); + _logger.LogError(ex, "An unexpected error occurred in GetAllProjects for tenant {TenantId}.", tenantId); return ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); } } @@ -178,7 +182,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError("An unexpected error occurred while getting project {ProjectId} : \n {Error}", id, ex.Message); + _logger.LogError(ex, "An unexpected error occurred while getting project {ProjectId}", id); return ApiResponse.ErrorResponse("An internal server error occurred.", null, 500); } } @@ -244,7 +248,7 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { // --- Step 5: Graceful Error Handling --- - _logger.LogError("An unexpected error occurred in Get Project Details for project {ProjectId} for tenant {TenantId}. \n {Error}", id, tenantId, ex.Message); + _logger.LogError(ex, "An unexpected error occurred in Get Project Details for project {ProjectId} for tenant {TenantId}. ", id, tenantId); return ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); } } @@ -360,7 +364,7 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { // Log the detailed exception - _logger.LogError("Failed to create project in database. Rolling back transaction. \n {Error}", ex.Message); + _logger.LogError(ex, "Failed to create project in database. Rolling back transaction."); // Return a server error as the primary operation failed return ApiResponse.ErrorResponse("An error occurred while saving the project.", ex.Message, 500); } @@ -379,7 +383,7 @@ namespace Marco.Pms.Services.Service { // 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. \n {Error}", project.Id, ex.Message); + _logger.LogError(ex, "Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. ", project.Id); } // 4. Return a success response to the user as soon as the critical data is saved. @@ -435,7 +439,7 @@ namespace Marco.Pms.Services.Service { // --- Step 3: Handle Concurrency Conflicts --- // This happens if another user modified the project after we fetched it. - _logger.LogWarning("Concurrency conflict while updating project {ProjectId} \n {Error}", id, ex.Message); + _logger.LogError(ex, "Concurrency conflict while updating project {ProjectId} ", id); return ApiResponse.ErrorResponse("Conflict occurred.", "This project has been modified by someone else. Please refresh and try again.", 409); } @@ -458,13 +462,216 @@ namespace Marco.Pms.Services.Service catch (Exception ex) { // --- Step 6: Graceful Error Handling for Unexpected Errors --- - _logger.LogError("An unexpected error occurred while updating project {ProjectId} \n {Error}", id, ex.Message); + _logger.LogError(ex, "An unexpected error occurred while updating project {ProjectId} ", id); return ApiResponse.ErrorResponse("An internal server error occurred.", null, 500); } } #endregion + #region =================================================================== Project Allocation APIs =================================================================== + + public async Task> GetEmployeeByProjectID(Guid? projectid, bool includeInactive, Guid tenantId, Employee loggedInEmployee) + { + if (projectid == null) + { + return ApiResponse.ErrorResponse("Invalid Input Parameter", 404); + } + // Fetch assigned project + List result = new List(); + + var employeeQuery = _context.ProjectAllocations + .Include(pa => pa.Employee) + .Where(pa => pa.ProjectId == projectid && pa.TenantId == tenantId && pa.Employee != null); + + if (includeInactive) + { + + result = await employeeQuery.Select(pa => pa.Employee ?? new Employee()).ToListAsync(); + } + else + { + result = await employeeQuery + .Where(pa => pa.IsActive) + .Select(pa => pa.Employee ?? new Employee()).ToListAsync(); + } + + List resultVM = new List(); + foreach (Employee employee in result) + { + EmployeeVM vm = _mapper.Map(employee); + resultVM.Add(vm); + } + + return ApiResponse.SuccessResponse(resultVM, "Successfully fetched the list of employees for seleted project", 200); + } + + /// + /// Retrieves a list of employees for a specific project. + /// This method is optimized to perform all filtering and mapping on the database server. + /// + /// The ID of the project. + /// Whether to include employees from inactive allocations. + /// The ID of the current tenant. + /// The current authenticated employee (used for permission checks). + /// An ApiResponse containing a list of employees or an error. + public async Task> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee) + { + // --- Step 1: Input Validation --- + if (projectId == null) + { + _logger.LogWarning("GetEmployeeByProjectID called with a null projectId."); + // 400 Bad Request is more appropriate for invalid input than 404 Not Found. + return ApiResponse.ErrorResponse("Project ID is required.", "Invalid Input Parameter", 400); + } + + _logger.LogInfo("Fetching employees for ProjectID: {ProjectId}, IncludeInactive: {IncludeInactive}", projectId, includeInactive); + + try + { + // --- CRITICAL: Security Check --- + // Before fetching data, you MUST verify the user has permission to see it. + // This is a placeholder for your actual permission logic. + var hasProjectPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + var hasAllEmployeePermission = await _permission.HasPermission(PermissionsMaster.ViewAllEmployees, loggedInEmployee.Id); + var hasviewTeamPermission = await _permission.HasPermission(PermissionsMaster.ViewTeamMembers, loggedInEmployee.Id); + + if (!(hasProjectPermission && (hasAllEmployeePermission || hasviewTeamPermission))) + { + _logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId); + return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to view this project's team.", 403); + } + + // --- Step 2: Build a Single, Efficient IQueryable --- + // We start with the base query and conditionally add filters before executing it. + // This avoids code duplication and is highly performant. + var employeeQuery = _context.ProjectAllocations + .Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId); + + // Conditionally apply the filter for active allocations. + if (!includeInactive) + { + employeeQuery = employeeQuery.Where(pa => pa.IsActive); + } + + // --- Step 3: Project Directly to the ViewModel on the Database Server --- + // This is the most significant performance optimization. + // Instead of fetching full Employee entities, we select only the data needed for the EmployeeVM. + // AutoMapper's ProjectTo is perfect for this, as it translates the mapping configuration into an efficient SQL SELECT statement. + var resultVM = await employeeQuery + .Where(pa => pa.Employee != null) // Safety check for data integrity + .Select(pa => pa.Employee) // Navigate to the Employee entity + .ProjectTo(_mapper.ConfigurationProvider) // Let AutoMapper generate the SELECT + .ToListAsync(); + + _logger.LogInfo("Successfully fetched {EmployeeCount} employees for project {ProjectId}.", resultVM.Count, projectId); + + // Note: The original mapping loop is now completely gone, replaced by the single efficient query above. + + return ApiResponse.SuccessResponse(resultVM, "Successfully fetched the list of employees for the selected project.", 200); + } + catch (Exception ex) + { + // --- Step 4: Graceful Error Handling --- + _logger.LogError(ex, "An error occurred while fetching employees for project {ProjectId}. ", projectId); + return ApiResponse.ErrorResponse("An internal server error occurred.", "Database Query Failed", 500); + } + } + + public async Task> GetProjectAllocation(Guid? projectId, Guid tenantId, Employee loggedInEmployee) + { + 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 ApiResponse.SuccessResponse(employees, "Success.", 200); + } + + /// + /// Retrieves project allocation details for a specific project. + /// This method is optimized for performance and includes security checks. + /// + /// The ID of the project. + /// The ID of the current tenant. + /// The current authenticated employee for permission checks. + /// An ApiResponse containing allocation details or an appropriate error. + public async Task> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee) + { + // --- Step 1: Input Validation --- + if (projectId == null) + { + _logger.LogWarning("GetProjectAllocation called with a null projectId."); + return ApiResponse.ErrorResponse("Project ID is required.", "Invalid Input Parameter", 400); + } + + _logger.LogInfo("Fetching allocations for ProjectID: {ProjectId} for user {UserId}", projectId, loggedInEmployee.Id); + + try + { + // --- Step 2: Security and Existence Checks --- + // Before fetching data, you MUST verify the user has permission to see it. + // This is a placeholder for your actual permission logic. + var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.Value); + if (!hasPermission) + { + _logger.LogWarning("Access DENIED for user {UserId} on project {ProjectId}.", loggedInEmployee.Id, projectId); + return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to view this project's team.", 403); + } + + // --- Step 3: Execute a Single, Optimized Database Query --- + // This query projects directly to a new object on the database server, which is highly efficient. + var allocations = await _context.ProjectAllocations + // Filter down to the relevant records first. + .Where(pa => pa.ProjectId == projectId && pa.TenantId == tenantId && pa.Employee != null) + // Project directly to the final shape. This tells EF Core which columns to select. + // The redundant .Include() is removed as EF Core infers the JOIN from this Select. + .Select(pa => new + { + // Fields from ProjectAllocation + ID = pa.Id, + pa.EmployeeId, + pa.ProjectId, + pa.AllocationDate, + pa.ReAllocationDate, + pa.IsActive, + + // Fields from the joined Employee table (no null checks needed due to the 'Where' clause) + FirstName = pa.Employee!.FirstName, + LastName = pa.Employee.LastName, + MiddleName = pa.Employee.MiddleName, + + // Simplified JobRoleId logic: Use the allocation's role if it exists, otherwise fall back to the employee's default role. + JobRoleId = pa.JobRoleId ?? pa.Employee.JobRoleId + }) + .ToListAsync(); + + _logger.LogInfo("Successfully fetched {AllocationCount} allocations for project {ProjectId}.", allocations.Count, projectId); + + return ApiResponse.SuccessResponse(allocations, "Project allocations retrieved successfully.", 200); + } + catch (Exception ex) + { + // --- Step 4: Graceful Error Handling --- + // Log the full exception for debugging, but return a generic, safe error message. + _logger.LogError(ex, "An error occurred while fetching allocations for project {ProjectId}.", projectId); + return ApiResponse.ErrorResponse("An internal server error occurred.", "Database query failed.", 500); + } + } + #endregion + #region =================================================================== Helper Functions =================================================================== /// @@ -661,7 +868,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogWarning("Failed to update cache for project {ProjectId} : \n {Error}", projectId, ex.Message); + _logger.LogError(ex, "Failed to update cache for project {ProjectId} : ", projectId); } // Map from the database entity to the response ViewModel. @@ -682,7 +889,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError("Background cache update failed for project {ProjectId} \n {Error}", project.Id, ex.Message); + _logger.LogError(ex, "Background cache update failed for project {ProjectId} ", project.Id); } } diff --git a/Marco.Pms.Services/Service/RefreshTokenService.cs b/Marco.Pms.Services/Service/RefreshTokenService.cs index 231e27c..84ef3fd 100644 --- a/Marco.Pms.Services/Service/RefreshTokenService.cs +++ b/Marco.Pms.Services/Service/RefreshTokenService.cs @@ -1,11 +1,11 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using Marco.Pms.DataAccess.Data; +using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; #nullable disable namespace MarcoBMS.Services.Service @@ -94,7 +94,7 @@ namespace MarcoBMS.Services.Service } catch (Exception ex) { - _logger.LogError("{Error}", ex.Message); + _logger.LogError(ex, "Error occured while creating new JWT token for user {UserId}", userId); throw; } } @@ -132,7 +132,7 @@ namespace MarcoBMS.Services.Service } catch (Exception ex) { - _logger.LogError("Error creating MPIN token for userId: {UserId}, tenantId: {TenantId}, error : {Error}", userId, tenantId, ex.Message); + _logger.LogError(ex, "Error creating MPIN token for userId: {UserId}, tenantId: {TenantId}", userId, tenantId); throw; } } @@ -218,7 +218,7 @@ namespace MarcoBMS.Services.Service catch (Exception ex) { // Token is invalid - _logger.LogError($"Token validation failed: {ex.Message}"); + _logger.LogError(ex, "Token validation failed"); return null; } } diff --git a/Marco.Pms.Services/Service/S3UploadService.cs b/Marco.Pms.Services/Service/S3UploadService.cs index c29cfdd..4ce7a4b 100644 --- a/Marco.Pms.Services/Service/S3UploadService.cs +++ b/Marco.Pms.Services/Service/S3UploadService.cs @@ -64,7 +64,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError("{error} while uploading file to S3", ex.Message); + _logger.LogError(ex, "error occured while uploading file to S3"); } @@ -87,7 +87,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError("{error} while requesting presigned url from Amazon S3", ex.Message); + _logger.LogError(ex, "error occured while requesting presigned url from Amazon S3", ex.Message); return string.Empty; } } @@ -107,7 +107,7 @@ namespace Marco.Pms.Services.Service } catch (Exception ex) { - _logger.LogError("{error} while deleting from Amazon S3", ex.Message); + _logger.LogError(ex, "error ocured while deleting from Amazon S3"); return false; } } @@ -202,7 +202,7 @@ namespace Marco.Pms.Services.Service } else { - _logger.LogError("Warning: Could not find MimeType, Type, or ContentType property in Definition."); + _logger.LogWarning("Warning: Could not find MimeType, Type, or ContentType property in Definition."); return "application/octet-stream"; } } @@ -211,16 +211,16 @@ namespace Marco.Pms.Services.Service return "application/octet-stream"; // Default if type cannot be determined } } - catch (FormatException) + catch (FormatException fEx) { // Handle cases where the input string is not valid Base64 - _logger.LogError("Invalid Base64 string."); + _logger.LogError(fEx, "Invalid Base64 string."); return string.Empty; } catch (Exception ex) { // Handle other potential errors during decoding or inspection - _logger.LogError($"An error occurred: {ex.Message}"); + _logger.LogError(ex, "errors during decoding or inspection"); return string.Empty; } } diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index a23eba0..d0539b0 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -13,5 +13,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee); Task> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee); + Task> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee); + Task> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee); } } diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/ISignalRService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/ISignalRService.cs new file mode 100644 index 0000000..c37322b --- /dev/null +++ b/Marco.Pms.Services/Service/ServiceInterfaces/ISignalRService.cs @@ -0,0 +1,7 @@ +namespace Marco.Pms.Services.Service.ServiceInterfaces +{ + public interface ISignalRService + { + Task SendNotificationAsync(object notification); + } +} diff --git a/Marco.Pms.Services/Service/SignalRService.cs b/Marco.Pms.Services/Service/SignalRService.cs new file mode 100644 index 0000000..fecc9b0 --- /dev/null +++ b/Marco.Pms.Services/Service/SignalRService.cs @@ -0,0 +1,29 @@ +using Marco.Pms.Services.Hubs; +using Marco.Pms.Services.Service.ServiceInterfaces; +using MarcoBMS.Services.Service; +using Microsoft.AspNetCore.SignalR; + +namespace Marco.Pms.Services.Service +{ + public class SignalRService : ISignalRService + { + private readonly IHubContext _signalR; + private readonly ILoggingService _logger; + public SignalRService(IHubContext signalR, ILoggingService logger) + { + _signalR = signalR ?? throw new ArgumentNullException(nameof(signalR)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + public async Task SendNotificationAsync(object notification) + { + try + { + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occured during sending notification through signalR"); + } + } + } +}