Directory_Refactor #128
@ -144,63 +144,47 @@ namespace Marco.Pms.Services.Controllers
|
||||
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> DeleteContactNote(Guid id, [FromQuery] bool? active)
|
||||
public async Task<IActionResult> 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
|
||||
|
@ -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<CreateContactNoteDto, ContactNote>();
|
||||
CreateMap<UpdateContactNoteDto, ContactNote>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
@ -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<object>.ErrorResponse("Internal Server Error", "An error occurred while fetching notes. Please try again later.", 500);
|
||||
}
|
||||
}
|
||||
public async Task<ApiResponse<object>> GetNoteListByContactId(Guid id, bool active, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all notes associated with a given contact, subject to permission checks and contact-bucket mappings.
|
||||
/// </summary>
|
||||
/// <param name="id">The contact ID.</param>
|
||||
/// <param name="tenantId">The tenant ID of the current user.</param>
|
||||
/// <param name="active">Whether to filter for active notes only.</param>
|
||||
/// <param name="loggedInEmployee">The currently logged in employee object.</param>
|
||||
/// <returns>Returns a list of contact notes wrapped in ApiResponse.</returns>
|
||||
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.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<object>.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<object>.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<object>.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<ContactNote> notes = await notesQuery.ToListAsync();
|
||||
|
||||
var noteIds = notes.Select(n => n.Id).ToList();
|
||||
List<DirectoryUpdateLog>? updateLogs = await _context.DirectoryUpdateLogs
|
||||
.Include(l => l.Employee)
|
||||
.ThenInclude(e => e!.JobRole)
|
||||
.Where(l => noteIds.Contains(l.RefereanceId))
|
||||
List<ContactNote> 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<DirectoryUpdateLog> 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<ContactNoteVM> noteVMs = _mapper.Map<List<ContactNoteVM>>(notes);
|
||||
|
||||
_logger.LogInfo("{count} contact-notes record from contact {ContactId} fetched by Employee {EmployeeId}", noteVMs.Count, id, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.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<object>.SuccessResponse(
|
||||
noteVMs,
|
||||
$"{noteVMs.Count} contact-notes record(s) fetched successfully",
|
||||
StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<object>> CreateContactNote(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee)
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="noteDto">The DTO containing the note details.</param>
|
||||
/// <param name="tenantId">The tenant identifier to which the contact belongs.</param>
|
||||
/// <param name="loggedInEmployee">The logged-in employee attempting the action.</param>
|
||||
/// <returns>ApiResponse containing the created note details or error information.</returns>
|
||||
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.ErrorResponse("Empty payload.", "Request body cannot be null.", 400);
|
||||
}
|
||||
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
|
||||
}
|
||||
public async Task<ApiResponse<object>> 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<object>.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<object>.ErrorResponse("Note not found", "Note not found", 404);
|
||||
return ApiResponse<object>.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<object>.ErrorResponse("Contact not found", "Contact not found", 404);
|
||||
}
|
||||
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
|
||||
}
|
||||
public async Task<ApiResponse<object>> 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<ContactNote>(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<ContactNoteVM>(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<object>.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<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing contact note and logs changes
|
||||
/// both in relational DB (SQL) and update logs (possibly MongoDB).
|
||||
/// </summary>
|
||||
/// <param name="id">The note ID that needs to be updated.</param>
|
||||
/// <param name="noteDto">DTO with updated note data.</param>
|
||||
/// <returns>Standardized ApiResponse with updated note or error details.</returns>
|
||||
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.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<object>.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<ContactNoteVM>(contactNote);
|
||||
noteVM.UpdatedAt = contactNote.UpdatedAt;
|
||||
noteVM.UpdatedBy = _mapper.Map<BasicEmployeeVM>(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<object>.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<object>.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<object>.ErrorResponse("Failed to update note", "An unexpected error occurred while saving note.", 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft deletes (or restores) a contact note by updating its active status.
|
||||
/// Also pushes an update log entry in SQL and Mongo (audit trail).
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the contact note to delete/restore.</param>
|
||||
/// <param name="active">Flag to set note as active or inactive.</param>
|
||||
/// <param name="tenantId">Tenant identifier of the logged-in user.</param>
|
||||
/// <param name="loggedInEmployee">The employee performing this action.</param>
|
||||
/// <returns>ApiResponse with success or error details.</returns>
|
||||
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.ErrorResponse("Failed to delete note",
|
||||
"An unexpected error occurred while deleting/restoring the note.",
|
||||
500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Bucket APIs ===================================================================
|
||||
|
@ -21,10 +21,10 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
|
||||
|
||||
Task<ApiResponse<object>> GetListOFAllNotesAsync(Guid? projectId, int pageSize, int pageNumber, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> GetNoteListByContactId(Guid id, bool active, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> CreateContactNote(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> UpdateContactNote(Guid id, UpdateContactNoteDto noteDto);
|
||||
Task<ApiResponse<object>> DeleteContactNote(Guid id, bool active);
|
||||
Task<ApiResponse<object>> GetNoteListByContactIdAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> CreateContactNoteAsync(CreateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> UpdateContactNoteAsync(Guid id, UpdateContactNoteDto noteDto, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> DeleteContactNoteAsync(Guid id, bool active, Guid tenantId, Employee loggedInEmployee);
|
||||
|
||||
|
||||
Task<ApiResponse<object>> GetBucketListAsync(Guid tenantId, Employee loggedInEmployee);
|
||||
|
Loading…
x
Reference in New Issue
Block a user