|
|
|
@ -12,6 +12,7 @@ using Marco.Pms.Model.ViewModels.Projects;
|
|
|
|
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
|
|
|
|
using MarcoBMS.Services.Helpers;
|
|
|
|
|
using MarcoBMS.Services.Service;
|
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
|
|
|
@ -382,6 +383,129 @@ namespace Marco.Pms.Services.Service
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(list, System.String.Format("{0} contacts fetched successfully", list.Count), 200);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
public async Task<ApiResponse<object>> GetContactsListByBucketIdAsync(Guid bucketId, Guid tenantId, Employee loggedInEmployee)
|
|
|
|
|
{
|
|
|
|
|
if (bucketId == Guid.Empty)
|
|
|
|
|
{
|
|
|
|
|
_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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(loggedInEmployee.Id);
|
|
|
|
|
|
|
|
|
|
if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
|
|
|
|
|
{
|
|
|
|
|
// Log the specific denial reason for security auditing.
|
|
|
|
|
_logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get organization list for tenant {TenantId} due to lack of permissions.", loggedInEmployee.Id, tenantId);
|
|
|
|
|
// Return a strongly-typed error response.
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to perform this action.", 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Bucket? bucket = await _context.Buckets.FirstOrDefaultAsync(b => b.Id == bucketId && b.TenantId == tenantId);
|
|
|
|
|
if (bucket == null)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInfo("Employee ID {EmployeeId} attempted access to bucket ID {BucketId}, but not found in database", loggedInEmployee.Id);
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("Bucket not found", "Bucket not found", 404);
|
|
|
|
|
}
|
|
|
|
|
List<EmployeeBucketMapping>? employeeBuckets = await _context.EmployeeBucketMappings.Where(em => em.BucketId == bucketId).ToListAsync();
|
|
|
|
|
|
|
|
|
|
EmployeeBucketMapping? employeeBucket = null;
|
|
|
|
|
if (hasAdminPermission)
|
|
|
|
|
{
|
|
|
|
|
employeeBucket = employeeBuckets.FirstOrDefault();
|
|
|
|
|
}
|
|
|
|
|
else if (hasManagerPermission || hasUserPermission)
|
|
|
|
|
{
|
|
|
|
|
employeeBucket = employeeBuckets.FirstOrDefault(eb => eb.EmployeeId == loggedInEmployee.Id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (employeeBucket == null)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInfo("Employee ID {EmployeeId} does not have access to bucket ID {BucketId}", loggedInEmployee.Id);
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("You do not have access to this bucket.", "You do not have access to this bucket.", 401);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<ContactBucketMapping> contactBucket = await _context.ContactBucketMappings.Where(cb => cb.BucketId == bucketId).ToListAsync() ?? new List<ContactBucketMapping>();
|
|
|
|
|
List<ContactVM> contactVMs = new List<ContactVM>();
|
|
|
|
|
if (contactBucket.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var contactIds = contactBucket.Select(cb => cb.ContactId).ToList();
|
|
|
|
|
List<Contact> contacts = await _context.Contacts.Include(c => c.ContactCategory).Where(c => contactIds.Contains(c.Id) && c.IsActive).ToListAsync();
|
|
|
|
|
List<ContactPhone> phones = await _context.ContactsPhones.Where(p => contactIds.Contains(p.ContactId)).ToListAsync();
|
|
|
|
|
List<ContactEmail> emails = await _context.ContactsEmails.Where(e => contactIds.Contains(e.ContactId)).ToListAsync();
|
|
|
|
|
|
|
|
|
|
List<ContactTagMapping>? tags = await _context.ContactTagMappings.Where(ct => contactIds.Contains(ct.ContactId)).ToListAsync();
|
|
|
|
|
List<ContactProjectMapping>? contactProjects = await _context.ContactProjectMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync();
|
|
|
|
|
List<ContactBucketMapping>? contactBuckets = await _context.ContactBucketMappings.Where(cp => contactIds.Contains(cp.ContactId)).ToListAsync();
|
|
|
|
|
|
|
|
|
|
List<Guid> tagIds = new List<Guid>();
|
|
|
|
|
List<ContactTagMaster> tagMasters = new List<ContactTagMaster>();
|
|
|
|
|
if (tags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
tagIds = tags.Select(ct => ct.ContactTagId).ToList();
|
|
|
|
|
tagMasters = await _context.ContactTagMasters.Where(t => tagIds.Contains(t.Id)).ToListAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (contacts.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var contact in contacts)
|
|
|
|
|
{
|
|
|
|
|
List<ContactEmailVM>? emailVMs = new List<ContactEmailVM>();
|
|
|
|
|
List<ContactPhoneVM>? phoneVMs = new List<ContactPhoneVM>();
|
|
|
|
|
List<ContactTagVM>? tagVMs = new List<ContactTagVM>();
|
|
|
|
|
|
|
|
|
|
List<ContactPhone> contactPhones = phones.Where(p => p.ContactId == contact.Id).ToList();
|
|
|
|
|
List<ContactEmail> contactEmails = emails.Where(e => e.ContactId == contact.Id).ToList();
|
|
|
|
|
|
|
|
|
|
List<ContactTagMapping>? contactTags = tags.Where(t => t.ContactId == contact.Id).ToList();
|
|
|
|
|
List<ContactProjectMapping>? projectMappings = contactProjects.Where(cp => cp.ContactId == contact.Id).ToList();
|
|
|
|
|
List<ContactBucketMapping>? bucketMappings = contactBuckets.Where(cb => cb.ContactId == contact.Id).ToList();
|
|
|
|
|
|
|
|
|
|
if (contactPhones.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var phone in contactPhones)
|
|
|
|
|
{
|
|
|
|
|
ContactPhoneVM phoneVM = phone.ToContactPhoneVMFromContactPhone();
|
|
|
|
|
phoneVMs.Add(phoneVM);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (contactEmails.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var email in contactEmails)
|
|
|
|
|
{
|
|
|
|
|
ContactEmailVM emailVM = email.ToContactEmailVMFromContactEmail();
|
|
|
|
|
emailVMs.Add(emailVM);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (contactTags.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var contactTag in contactTags)
|
|
|
|
|
{
|
|
|
|
|
ContactTagMaster? tagMaster = tagMasters.Find(t => t.Id == contactTag.ContactTagId);
|
|
|
|
|
if (tagMaster != null)
|
|
|
|
|
{
|
|
|
|
|
ContactTagVM tagVM = tagMaster.ToContactTagVMFromContactTagMaster();
|
|
|
|
|
tagVMs.Add(tagVM);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ContactVM contactVM = contact.ToContactVMFromContact();
|
|
|
|
|
contactVM.ContactEmails = emailVMs;
|
|
|
|
|
contactVM.ContactPhones = phoneVMs;
|
|
|
|
|
contactVM.Tags = tagVMs;
|
|
|
|
|
contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList();
|
|
|
|
|
contactVM.BucketIds = bucketMappings.Select(cb => cb.BucketId).ToList();
|
|
|
|
|
contactVMs.Add(contactVM);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
_logger.LogInfo("{count} contact from Bucket {BucketId} fetched by Employee {EmployeeId}", contactVMs.Count, bucketId, loggedInEmployee.Id);
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(contactVMs, $"{contactVMs.Count} contacts fetched successfully", 200);
|
|
|
|
|
}
|
|
|
|
|
public async Task<ApiResponse<object>> GetContactsListByBucketId(Guid id)
|
|
|
|
|
{
|
|
|
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
|
|
@ -611,192 +735,199 @@ namespace Marco.Pms.Services.Service
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(phonesTask, emailsTask, contactProjectsTask, contactBucketsTask, contactTagsTask);
|
|
|
|
|
var contactNotesTask = Task.Run(async () =>
|
|
|
|
|
{
|
|
|
|
|
await using var taskDbContext = await _dbContextFactory.CreateDbContextAsync();
|
|
|
|
|
return await taskDbContext.ContactNotes
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.Include(cn => cn.Createdby)
|
|
|
|
|
.Include(cn => cn.UpdatedBy)
|
|
|
|
|
.Include(cn => cn.Contact)
|
|
|
|
|
.Where(cn => cn.ContactId == contact.Id && cn.Createdby != null && cn.Createdby.TenantId == tenantId)
|
|
|
|
|
.Select(cn => _mapper.Map<ContactNoteVM>(cn))
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(phonesTask, emailsTask, contactProjectsTask, contactBucketsTask, contactTagsTask, contactNotesTask);
|
|
|
|
|
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);
|
|
|
|
|
contactVM.Notes = contactNotesTask.Result;
|
|
|
|
|
_logger.LogInfo("Employee ID {EmployeeId} fetched profile of contact {ContactId}", loggedInEmployeeId, contact.Id);
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(contactVM, "Contact profile fetched successfully");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "An unexpected error occurred while fetching contact list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployeeId);
|
|
|
|
|
_logger.LogError(ex, "An unexpected error occurred while fetching contact profile 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()
|
|
|
|
|
{
|
|
|
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
|
|
|
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
|
|
|
|
|
|
var organizationList = await _context.Contacts.Where(c => c.TenantId == tenantId).Select(c => c.Organization).Distinct().ToListAsync();
|
|
|
|
|
_logger.LogInfo("Employee {EmployeeId} fetched list of organizations in a tenant {TenantId}", LoggedInEmployee.Id, tenantId);
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(organizationList, $"{organizationList.Count} records of organization names fetched from contacts", 200);
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Asynchronously retrieves a distinct list of organization names for a given tenant.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="tenantId">The unique identifier of the tenant.</param>
|
|
|
|
|
/// <param name="loggedInEmployee">The employee making the request, used for permission checks.</param>
|
|
|
|
|
/// <returns>
|
|
|
|
|
/// An ApiResponse containing the list of organization names on success,
|
|
|
|
|
/// or an error response with appropriate status codes (403 for Forbidden, 500 for internal errors).
|
|
|
|
|
/// </returns>
|
|
|
|
|
public async Task<ApiResponse<object>> GetOrganizationListAsync(Guid tenantId, Employee loggedInEmployee)
|
|
|
|
|
{
|
|
|
|
|
// --- Parameter Validation ---
|
|
|
|
|
// Fail fast if essential parameters are not provided.
|
|
|
|
|
ArgumentNullException.ThrowIfNull(loggedInEmployee);
|
|
|
|
|
|
|
|
|
|
var employeeId = loggedInEmployee.Id;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// --- 1. Permission Check ---
|
|
|
|
|
// Verify that the employee has at least one of the required permissions to view this data.
|
|
|
|
|
// This prevents unauthorized data access early in the process.
|
|
|
|
|
var (hasAdminPermission, hasManagerPermission, hasUserPermission) = await CheckPermissionsAsync(employeeId);
|
|
|
|
|
|
|
|
|
|
if (!hasAdminPermission && !hasManagerPermission && !hasUserPermission)
|
|
|
|
|
{
|
|
|
|
|
// Log the specific denial reason for security auditing.
|
|
|
|
|
_logger.LogWarning("Access DENIED for employee {EmployeeId} attempting to get organization list for tenant {TenantId} due to lack of permissions.", employeeId, tenantId);
|
|
|
|
|
// Return a strongly-typed error response.
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to perform this action.", 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 2. Database Query ---
|
|
|
|
|
// Build and execute the database query efficiently.
|
|
|
|
|
_logger.LogDebug("Fetching organization list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, employeeId);
|
|
|
|
|
|
|
|
|
|
var organizationList = await _context.Contacts
|
|
|
|
|
// Filter contacts by the specified tenant to ensure data isolation and Filter out contacts that do not have an organization name to ensure data quality.
|
|
|
|
|
.Where(c => c.TenantId == tenantId && !string.IsNullOrEmpty(c.Organization))
|
|
|
|
|
|
|
|
|
|
// Project only the 'Organization' column. This is a major performance optimization
|
|
|
|
|
// as it avoids loading entire 'Contact' entities into memory.
|
|
|
|
|
.Select(c => c.Organization)
|
|
|
|
|
// Let the database perform the distinct operation, which is highly efficient.
|
|
|
|
|
.Distinct()
|
|
|
|
|
// Execute the query asynchronously and materialize the results into a list.
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
// --- 3. Success Response ---
|
|
|
|
|
// Log the successful operation with key details.
|
|
|
|
|
_logger.LogInfo("Successfully fetched {OrganizationCount} distinct organizations for Tenant {TenantId} for employee {EmployeeId}", organizationList.Count, tenantId, employeeId);
|
|
|
|
|
|
|
|
|
|
// Return a strongly-typed success response with the data and a descriptive message.
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(
|
|
|
|
|
organizationList,
|
|
|
|
|
$"{organizationList.Count} unique organization(s) found.",
|
|
|
|
|
200
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
// --- 4. Exception Handling ---
|
|
|
|
|
// Log the full exception details for effective debugging, including context.
|
|
|
|
|
_logger.LogError(ex, "An unexpected error occurred while fetching organization list for Tenant {TenantId} by Employee {EmployeeId}", tenantId, employeeId);
|
|
|
|
|
|
|
|
|
|
// Return a generic, strongly-typed error response to the client to avoid leaking implementation details.
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("An internal server error occurred.", ExceptionMapper(ex), 500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region =================================================================== Contact Post APIs ===================================================================
|
|
|
|
|
public async Task<ApiResponse<object>> CreateContact(CreateContactDto createContact)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new contact along with its associated details such as phone numbers, emails, tags, and project/bucket mappings.
|
|
|
|
|
/// This operation is performed within a single database transaction to ensure data integrity.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="createContact">The DTO containing the details for the new contact.</param>
|
|
|
|
|
/// <param name="tenantId">The ID of the tenant to which the contact belongs.</param>
|
|
|
|
|
/// <param name="loggedInEmployee">The employee performing the action.</param>
|
|
|
|
|
/// <returns>An ApiResponse containing the newly created contact's view model or an error.</returns>
|
|
|
|
|
public async Task<ApiResponse<object>> CreateContactAsync(CreateContactDto createContact, Guid tenantId, Employee loggedInEmployee)
|
|
|
|
|
{
|
|
|
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
|
|
|
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
|
|
|
|
if (createContact != null)
|
|
|
|
|
Guid loggedInEmployeeId = loggedInEmployee.Id;
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(createContact.Name) ||
|
|
|
|
|
string.IsNullOrWhiteSpace(createContact.Organization) ||
|
|
|
|
|
!(createContact.BucketIds?.Any() ?? false))
|
|
|
|
|
{
|
|
|
|
|
List<ContactPhone> phones = new List<ContactPhone>();
|
|
|
|
|
List<ContactEmail> emails = new List<ContactEmail>();
|
|
|
|
|
List<ContactBucketMapping> contactBucketMappings = new List<ContactBucketMapping>();
|
|
|
|
|
List<ContactTagMapping> contactTagMappings = new List<ContactTagMapping>();
|
|
|
|
|
_logger.LogWarning("Validation failed for CreateContactAsync. Payload missing required fields. Triggered by Employee: {LoggedInEmployeeId}", loggedInEmployeeId);
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("Payload is missing required fields: Name, Organization, and at least one BucketId are required.", "Invalid Payload", 400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Contact? contact = createContact.ToContactFromCreateContactDto(tenantId, LoggedInEmployee.Id);
|
|
|
|
|
using var transaction = await _context.Database.BeginTransactionAsync();
|
|
|
|
|
_logger.LogInfo("Starting transaction to create contact for Tenant {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployeeId);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var contact = _mapper.Map<Contact>(createContact);
|
|
|
|
|
contact.CreatedAt = DateTime.UtcNow;
|
|
|
|
|
contact.CreatedById = loggedInEmployeeId;
|
|
|
|
|
contact.TenantId = tenantId;
|
|
|
|
|
_context.Contacts.Add(contact);
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
_logger.LogInfo("Contact with ID {ContactId} created by Employee with ID {LoggedInEmployeeId}", contact.Id, LoggedInEmployee.Id);
|
|
|
|
|
|
|
|
|
|
var tags = await _context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync();
|
|
|
|
|
var tagNames = tags.Select(t => t.Name.ToLower()).ToList();
|
|
|
|
|
var buckets = await _context.Buckets.Where(b => b.TenantId == tenantId).Select(b => b.Id).ToListAsync();
|
|
|
|
|
var projects = await _context.Projects.Where(p => p.TenantId == tenantId).Select(p => p.Id).ToListAsync();
|
|
|
|
|
// --- Process Phones ---
|
|
|
|
|
var existingPhoneList = await _context.ContactsPhones
|
|
|
|
|
.Where(p => p.TenantId == tenantId && p.IsPrimary)
|
|
|
|
|
.Select(p => p.PhoneNumber)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
var existingPhones = new HashSet<string>(existingPhoneList);
|
|
|
|
|
var phoneVMs = ProcessContactPhones(createContact, contact, existingPhones);
|
|
|
|
|
|
|
|
|
|
if (createContact.ContactPhones != null)
|
|
|
|
|
// --- Process Emails ---
|
|
|
|
|
var existingEmailList = await _context.ContactsEmails
|
|
|
|
|
.Where(e => e.TenantId == tenantId && e.IsPrimary)
|
|
|
|
|
.Select(e => e.EmailAddress)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
var existingEmails = new HashSet<string>(existingEmailList, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
var emailVMs = ProcessContactEmails(createContact, contact, existingEmails);
|
|
|
|
|
|
|
|
|
|
// --- Process Tags ---
|
|
|
|
|
var tenantTags = await _context.ContactTagMasters
|
|
|
|
|
.Where(t => t.TenantId == tenantId)
|
|
|
|
|
.ToDictionaryAsync(t => t.Name.ToLowerInvariant(), t => t);
|
|
|
|
|
var tagVMs = ProcessTags(createContact, contact, tenantTags);
|
|
|
|
|
|
|
|
|
|
// --- Process Mappings ---
|
|
|
|
|
var contactBucketMappings = await ProcessBucketMappingsAsync(createContact, contact, tenantId);
|
|
|
|
|
var projectMappings = await ProcessProjectMappingsAsync(createContact, contact, tenantId);
|
|
|
|
|
|
|
|
|
|
// --- Final Save and Commit ---
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var changesCount = await _context.SaveChangesAsync();
|
|
|
|
|
await transaction.CommitAsync();
|
|
|
|
|
|
|
|
|
|
foreach (var contactPhone in createContact.ContactPhones)
|
|
|
|
|
{
|
|
|
|
|
ContactPhone phone = contactPhone.ToContactPhoneFromCreateContactPhoneDto(tenantId, contact.Id);
|
|
|
|
|
phones.Add(phone);
|
|
|
|
|
}
|
|
|
|
|
_context.ContactsPhones.AddRange(phones);
|
|
|
|
|
_logger.LogInfo("{count} phone number are saved in contact with ID {ContactId} by employee with ID {LoggedEmployeeId}", phones.Count, contact.Id, LoggedInEmployee.Id);
|
|
|
|
|
_logger.LogInfo(
|
|
|
|
|
"Successfully created Contact {ContactId} with {Count} related records for Tenant {TenantId} by Employee {EmployeeId}. Transaction committed.",
|
|
|
|
|
contact.Id, changesCount - 1, tenantId, loggedInEmployeeId);
|
|
|
|
|
}
|
|
|
|
|
if (createContact.ContactEmails != null)
|
|
|
|
|
catch (DbUpdateException dbEx)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
foreach (var contactEmail in createContact.ContactEmails)
|
|
|
|
|
{
|
|
|
|
|
ContactEmail email = contactEmail.ToContactEmailFromCreateContactEmailDto(tenantId, contact.Id);
|
|
|
|
|
emails.Add(email);
|
|
|
|
|
}
|
|
|
|
|
_context.ContactsEmails.AddRange(emails);
|
|
|
|
|
_logger.LogInfo("{count} email addresses are saved in contact with ID {ContactId} by employee with ID {LoggedEmployeeId}", emails.Count, contact.Id, LoggedInEmployee.Id);
|
|
|
|
|
await transaction.RollbackAsync();
|
|
|
|
|
_logger.LogError(dbEx, "Database exception during contact creation for Tenant {TenantId}. Transaction rolled back.", tenantId);
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("An internal database error occurred.", ExceptionMapper(dbEx), 500);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (createContact.BucketIds != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var bucket in createContact.BucketIds)
|
|
|
|
|
{
|
|
|
|
|
if (buckets.Contains(bucket))
|
|
|
|
|
{
|
|
|
|
|
ContactBucketMapping bucketMapping = new ContactBucketMapping
|
|
|
|
|
{
|
|
|
|
|
BucketId = bucket,
|
|
|
|
|
ContactId = contact.Id
|
|
|
|
|
};
|
|
|
|
|
contactBucketMappings.Add(bucketMapping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_context.ContactBucketMappings.AddRange(contactBucketMappings);
|
|
|
|
|
_logger.LogInfo("Contact with ID {ContactId} added to {count} number of buckets by employee with ID {LoggedEmployeeId}", contact.Id, contactBucketMappings.Count, LoggedInEmployee.Id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (createContact.ProjectIds != null)
|
|
|
|
|
{
|
|
|
|
|
List<ContactProjectMapping> projectMappings = new List<ContactProjectMapping>();
|
|
|
|
|
foreach (var projectId in createContact.ProjectIds)
|
|
|
|
|
{
|
|
|
|
|
if (projects.Contains(projectId))
|
|
|
|
|
{
|
|
|
|
|
ContactProjectMapping projectMapping = new ContactProjectMapping
|
|
|
|
|
{
|
|
|
|
|
ProjectId = projectId,
|
|
|
|
|
ContactId = contact.Id,
|
|
|
|
|
TenantId = tenantId
|
|
|
|
|
};
|
|
|
|
|
projectMappings.Add(projectMapping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_context.ContactProjectMappings.AddRange(projectMappings);
|
|
|
|
|
_logger.LogInfo("Contact with ID {ContactId} added to {count} number of project by employee with ID {LoggedEmployeeId}", contact.Id, projectMappings.Count, LoggedInEmployee.Id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (createContact.Tags != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var tag in createContact.Tags)
|
|
|
|
|
{
|
|
|
|
|
if (tagNames.Contains(tag.Name.ToLower()))
|
|
|
|
|
{
|
|
|
|
|
ContactTagMaster existingTag = tags.Find(t => t.Name == tag.Name) ?? new ContactTagMaster();
|
|
|
|
|
_context.ContactTagMappings.Add(new ContactTagMapping
|
|
|
|
|
{
|
|
|
|
|
ContactId = contact.Id,
|
|
|
|
|
ContactTagId = tag.Id ?? existingTag.Id
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else if (tag.Id == null || tags.Where(t => t.Name == tag.Name) == null)
|
|
|
|
|
{
|
|
|
|
|
var newtag = new ContactTagMaster
|
|
|
|
|
{
|
|
|
|
|
Name = tag.Name,
|
|
|
|
|
TenantId = tenantId
|
|
|
|
|
};
|
|
|
|
|
_context.ContactTagMasters.Add(newtag);
|
|
|
|
|
ContactTagMapping tagMapping = new ContactTagMapping
|
|
|
|
|
{
|
|
|
|
|
ContactTagId = newtag.Id,
|
|
|
|
|
ContactId = contact.Id
|
|
|
|
|
};
|
|
|
|
|
contactTagMappings.Add(tagMapping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_context.ContactTagMappings.AddRange(contactTagMappings);
|
|
|
|
|
_logger.LogInfo("{count} number of tags added to Contact with ID {ContactId} by employee with ID {LoggedEmployeeId}", contactTagMappings.Count, contact.Id, LoggedInEmployee.Id);
|
|
|
|
|
}
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
ContactVM contactVM = new ContactVM();
|
|
|
|
|
List<ContactPhoneVM> phoneVMs = new List<ContactPhoneVM>();
|
|
|
|
|
|
|
|
|
|
contact = await _context.Contacts.Include(c => c.ContactCategory).FirstOrDefaultAsync(c => c.Id == contact.Id) ?? new Contact();
|
|
|
|
|
var tagIds = contactTagMappings.Select(t => t.ContactTagId).ToList();
|
|
|
|
|
tags = await _context.ContactTagMasters.Where(t => t.TenantId == tenantId && tagIds.Contains(t.Id)).ToListAsync();
|
|
|
|
|
List<ContactProjectMapping> contactProjects = await _context.ContactProjectMappings.Where(cp => cp.ContactId == contact.Id).ToListAsync();
|
|
|
|
|
List<ContactBucketMapping> bucketMappings = await _context.ContactBucketMappings.Where(cb => cb.ContactId == contact.Id).ToListAsync();
|
|
|
|
|
foreach (var phone in phones)
|
|
|
|
|
{
|
|
|
|
|
ContactPhoneVM phoneVM = phone.ToContactPhoneVMFromContactPhone();
|
|
|
|
|
phoneVMs.Add(phoneVM);
|
|
|
|
|
}
|
|
|
|
|
List<ContactEmailVM> emailVMs = new List<ContactEmailVM>();
|
|
|
|
|
foreach (var email in emails)
|
|
|
|
|
{
|
|
|
|
|
ContactEmailVM emailVM = email.ToContactEmailVMFromContactEmail();
|
|
|
|
|
emailVMs.Add(emailVM);
|
|
|
|
|
}
|
|
|
|
|
List<ContactTagVM> tagVMs = new List<ContactTagVM>();
|
|
|
|
|
foreach (var contactTagMapping in contactTagMappings)
|
|
|
|
|
{
|
|
|
|
|
ContactTagVM tagVM = new ContactTagVM();
|
|
|
|
|
var tag = tags.Find(t => t.Id == contactTagMapping.ContactTagId);
|
|
|
|
|
tagVM = tag != null ? tag.ToContactTagVMFromContactTagMaster() : new ContactTagVM();
|
|
|
|
|
tagVMs.Add(tagVM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
contactVM = contact.ToContactVMFromContact();
|
|
|
|
|
// --- Construct and Return Response ---
|
|
|
|
|
var contactVM = _mapper.Map<ContactVM>(contact);
|
|
|
|
|
contactVM.ContactPhones = phoneVMs;
|
|
|
|
|
contactVM.ContactEmails = emailVMs;
|
|
|
|
|
contactVM.Tags = tagVMs;
|
|
|
|
|
contactVM.ProjectIds = contactProjects.Select(cp => cp.ProjectId).ToList();
|
|
|
|
|
contactVM.BucketIds = bucketMappings.Select(cb => cb.BucketId).ToList();
|
|
|
|
|
contactVM.BucketIds = contactBucketMappings.Select(cb => cb.BucketId).ToList();
|
|
|
|
|
contactVM.ProjectIds = projectMappings.Select(cp => cp.ProjectId).ToList();
|
|
|
|
|
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(contactVM, "Contact Created Successfully", 200);
|
|
|
|
|
return ApiResponse<object>.SuccessResponse(contactVM, "Contact created successfully.", 201);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "An unexpected exception occurred during contact creation for Tenant {TenantId}. Transaction rolled back.", tenantId);
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("An unexpected internal error occurred.", ExceptionMapper(ex), 500);
|
|
|
|
|
}
|
|
|
|
|
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id);
|
|
|
|
|
return ApiResponse<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
@ -1787,6 +1918,118 @@ namespace Marco.Pms.Services.Service
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Helper Methods for Readability and Separation of Concerns ---
|
|
|
|
|
|
|
|
|
|
private List<ContactPhoneVM> ProcessContactPhones(CreateContactDto dto, Contact contact, ISet<string> existingPhones)
|
|
|
|
|
{
|
|
|
|
|
if (!(dto.ContactPhones?.Any() ?? false)) return new List<ContactPhoneVM>();
|
|
|
|
|
|
|
|
|
|
var newPhones = dto.ContactPhones
|
|
|
|
|
.Where(p => !string.IsNullOrWhiteSpace(p.PhoneNumber) && existingPhones.Add(p.PhoneNumber)) // .Add returns true if the item was added (i.e., not present)
|
|
|
|
|
.Select(pDto =>
|
|
|
|
|
{
|
|
|
|
|
var phone = _mapper.Map<ContactPhone>(pDto);
|
|
|
|
|
phone.ContactId = contact.Id;
|
|
|
|
|
phone.TenantId = contact.TenantId; // Ensure tenant is set on child entities
|
|
|
|
|
return phone;
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
_context.ContactsPhones.AddRange(newPhones);
|
|
|
|
|
_logger.LogInfo("Adding {Count} new phone numbers for Contact {ContactId}.", newPhones.Count, contact.Id);
|
|
|
|
|
|
|
|
|
|
return newPhones.Select(p => _mapper.Map<ContactPhoneVM>(p)).ToList();
|
|
|
|
|
}
|
|
|
|
|
private List<ContactEmailVM> ProcessContactEmails(CreateContactDto dto, Contact contact, ISet<string> existingEmails)
|
|
|
|
|
{
|
|
|
|
|
if (!(dto.ContactEmails?.Any() ?? false)) return new List<ContactEmailVM>();
|
|
|
|
|
|
|
|
|
|
var newEmails = dto.ContactEmails
|
|
|
|
|
.Where(e => !string.IsNullOrWhiteSpace(e.EmailAddress) && existingEmails.Add(e.EmailAddress)) // HashSet handles case-insensitivity set in constructor
|
|
|
|
|
.Select(eDto =>
|
|
|
|
|
{
|
|
|
|
|
var email = _mapper.Map<ContactEmail>(eDto);
|
|
|
|
|
email.ContactId = contact.Id;
|
|
|
|
|
email.TenantId = contact.TenantId;
|
|
|
|
|
return email;
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
_context.ContactsEmails.AddRange(newEmails);
|
|
|
|
|
_logger.LogInfo("Adding {Count} new email addresses for Contact {ContactId}.", newEmails.Count, contact.Id);
|
|
|
|
|
|
|
|
|
|
return newEmails.Select(e => _mapper.Map<ContactEmailVM>(e)).ToList();
|
|
|
|
|
}
|
|
|
|
|
private List<ContactTagVM> ProcessTags(CreateContactDto dto, Contact contact, IDictionary<string, ContactTagMaster> tenantTags)
|
|
|
|
|
{
|
|
|
|
|
if (!(dto.Tags?.Any() ?? false)) return new List<ContactTagVM>();
|
|
|
|
|
|
|
|
|
|
var tagVMs = new List<ContactTagVM>();
|
|
|
|
|
var newTagMappings = new List<ContactTagMapping>();
|
|
|
|
|
|
|
|
|
|
foreach (var tagName in dto.Tags.Select(t => t.Name).Distinct(StringComparer.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(tagName)) continue;
|
|
|
|
|
|
|
|
|
|
var normalizedTag = tagName.ToLowerInvariant();
|
|
|
|
|
if (!tenantTags.TryGetValue(normalizedTag, out var tagMaster))
|
|
|
|
|
{
|
|
|
|
|
// Tag does not exist, create it.
|
|
|
|
|
tagMaster = new ContactTagMaster { Name = tagName, Description = tagName, TenantId = contact.TenantId };
|
|
|
|
|
_context.ContactTagMasters.Add(tagMaster);
|
|
|
|
|
tenantTags.Add(normalizedTag, tagMaster); // Add to dictionary to handle duplicates in the input list
|
|
|
|
|
_logger.LogDebug("Creating new tag '{TagName}' for Tenant {TenantId}.", tagName, contact.TenantId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newTagMappings.Add(new ContactTagMapping { ContactId = contact.Id, ContactTag = tagMaster });
|
|
|
|
|
tagVMs.Add(_mapper.Map<ContactTagVM>(tagMaster));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_context.ContactTagMappings.AddRange(newTagMappings);
|
|
|
|
|
_logger.LogInfo("Adding {Count} tag mappings for Contact {ContactId}.", newTagMappings.Count, contact.Id);
|
|
|
|
|
|
|
|
|
|
return tagVMs;
|
|
|
|
|
}
|
|
|
|
|
private async Task<List<ContactBucketMapping>> ProcessBucketMappingsAsync(CreateContactDto dto, Contact contact, Guid tenantId)
|
|
|
|
|
{
|
|
|
|
|
if (!(dto.BucketIds?.Any() ?? false)) return new List<ContactBucketMapping>();
|
|
|
|
|
|
|
|
|
|
var validBucketIds = await _context.Buckets
|
|
|
|
|
.Where(b => dto.BucketIds.Contains(b.Id) && b.TenantId == tenantId)
|
|
|
|
|
.Select(b => b.Id)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
var mappings = validBucketIds.Select(bucketId => new ContactBucketMapping
|
|
|
|
|
{
|
|
|
|
|
BucketId = bucketId,
|
|
|
|
|
ContactId = contact.Id
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
_context.ContactBucketMappings.AddRange(mappings);
|
|
|
|
|
_logger.LogInfo("Adding {Count} bucket mappings for Contact {ContactId}.", mappings.Count, contact.Id);
|
|
|
|
|
|
|
|
|
|
return mappings;
|
|
|
|
|
}
|
|
|
|
|
private async Task<List<ContactProjectMapping>> ProcessProjectMappingsAsync(CreateContactDto dto, Contact contact, Guid tenantId)
|
|
|
|
|
{
|
|
|
|
|
if (!(dto.ProjectIds?.Any() ?? false)) return new List<ContactProjectMapping>();
|
|
|
|
|
|
|
|
|
|
var validProjectIds = await _context.Projects
|
|
|
|
|
.Where(p => dto.ProjectIds.Contains(p.Id) && p.TenantId == tenantId)
|
|
|
|
|
.Select(p => p.Id)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
var mappings = validProjectIds.Select(projectId => new ContactProjectMapping
|
|
|
|
|
{
|
|
|
|
|
ProjectId = projectId,
|
|
|
|
|
ContactId = contact.Id,
|
|
|
|
|
TenantId = tenantId
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
_context.ContactProjectMappings.AddRange(mappings);
|
|
|
|
|
_logger.LogInfo("Adding {Count} project mappings for Contact {ContactId}.", mappings.Count, contact.Id);
|
|
|
|
|
|
|
|
|
|
return mappings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|