Document_Manager #129

Merged
ashutosh.nehete merged 83 commits from Document_Manager into main 2025-09-11 04:12:01 +00:00
6 changed files with 151 additions and 142 deletions
Showing only changes of commit 53a93542e9 - Show all commits

View File

@ -21,6 +21,6 @@ namespace Marco.Pms.Model.ViewModels.Directory
public List<BasicProjectVM> Projects { get; set; } = new List<BasicProjectVM>();
public List<BucketVM> Buckets { get; set; } = new List<BucketVM>();
public List<ContactTagVM> Tags { get; set; } = new List<ContactTagVM>();
public List<ContactNoteVM> Notes { get; set; } = new List<ContactNoteVM>();
//public List<ContactNoteVM> Notes { get; set; } = new List<ContactNoteVM>();
}
}

View File

@ -41,6 +41,7 @@ namespace Marco.Pms.Services.Controllers
return StatusCode(response.StatusCode, response);
}
[HttpGet]
public async Task<IActionResult> GetContactList([FromQuery] string? search, [FromQuery] List<Guid>? bucketIds, [FromQuery] List<Guid>? categoryIds, [FromQuery] Guid? projectId, [FromQuery] bool active = true)
{
@ -88,19 +89,9 @@ namespace Marco.Pms.Services.Controllers
[HttpGet("profile/{id}")]
public async Task<IActionResult> GetContactProfile(Guid id)
{
var response = await _directoryService.GetContactProfile(id);
if (response.StatusCode == 200)
{
return Ok(response);
}
else if (response.StatusCode == 404)
{
return NotFound(response);
}
else
{
return BadRequest(response);
}
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _directoryService.GetContactProfileAsync(id, tenantId, loggedInEmployee);
return StatusCode(response.StatusCode, response);
}
[HttpGet("organization")]

View File

@ -825,25 +825,25 @@ namespace Marco.Pms.Services.Helpers
}
contactVM.Tags = tagVMs;
}
List<ContactNote>? notes = await _context.ContactNotes.Where(n => n.ContactId == contact.Id && n.IsActive).ToListAsync();
if (notes.Any())
{
List<Guid>? noteIds = notes.Select(n => n.Id).ToList();
List<DirectoryUpdateLog>? noteUpdateLogs = await _context.DirectoryUpdateLogs.Include(l => l.Employee).Where(l => noteIds.Contains(l.RefereanceId)).OrderByDescending(l => l.UpdateAt).ToListAsync();
List<ContactNoteVM>? noteVMs = new List<ContactNoteVM>();
foreach (var note in notes)
{
DirectoryUpdateLog? noteUpdateLog = noteUpdateLogs.Where(n => n.RefereanceId == note.Id).OrderByDescending(l => l.UpdateAt).FirstOrDefault();
ContactNoteVM noteVM = note.ToContactNoteVMFromContactNote();
if (noteUpdateLog != null)
{
noteVM.UpdatedAt = noteUpdateLog.UpdateAt;
noteVM.UpdatedBy = noteUpdateLog.Employee != null ? noteUpdateLog.Employee.ToBasicEmployeeVMFromEmployee() : null;
}
noteVMs.Add(noteVM);
}
contactVM.Notes = noteVMs;
}
//List<ContactNote>? notes = await _context.ContactNotes.Where(n => n.ContactId == contact.Id && n.IsActive).ToListAsync();
//if (notes.Any())
//{
// List<Guid>? noteIds = notes.Select(n => n.Id).ToList();
// List<DirectoryUpdateLog>? noteUpdateLogs = await _context.DirectoryUpdateLogs.Include(l => l.Employee).Where(l => noteIds.Contains(l.RefereanceId)).OrderByDescending(l => l.UpdateAt).ToListAsync();
// List<ContactNoteVM>? noteVMs = new List<ContactNoteVM>();
// foreach (var note in notes)
// {
// DirectoryUpdateLog? noteUpdateLog = noteUpdateLogs.Where(n => n.RefereanceId == note.Id).OrderByDescending(l => l.UpdateAt).FirstOrDefault();
// ContactNoteVM noteVM = note.ToContactNoteVMFromContactNote();
// if (noteUpdateLog != null)
// {
// noteVM.UpdatedAt = noteUpdateLog.UpdateAt;
// noteVM.UpdatedBy = noteUpdateLog.Employee != null ? noteUpdateLog.Employee.ToBasicEmployeeVMFromEmployee() : null;
// }
// noteVMs.Add(noteVM);
// }
// contactVM.Notes = noteVMs;
//}
_logger.LogInfo("Employee ID {EmployeeId} fetched profile of contact {COntactId}", LoggedInEmployee.Id, contact.Id);
return ApiResponse<object>.SuccessResponse(contactVM, "Contact profile fetched successfully");

