From 4af68918217db7cd82c601b14510543707225d68 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 20 Aug 2025 10:55:16 +0530 Subject: [PATCH] Optimized the Directory notes API --- .../Controllers/DirectoryController.cs | 60 +-- .../MappingProfiles/MappingProfile.cs | 2 + .../Service/DirectoryService.cs | 432 +++++++++++++----- .../ServiceInterfaces/IDirectoryService.cs | 8 +- 4 files changed, 355 insertions(+), 147 deletions(-) diff --git a/Marco.Pms.Services/Controllers/DirectoryController.cs b/Marco.Pms.Services/Controllers/DirectoryController.cs index bdc8598..5731065 100644 --- a/Marco.Pms.Services/Controllers/DirectoryController.cs +++ b/Marco.Pms.Services/Controllers/DirectoryController.cs @@ -144,63 +144,47 @@ namespace Marco.Pms.Services.Controllers public async Task CreateContactNote([FromBody] CreateContactNoteDto noteDto) { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - var response = await _directoryService.CreateContactNote(noteDto, tenantId, loggedInEmployee); ; - if (response.StatusCode == 200) + var response = await _directoryService.CreateContactNoteAsync(noteDto, tenantId, loggedInEmployee); ; + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Notes", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } [HttpGet("notes/{ContactId}")] public async Task GetNoteListByContactId(Guid contactId, [FromQuery] bool active = true) { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - var response = await _directoryService.GetNoteListByContactId(contactId, active, tenantId, loggedInEmployee); - if (response.StatusCode == 200) - { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else - { - return BadRequest(response); - } + var response = await _directoryService.GetNoteListByContactIdAsync(contactId, active, tenantId, loggedInEmployee); + return StatusCode(response.StatusCode, response); } [HttpPut("note/{id}")] public async Task UpdateContactNote(Guid id, [FromBody] UpdateContactNoteDto noteDto) { - var response = await _directoryService.UpdateContactNote(id, noteDto); - if (response.StatusCode == 200) + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.UpdateContactNoteAsync(id, noteDto, tenantId, loggedInEmployee); + if (response.Success) { - return Ok(response); - } - else if (response.StatusCode == 404) - { - return NotFound(response); - } - else - { - return BadRequest(response); + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Notes", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); } + return StatusCode(response.StatusCode, response); } [HttpDelete("note/{id}")] - public async Task DeleteContactNote(Guid id, [FromQuery] bool? active) + public async Task DeleteContactNote(Guid id, [FromQuery] bool active = false) { - var response = await _directoryService.DeleteContactNote(id, active ?? false); - return Ok(response); + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _directoryService.DeleteContactNoteAsync(id, active, tenantId, loggedInEmployee); + if (response.Success) + { + var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Directory_Notes", Response = response.Data }; + await _signalR.SendNotificationAsync(notification); + } + return StatusCode(response.StatusCode, response); } #endregion diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index a4d0c55..21be173 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -274,6 +274,8 @@ namespace Marco.Pms.Services.MappingProfiles dest => dest.OrganizationName, opt => opt.MapFrom(src => src.Contact != null ? src.Contact.Organization : string.Empty) ); + CreateMap(); + CreateMap(); #endregion } diff --git a/Marco.Pms.Services/Service/DirectoryService.cs b/Marco.Pms.Services/Service/DirectoryService.cs index bcf19cc..83b4079 100644 --- a/Marco.Pms.Services/Service/DirectoryService.cs +++ b/Marco.Pms.Services/Service/DirectoryService.cs @@ -8,6 +8,7 @@ using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Mapper; using Marco.Pms.Model.MongoDBModels.Utility; using Marco.Pms.Model.Utilities; +using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Directory; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Projects; @@ -1549,151 +1550,372 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("Internal Server Error", "An error occurred while fetching notes. Please try again later.", 500); } } - public async Task> GetNoteListByContactId(Guid id, bool active, Guid tenantId, Employee loggedInEmployee) - { - var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id); + /// + /// Fetches all notes associated with a given contact, subject to permission checks and contact-bucket mappings. + /// + /// The contact ID. + /// The tenant ID of the current user. + /// Whether to filter for active notes only. + /// The currently logged in employee object. + /// Returns a list of contact notes wrapped in ApiResponse. + public async Task> GetNoteListByContactIdAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee) + { + // Step 1: Permission Validation + var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id); if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission) { - _logger.LogWarning("Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId} Do not have permission", loggedInEmployee.Id, tenantId); - return ApiResponse.ErrorResponse("Access Denied", "You don't have access to view notes.", 403); + _logger.LogWarning( + "Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId}. No permissions granted.", + loggedInEmployee.Id, tenantId); + + return ApiResponse.ErrorResponse( + "Access Denied", + "You don't have access to view notes.", + StatusCodes.Status403Forbidden); } - Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId); + // Step 2: Validate Contact Exists + Contact? contact = await _context.Contacts + .AsNoTracking() // optimization: no tracking needed + .FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId); + if (contact == null) { - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} attempted to fetch a list notes from contact with ID {ContactId}, but the contact was not found in the database.", loggedInEmployee.Id, id); - return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); + _logger.LogWarning( + "Employee {EmployeeId} attempted to fetch notes for Contact {ContactId}, but the contact was not found. TenantId: {TenantId}", + loggedInEmployee.Id, id, tenantId); + + return ApiResponse.ErrorResponse( + "Contact not found", + "Contact not found", + StatusCodes.Status404NotFound); } - if (!hasAdminPermission && (hasManagerPermission || hasUserPermission)) + // Step 3: Bucket-level Security Checks (Non-admin users) + if (!hasAdminPermission) { - var bucketIds = await _context.EmployeeBucketMappings.Where(em => em.EmployeeId == loggedInEmployee.Id).Select(em => em.BucketId).ToListAsync(); - var hasContactAccess = await _context.ContactBucketMappings.AnyAsync(cb => bucketIds.Contains(cb.BucketId) && cb.ContactId == contact.Id); + var employeeBucketIds = await _context.EmployeeBucketMappings + .AsNoTracking() + .Where(em => em.EmployeeId == loggedInEmployee.Id) + .Select(em => em.BucketId) + .ToListAsync(); + + bool hasContactAccess = await _context.ContactBucketMappings + .AsNoTracking() + .AnyAsync(cb => employeeBucketIds.Contains(cb.BucketId) && cb.ContactId == contact.Id); + if (!hasContactAccess) { - _logger.LogWarning("Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId} Do not have access of bucket", loggedInEmployee.Id, tenantId); - return ApiResponse.ErrorResponse("Access Denied", "You don't have access to view notes.", 403); + _logger.LogWarning( + "Access Denied. EmployeeId: {EmployeeId}, TenantId: {TenantId}. No bucket access for ContactId {ContactId}", + loggedInEmployee.Id, tenantId, contact.Id); + + return ApiResponse.ErrorResponse( + "Access Denied", + "You don't have access to view notes.", + StatusCodes.Status403Forbidden); } } + // Step 4: Fetch Notes var notesQuery = _context.ContactNotes - .Include(n => n.Createdby) - .ThenInclude(e => e!.JobRole) - .Include(n => n.UpdatedBy) - .ThenInclude(e => e!.JobRole) - .Where(n => n.ContactId == contact.Id && n.TenantId == tenantId); + .Include(n => n.Createdby) // Eager load creator + .ThenInclude(e => e!.JobRole) + .Include(n => n.UpdatedBy) // Eager load updater + .ThenInclude(e => e!.JobRole) + .Where(n => n.ContactId == contact.Id && n.TenantId == tenantId); if (active) - { notesQuery = notesQuery.Where(n => n.IsActive); - } - List notes = await notesQuery.ToListAsync(); - - var noteIds = notes.Select(n => n.Id).ToList(); - List? updateLogs = await _context.DirectoryUpdateLogs - .Include(l => l.Employee) - .ThenInclude(e => e!.JobRole) - .Where(l => noteIds.Contains(l.RefereanceId)) + List notes = await notesQuery + .AsNoTracking() // reduce EF overhead .ToListAsync(); + // Step 5: Fetch Update Logs in one DB call + var noteIds = notes.Select(n => n.Id).ToList(); + List updateLogs = new(); + + if (noteIds.Count > 0) // only fetch logs if needed + { + updateLogs = await _context.DirectoryUpdateLogs + .Include(l => l.Employee) + .ThenInclude(e => e!.JobRole) + .AsNoTracking() + .Where(l => noteIds.Contains(l.RefereanceId)) + .ToListAsync(); + } + + // Step 6: Map Entities to ViewModels List noteVMs = _mapper.Map>(notes); - _logger.LogInfo("{count} contact-notes record from contact {ContactId} fetched by Employee {EmployeeId}", noteVMs.Count, id, loggedInEmployee.Id); - return ApiResponse.SuccessResponse(noteVMs, $"{noteVMs.Count} contact-notes record fetched successfully", 200); + // Step 7: Final Log + Response + _logger.LogInfo( + "Employee {EmployeeId} successfully fetched {Count} notes for Contact {ContactId} in Tenant {TenantId}", + loggedInEmployee.Id, noteVMs.Count, id, tenantId); + + return ApiResponse.SuccessResponse( + noteVMs, + $"{noteVMs.Count} contact-notes record(s) fetched successfully", + StatusCodes.Status200OK); } - public async Task> CreateContactNote(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee) + /// + /// Creates a note for a given contact under the specified tenant. + /// Ensures that the contact exists and belongs to the tenant before adding the note. + /// + /// The DTO containing the note details. + /// The tenant identifier to which the contact belongs. + /// The logged-in employee attempting the action. + /// ApiResponse containing the created note details or error information. + public async Task> CreateContactNoteAsync(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee) { - if (noteDto != null) + // Validate request payload + if (noteDto == null) { - Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == noteDto.ContactId && c.IsActive && c.TenantId == tenantId); - if (contact != null) - { - ContactNote note = noteDto.ToContactNoteFromCreateContactNoteDto(tenantId, loggedInEmployee.Id); - _context.ContactNotes.Add(note); - await _context.SaveChangesAsync(); - ContactNoteVM noteVM = note.ToContactNoteVMFromContactNote(); - _logger.LogInfo("Employee {EmployeeId} Added note at contact {ContactId}", loggedInEmployee.Id, contact.Id); - return ApiResponse.SuccessResponse(noteVM, "Note added successfully", 200); - } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} attempted to add a note to contact with ID {ContactId}, but the contact was not found in the database.", loggedInEmployee.Id, noteDto.ContactId); - return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); + _logger.LogWarning( + "Employee {EmployeeId} attempted to create a note with an empty payload.", + loggedInEmployee.Id); + + return ApiResponse.ErrorResponse("Empty payload.", "Request body cannot be null.", 400); } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", loggedInEmployee.Id); - return ApiResponse.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400); - } - public async Task> UpdateContactNote(Guid id, UpdateContactNoteDto noteDto) - { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - if (noteDto != null && id == noteDto.Id) + + try { - Contact? contact = await _context.Contacts.FirstOrDefaultAsync(c => c.Id == noteDto.ContactId && c.TenantId == tenantId); - if (contact != null) + // Check if the contact exists and is active for this tenant + Contact? contact = await _context.Contacts + .AsNoTracking() // optimization for read-only query + .FirstOrDefaultAsync(c => c.Id == noteDto.ContactId && c.IsActive && c.TenantId == tenantId); + + if (contact == null) { - ContactNote? contactNote = await _context.ContactNotes - .Include(cn => cn.Createdby) - .ThenInclude(e => e!.JobRole) - .Include(cn => cn.Contact) - .FirstOrDefaultAsync(n => n.Id == noteDto.Id && n.ContactId == contact.Id && n.IsActive); - if (contactNote != null) - { - contactNote.Note = noteDto.Note; - contactNote.UpdatedById = LoggedInEmployee.Id; - contactNote.UpdatedAt = DateTime.UtcNow; + _logger.LogWarning( + "Employee {EmployeeId} attempted to add a note to Contact {ContactId}, but it was not found for tenant {TenantId}.", + loggedInEmployee.Id, noteDto.ContactId, tenantId); - _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog - { - RefereanceId = id, - UpdatedById = LoggedInEmployee.Id, - UpdateAt = DateTime.UtcNow - }); - - await _context.SaveChangesAsync(); - ContactNoteVM noteVM = contactNote.ToContactNoteVMFromContactNote(); - noteVM.UpdatedAt = DateTime.UtcNow; - noteVM.UpdatedBy = LoggedInEmployee.ToBasicEmployeeVMFromEmployee(); - - _logger.LogInfo("Employee {EmployeeId} updated note {NoteId} at contact {ContactId}", LoggedInEmployee.Id, noteVM.Id, contact.Id); - return ApiResponse.SuccessResponse(noteVM, "Note updated successfully", 200); - } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} attempted to update a note {NoteId} to contact with ID {ContactId}, but the Note was not found in the database.", LoggedInEmployee.Id, noteDto.Id, noteDto.ContactId); - return ApiResponse.ErrorResponse("Note not found", "Note not found", 404); + return ApiResponse.ErrorResponse("Contact not found.", "The specified contact does not exist.", 404); } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} attempted to update a note {NoteId} to contact with ID {ContactId}, but the contact was not found in the database.", LoggedInEmployee.Id, noteDto.Id, noteDto.ContactId); - return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); - } - _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id); - return ApiResponse.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400); - } - public async Task> DeleteContactNote(Guid id, bool active) - { - Guid tenantId = _userHelper.GetTenantId(); - var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - ContactNote? note = await _context.ContactNotes.FirstOrDefaultAsync(n => n.Id == id && n.TenantId == tenantId); - if (note != null) - { - note.IsActive = active; - note.UpdatedById = LoggedInEmployee.Id; - note.UpdatedAt = DateTime.UtcNow; + // Map DTO -> Entity using AutoMapper + ContactNote note = _mapper.Map(noteDto); + note.CreatedById = loggedInEmployee.Id; + note.TenantId = tenantId; - _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog - { - RefereanceId = id, - UpdatedById = LoggedInEmployee.Id, - UpdateAt = DateTime.UtcNow - }); + // Save new note + await _context.ContactNotes.AddAsync(note); await _context.SaveChangesAsync(); - _logger.LogInfo("Employee {EmployeeId} deleted note {NoteId}", LoggedInEmployee.Id, id); + + // Map Entity -> ViewModel + ContactNoteVM noteVM = _mapper.Map(note); + + _logger.LogInfo( + "Employee {EmployeeId} successfully added a note (NoteId: {NoteId}) to Contact {ContactId} for Tenant {TenantId}.", + loggedInEmployee.Id, note.Id, contact.Id, tenantId); + + return ApiResponse.SuccessResponse(noteVM, "Note added successfully.", 200); + } + catch (Exception ex) + { + // Log unexpected errors to troubleshoot + _logger.LogError( + ex, + "Unexpected error occurred while Employee {EmployeeId} attempted to add a note for Contact {ContactId} in Tenant {TenantId}.", + loggedInEmployee.Id, noteDto.ContactId, tenantId); + + return ApiResponse.ErrorResponse("An unexpected error occurred.", ex.Message, 500); + } + } + + /// + /// Updates an existing contact note and logs changes + /// both in relational DB (SQL) and update logs (possibly MongoDB). + /// + /// The note ID that needs to be updated. + /// DTO with updated note data. + /// Standardized ApiResponse with updated note or error details. + public async Task> UpdateContactNoteAsync(Guid id, UpdateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee) + { + // Validation: check null payload or mismatched ID + if (noteDto == null || id != noteDto.Id) + { + _logger.LogWarning("Employee {EmployeeId} sent invalid or null payload. RouteId: {RouteId}, PayloadId: {PayloadId}", + loggedInEmployee.Id, id, noteDto?.Id ?? Guid.Empty); + + return ApiResponse.ErrorResponse("Invalid or empty payload", "Invalid or empty payload", 400); } - _logger.LogWarning("Employee {EmployeeId} tries to delete contact note {NoteId} but not found in database", LoggedInEmployee.Id, id); - return ApiResponse.SuccessResponse(new { }, "Note deleted successfully", 200); + // Check if the contact belongs to this tenant + Contact? contact = await _context.Contacts + .AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == noteDto.ContactId && c.TenantId == tenantId); + + if (contact == null) + { + _logger.LogWarning("Employee {EmployeeId} attempted to update note {NoteId} for Contact {ContactId}, but the contact was not found in Tenant {TenantId}.", + loggedInEmployee.Id, noteDto.Id, noteDto.ContactId, tenantId); + + return ApiResponse.ErrorResponse("Contact not found", "Contact not found", 404); + } + + // Fetch the contact note to be updated + ContactNote? contactNote = await _context.ContactNotes + .Include(cn => cn.Createdby) + .ThenInclude(e => e!.JobRole) + .Include(cn => cn.Contact) + .FirstOrDefaultAsync(n => + n.Id == noteDto.Id && + n.ContactId == contact.Id && + n.IsActive); + + if (contactNote == null) + { + _logger.LogWarning("Employee {EmployeeId} attempted to update Note {NoteId} for Contact {ContactId}, but the note was not found or inactive.", + loggedInEmployee.Id, noteDto.Id, noteDto.ContactId); + + return ApiResponse.ErrorResponse("Note not found", "Note not found", 404); + } + + // Capture old state for change-log before updating + var oldObject = _updateLogsHelper.EntityToBsonDocument(contactNote); + + // Apply updates + contactNote.Note = noteDto.Note; + contactNote.UpdatedById = loggedInEmployee.Id; + contactNote.UpdatedAt = DateTime.UtcNow; + + // Save change log into relational logs + _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog + { + RefereanceId = id, + UpdatedById = loggedInEmployee.Id, + UpdateAt = DateTime.UtcNow + }); + + // Wrap in try-catch for robustness + try + { + await _context.SaveChangesAsync(); + + // Map to ViewModel for output + ContactNoteVM noteVM = _mapper.Map(contactNote); + noteVM.UpdatedAt = contactNote.UpdatedAt; + noteVM.UpdatedBy = _mapper.Map(loggedInEmployee); + + // Push audit log asynchronously (Mongo / NoSQL Logs) + await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject + { + EntityId = contactNote.Id.ToString(), + UpdatedById = loggedInEmployee.Id.ToString(), + OldObject = oldObject, + UpdatedAt = DateTime.UtcNow + }, contactNoteCollection); + + // Success log + _logger.LogInfo("Employee {EmployeeId} successfully updated Note {NoteId} for Contact {ContactId} at {UpdatedAt}", + loggedInEmployee.Id, noteVM.Id, contact.Id, noteVM.UpdatedAt); + + return ApiResponse.SuccessResponse(noteVM, "Note updated successfully", 200); + } + catch (DbUpdateException ex) + { + _logger.LogError(ex, "Database Exception occurred while updating Note {NoteId} for Contact {ContactId} by Employee {EmployeeId}", + noteDto.Id, noteDto.ContactId, loggedInEmployee.Id); + + return ApiResponse.ErrorResponse("Failed to update note", "An unexpected error occurred while saving note.", 500); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred while updating Note {NoteId} for Contact {ContactId} by Employee {EmployeeId}", + noteDto.Id, noteDto.ContactId, loggedInEmployee.Id); + + return ApiResponse.ErrorResponse("Failed to update note", "An unexpected error occurred while saving note.", 500); + } } + /// + /// Soft deletes (or restores) a contact note by updating its active status. + /// Also pushes an update log entry in SQL and Mongo (audit trail). + /// + /// ID of the contact note to delete/restore. + /// Flag to set note as active or inactive. + /// Tenant identifier of the logged-in user. + /// The employee performing this action. + /// ApiResponse with success or error details. + public async Task> DeleteContactNoteAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee) + { + // Lookup note within the tenant + ContactNote? note = await _context.ContactNotes + .FirstOrDefaultAsync(n => n.Id == id && n.TenantId == tenantId); + + if (note == null) + { + // Log missing resource + _logger.LogWarning("Employee {EmployeeId} attempted to delete Note {NoteId}, but it was not found in Tenant {TenantId}", + loggedInEmployee.Id, id, tenantId); + + return ApiResponse.ErrorResponse("Note not found", "Note not found", 404); + } + + // Capture old state for audit logging + var oldObject = _updateLogsHelper.EntityToBsonDocument(note); + + // Update note metadata + var currentTime = DateTime.UtcNow; + note.IsActive = active; // soft delete (false) or restore (true) + note.UpdatedById = loggedInEmployee.Id; + note.UpdatedAt = currentTime; + + // Add relational update log entry + _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog + { + RefereanceId = id, + UpdatedById = loggedInEmployee.Id, + UpdateAt = currentTime + }); + + try + { + // Save SQL changes + await _context.SaveChangesAsync(); + + // Push audit log (Mongo / NoSQL) + await _updateLogsHelper.PushToUpdateLogsAsync(new UpdateLogsObject + { + EntityId = note.Id.ToString(), + UpdatedById = loggedInEmployee.Id.ToString(), + OldObject = oldObject, + UpdatedAt = currentTime + }, contactNoteCollection); + + // Log success — distinguish delete vs restore + if (!active) + { + _logger.LogInfo("Employee {EmployeeId} soft deleted Note {NoteId} at {Timestamp}", + loggedInEmployee.Id, id, currentTime); + } + else + { + _logger.LogInfo("Employee {EmployeeId} restored Note {NoteId} at {Timestamp}", + loggedInEmployee.Id, id, currentTime); + } + + return ApiResponse.SuccessResponse(new { }, + active ? "Note restored successfully" : "Note deleted successfully", + 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while updating Note {NoteId} (delete/restore) in Tenant {TenantId} by Employee {EmployeeId}", + id, tenantId, loggedInEmployee.Id); + + return ApiResponse.ErrorResponse("Failed to delete note", + "An unexpected error occurred while deleting/restoring the note.", + 500); + } + } + + #endregion #region =================================================================== Bucket APIs =================================================================== diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs index 07369a8..c7d7d45 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IDirectoryService.cs @@ -21,10 +21,10 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> GetListOFAllNotesAsync(Guid? projectId, int pageSize, int pageNumber, Guid tenantId, Employee loggedInEmployee); - Task> GetNoteListByContactId(Guid id, bool active, Guid tenantId, Employee loggedInEmployee); - Task> CreateContactNote(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee); - Task> UpdateContactNote(Guid id, UpdateContactNoteDto noteDto); - Task> DeleteContactNote(Guid id, bool active); + Task> GetNoteListByContactIdAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee); + Task> CreateContactNoteAsync(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee); + Task> UpdateContactNoteAsync(Guid id, UpdateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee); + Task> DeleteContactNoteAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee); Task> GetBucketListAsync(Guid tenantId, Employee loggedInEmployee);