View File

@ -5,6 +5,7 @@ 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.Activities;
using Marco.Pms.Model.ViewModels.Directory;
using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Model.ViewModels.Master;
@ -66,18 +67,21 @@ namespace Marco.Pms.Services.MappingProfiles
#region ======================================================= Employee =======================================================
CreateMap<Employee, EmployeeVM>();
CreateMap<Employee, BasicEmployeeVM>();
#endregion
#region ======================================================= Directory =======================================================
CreateMap<Contact, ContactVM>();
CreateMap<Contact, ContactProfileVM>();
CreateMap<ContactPhone, ContactPhoneVM>();
CreateMap<ContactEmail, ContactEmailVM>();
CreateMap<ContactCategoryMaster, ContactCategoryVM>();
CreateMap<ContactTagMaster, ContactTagVM>();
CreateMap<Bucket, BucketVM>();
#endregion

View File

@ -5,7 +5,6 @@ using Marco.Pms.Model.Dtos.Directory;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Directory;
using Marco.Pms.Model.ViewModels.Master;
@ -74,16 +73,7 @@ namespace Marco.Pms.Services.Service
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
// Step 1: Perform initial permission checks in parallel.
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
var hasAdminPermissionTask = permissionService.HasPermission(PermissionsMaster.DirectoryAdmin, loggedInEmployee.Id);
var hasManagerPermissionTask = permissionService.HasPermission(PermissionsMaster.DirectoryManager, loggedInEmployee.Id);
var hasUserPermissionTask = permissionService.HasPermission(PermissionsMaster.DirectoryUser, loggedInEmployee.Id);
await Task.WhenAll(hasAdminPermissionTask, hasManagerPermissionTask, hasUserPermissionTask);
var hasAdminPermission = hasAdminPermissionTask.Result;
var hasManagerPermission = hasManagerPermissionTask.Result;
var hasUserPermission = hasUserPermissionTask.Result;
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployeeId);
// Step 2: Build the core IQueryable<Contact> with all filtering logic applied on the server.
// This is the most critical optimization.
@ -170,7 +160,7 @@ namespace Marco.Pms.Services.Service
{
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
return await taskDbContext.ContactsPhones
.Where(p => finalContactIds.Contains(p.ContactId))
.Where(p => finalContactIds.Contains(p.ContactId) && p.TenantId == tenantId)
.ToListAsync();
});
@ -178,7 +168,7 @@ namespace Marco.Pms.Services.Service
{
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
return await taskDbContext.ContactsEmails
.Where(e => finalContactIds.Contains(e.ContactId))
.Where(e => finalContactIds.Contains(e.ContactId) && e.TenantId == tenantId)
.ToListAsync();
});
@ -512,120 +502,129 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("Employee ID {EmployeeId} sent an empty Bucket id", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Bucket ID is empty", "Bucket ID is empty", 400);
}
public async Task<ApiResponse<object>> GetContactProfile(Guid id)
public async Task<ApiResponse<object>> GetContactProfileAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
if (id != Guid.Empty)
Guid loggedInEmployeeId = loggedInEmployee.Id;
if (id == Guid.Empty)
{
Contact? contact = await _context.Contacts.Include(c => c.ContactCategory).Include(c => c.CreatedBy).FirstOrDefaultAsync(c => c.Id == id && c.IsActive);
_logger.LogInfo("Employee ID {EmployeeId} sent an empty contact id", loggedInEmployeeId);
return ApiResponse<object>.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400);
}
try
{
// Use a single DbContext for the entire operation to ensure consistency.
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
// Step 1: Perform initial permission checks in parallel.
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployeeId);
if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
{
_logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get contact profile due to lack of permissions.", loggedInEmployeeId);
return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to view contact.", 403);
}
Contact? contact = await dbContext.Contacts
.AsNoTracking() // Use AsNoTracking for read-only operations to improve performance.
.Include(c => c.ContactCategory)
.Include(c => c.CreatedBy)
.Include(c => c.UpdatedBy)
.FirstOrDefaultAsync(c => c.Id == id && c.IsActive && c.TenantId == tenantId);
if (contact == null)
{
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} tries to update contact with ID {ContactId} is not found in database", LoggedInEmployee.Id);
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} tries to update contact with ID {ContactId} is not found in database", loggedInEmployeeId);
return ApiResponse<object>.ErrorResponse("Contact not found", "Contact not found", 404);
}
ContactProfileVM contactVM = contact.ToContactProfileVMFromContact();
DirectoryUpdateLog? updateLog = await _context.DirectoryUpdateLogs.Include(l => l.Employee).Where(l => l.RefereanceId == contact.Id).OrderByDescending(l => l.UpdateAt).FirstOrDefaultAsync();
if (updateLog != null)
{
contactVM.UpdatedAt = updateLog.UpdateAt;
contactVM.UpdatedBy = updateLog.Employee != null ? updateLog.Employee.ToBasicEmployeeVMFromEmployee() : null;
}
ContactProfileVM contactVM = _mapper.Map<ContactProfileVM>(contact);
List<ContactPhone>? phones = await _context.ContactsPhones.Where(p => p.ContactId == contact.Id).ToListAsync();
if (phones.Any())
var phonesTask = Task.Run(async () =>
{
List<ContactPhoneVM>? phoneVMs = new List<ContactPhoneVM>();
foreach (var phone in phones)
{
ContactPhoneVM phoneVM = phone.ToContactPhoneVMFromContactPhone();
phoneVMs.Add(phoneVM);
}
contactVM.ContactPhones = phoneVMs;
}
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
return await taskDbContext.ContactsPhones
.AsNoTracking()
.Where(p => p.ContactId == contact.Id && p.TenantId == tenantId)
.Select(p => _mapper.Map<ContactPhoneVM>(p))
.ToListAsync();
});
List<ContactEmail>? emails = await _context.ContactsEmails.Where(e => e.ContactId == contact.Id).ToListAsync();
if (emails.Any())
var emailsTask = Task.Run(async () =>
{
List<ContactEmailVM>? emailVMs = new List<ContactEmailVM>();
foreach (var email in emails)
{
ContactEmailVM emailVM = email.ToContactEmailVMFromContactEmail();
emailVMs.Add(emailVM);
}
contactVM.ContactEmails = emailVMs;
}
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
return await taskDbContext.ContactsEmails
.AsNoTracking()
.Where(e => e.ContactId == contact.Id && e.TenantId == tenantId)
.Select(e => _mapper.Map<ContactEmailVM>(e))
.ToListAsync();
});
List<ContactProjectMapping>? contactProjects = await _context.ContactProjectMappings.Where(cp => cp.ContactId == contact.Id).ToListAsync();
if (contactProjects.Any())
var contactProjectsTask = Task.Run(async () =>
{
List<Guid> projectIds = contactProjects.Select(cp => cp.ProjectId).ToList();
List<Project>? projects = await _context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).ToListAsync();
List<BasicProjectVM>? projectVMs = new List<BasicProjectVM>();
foreach (var project in projects)
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
return await taskDbContext.ContactProjectMappings
.AsNoTracking()
.Include(cp => cp.Project)
.Where(cp => cp.ContactId == contact.Id && cp.Project != null && cp.Project.TenantId == tenantId)
.Select(cp => new BasicProjectVM
{
BasicProjectVM projectVM = new BasicProjectVM
Id = cp.Project!.Id,
Name = cp.Project.Name
})
.ToListAsync();
});
var contactBucketsTask = Task.Run(async () =>
{
Id = project.Id,
Name = project.Name
};
projectVMs.Add(projectVM);
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
var bucketQuery = taskDbContext.ContactBucketMappings
.AsNoTracking()
.Include(cb => cb.Bucket)
.ThenInclude(b => b!.CreatedBy)
.Where(cb => cb.ContactId == contact.Id && cb.Bucket != null && cb.Bucket.TenantId == tenantId);
if (hasAdminPermission)
{
return await bucketQuery
.Select(cb => _mapper.Map<BucketVM>(cb.Bucket))
.ToListAsync();
}
contactVM.Projects = projectVMs;
}
List<ContactBucketMapping>? contactBuckets = await _context.ContactBucketMappings.Where(cb => cb.ContactId == contact.Id).ToListAsync();
List<EmployeeBucketMapping>? employeeBuckets = await _context.EmployeeBucketMappings.Where(eb => eb.EmployeeId == LoggedInEmployee.Id).ToListAsync();
if (contactBuckets.Any() && employeeBuckets.Any())
List<Guid> employeeBucketIds = await taskDbContext.EmployeeBucketMappings
.AsNoTracking()
.Where(eb => eb.EmployeeId == loggedInEmployeeId)
.Select(eb => eb.BucketId)
.ToListAsync();
return await bucketQuery
.Where(cb => employeeBucketIds.Contains(cb.BucketId))
.Select(cb => _mapper.Map<BucketVM>(cb.Bucket))
.ToListAsync();
});
var contactTagsTask = Task.Run(async () =>
{
List<Guid> contactBucketIds = contactBuckets.Select(cb => cb.BucketId).ToList();
List<Guid> employeeBucketIds = employeeBuckets.Select(eb => eb.BucketId).ToList();
List<Bucket>? buckets = await _context.Buckets.Where(b => contactBucketIds.Contains(b.Id) && employeeBucketIds.Contains(b.Id)).ToListAsync();
List<BucketVM>? bucketVMs = new List<BucketVM>();
foreach (var bucket in buckets)
{
BucketVM bucketVM = bucket.ToBucketVMFromBucket();
bucketVMs.Add(bucketVM);
}
contactVM.Buckets = bucketVMs;
}
List<ContactTagMapping>? contactTags = await _context.ContactTagMappings.Where(ct => ct.ContactId == contact.Id).ToListAsync();
if (contactTags.Any())
{
List<Guid> tagIds = contactTags.Select(ct => ct.ContactTagId).ToList();
List<ContactTagMaster> tagMasters = await _context.ContactTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync();
List<ContactTagVM> tagVMs = new List<ContactTagVM>();
foreach (var tagMaster in tagMasters)
{
ContactTagVM tagVM = tagMaster.ToContactTagVMFromContactTagMaster();
tagVMs.Add(tagVM);
}
contactVM.Tags = tagVMs;
}
List<ContactNote>? notes = await _context.ContactNotes.Where(n => n.ContactId == contact.Id && n.IsActive).ToListAsync();
if (notes.Any())
{
List<Guid>? noteIds = notes.Select(n => n.Id).ToList();
List<DirectoryUpdateLog>? noteUpdateLogs = await _context.DirectoryUpdateLogs.Include(l => l.Employee).Where(l => noteIds.Contains(l.RefereanceId)).OrderByDescending(l => l.UpdateAt).ToListAsync();
List<ContactNoteVM>? noteVMs = new List<ContactNoteVM>();
foreach (var note in notes)
{
DirectoryUpdateLog? noteUpdateLog = noteUpdateLogs.Where(n => n.RefereanceId == note.Id).OrderByDescending(l => l.UpdateAt).FirstOrDefault();
ContactNoteVM noteVM = note.ToContactNoteVMFromContactNote();
if (noteUpdateLog != null)
{
noteVM.UpdatedAt = noteUpdateLog.UpdateAt;
noteVM.UpdatedBy = noteUpdateLog.Employee != null ? noteUpdateLog.Employee.ToBasicEmployeeVMFromEmployee() : null;
}
noteVMs.Add(noteVM);
}
contactVM.Notes = noteVMs;
}
_logger.LogInfo("Employee ID {EmployeeId} fetched profile of contact {COntactId}", LoggedInEmployee.Id, contact.Id);
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
return await taskDbContext.ContactTagMappings
.AsNoTracking()
.Include(ct => ct.ContactTag)
.Where(ct => ct.ContactId == contact.Id && ct.ContactTag != null && ct.ContactTag.TenantId == tenantId)
.Select(ct => _mapper.Map<ContactTagVM>(ct.ContactTag))
.ToListAsync();
});
await Task.WhenAll(phonesTask, emailsTask, contactProjectsTask, contactBucketsTask, contactTagsTask);
contactVM.ContactPhones = phonesTask.Result;
contactVM.ContactEmails = emailsTask.Result;
contactVM.Tags = contactTagsTask.Result;
contactVM.Buckets = contactBucketsTask.Result;
contactVM.Projects = contactProjectsTask.Result;
_logger.LogInfo("Employee ID {EmployeeId} fetched profile of contact {COntactId}", loggedInEmployeeId, contact.Id);
return ApiResponse<object>.SuccessResponse(contactVM, "Contact profile fetched successfully");
}
_logger.LogInfo("Employee ID {EmployeeId} sent an empty contact id", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Contact ID is empty", "Contact ID is empty", 400);
catch (Exception ex)
{
_logger.LogError(ex, "An unexpected error occurred while fetching contact list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployeeId);
return ApiResponse<object>.ErrorResponse("An internal error occurred.", ExceptionMapper(ex), 500);
}
}
public async Task<ApiResponse<object>> GetOrganizationList()
{
@ -1708,6 +1707,21 @@ namespace Marco.Pms.Services.Service
#region =================================================================== Helper Functions ===================================================================
private async Task<(bool hasAdmin, bool hasManager, bool hasUser)> CheckPermissionsAsync(Guid employeeId)
{
// Scoping the service provider ensures services are disposed of correctly.
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
// Run all permission checks in parallel.
var hasAdminTask = permissionService.HasPermission(PermissionsMaster.DirectoryAdmin, employeeId);
var hasManagerTask = permissionService.HasPermission(PermissionsMaster.DirectoryManager, employeeId);
var hasUserTask = permissionService.HasPermission(PermissionsMaster.DirectoryUser, employeeId);
await Task.WhenAll(hasAdminTask, hasManagerTask, hasUserTask);
return (hasAdminTask.Result, hasManagerTask.Result, hasUserTask.Result);
}
private bool Compare(string sentence, string search)
{
sentence = sentence.Trim().ToLower();

View File

@ -9,7 +9,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> GetListOfContactsAsync(string? search, string? filter, Guid? projectId, bool active, int pageSize, int pageNumber, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetListOfContactsOld(string? search, bool active, ContactFilterDto? filterDto, Guid? projectId);
Task<ApiResponse<object>> GetContactsListByBucketId(Guid id);
Task<ApiResponse<object>> GetContactProfile(Guid id);
Task<ApiResponse<object>> GetContactProfileAsync(Guid id, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetOrganizationList();
Task<ApiResponse<object>> CreateContact(CreateContactDto createContact);
Task<ApiResponse<object>> UpdateContact(Guid id, UpdateContactDto updateContact);