using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Helpers.Utility; using Marco.Pms.Model.Directory; using Marco.Pms.Model.DocumentManager; using Marco.Pms.Model.Dtos.DocumentManager; using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Mapper; using Marco.Pms.Model.Master; using Marco.Pms.Model.MongoDBModels.Utility; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.DocumentManager; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Service.ServiceInterfaces; using MarcoBMS.Services.Service; using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; namespace Marco.Pms.Services.Service { public class MasterService : IMasterService { private readonly IDbContextFactory _dbContextFactory; private readonly ApplicationDbContext _context; private readonly ILoggingService _logger; private readonly PermissionServices _permission; private readonly IMapper _mapper; private readonly UtilityMongoDBHelper _updateLogHelper; private readonly CacheUpdateHelper _cache; public MasterService( IDbContextFactory dbContextFactory, ApplicationDbContext context, ILoggingService logger, PermissionServices permission, IMapper mapper, UtilityMongoDBHelper updateLogHelper, CacheUpdateHelper cache) { _dbContextFactory = dbContextFactory; _context = context; _logger = logger; _permission = permission; _mapper = mapper; _updateLogHelper = updateLogHelper; _cache = cache; } #region =================================================================== Contact Category APIs =================================================================== public async Task> CreateContactCategory(CreateContactCategoryDto contactCategoryDto, Employee loggedInEmployee, Guid tenantId) { if (contactCategoryDto != null) { ContactCategoryMaster? existingContactCategory = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Name.ToLower() == (contactCategoryDto.Name != null ? contactCategoryDto.Name.ToLower() : "")); if (existingContactCategory == null) { ContactCategoryMaster contactCategory = contactCategoryDto.ToContactCategoryMasterFromCreateContactCategoryDto(tenantId); _context.ContactCategoryMasters.Add(contactCategory); await _context.SaveChangesAsync(); ContactCategoryVM categoryVM = contactCategory.ToContactCategoryVMFromContactCategoryMaster(); _logger.LogInfo("Employee ID {LoggedInEmployeeId} created a contact category {ContactCategoryId}.", loggedInEmployee.Id, contactCategory.Id); return ApiResponse.SuccessResponse(categoryVM, "Category Created Successfully", 200); } _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to create an existing contact category.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Category already existed", "Category already existed", 409); } _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> UpdateContactCategory(Guid id, UpdateContactCategoryDto contactCategoryDto, Employee loggedInEmployee, Guid tenantId) { if (contactCategoryDto != null && id == contactCategoryDto.Id) { ContactCategoryMaster? contactCategory = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); if (contactCategory != null) { contactCategory.Name = contactCategoryDto.Name ?? ""; contactCategory.Description = contactCategoryDto.Description ?? ""; _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog { RefereanceId = contactCategory.Id, UpdatedById = loggedInEmployee.Id, UpdateAt = DateTime.UtcNow }); await _context.SaveChangesAsync(); ContactCategoryVM categoryVM = contactCategory.ToContactCategoryVMFromContactCategoryMaster(); _logger.LogInfo("Employee ID {LoggedInEmployeeId} created a contact category {ContactCategoryId}.", loggedInEmployee.Id, contactCategory.Id); return ApiResponse.SuccessResponse(categoryVM, "Category Created Successfully", 200); } _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to update a contact category but not found in database.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Category not found", "Category 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> GetContactCategoriesList(Employee loggedInEmployee, Guid tenantId) { var categoryList = await _context.ContactCategoryMasters.Where(c => c.TenantId == tenantId).ToListAsync(); List contactCategories = new List(); foreach (var category in categoryList) { ContactCategoryVM categoryVM = category.ToContactCategoryVMFromContactCategoryMaster(); contactCategories.Add(categoryVM); } _logger.LogInfo("{count} contact categoires are fetched by Employee with ID {LoggedInEmployeeId}", contactCategories.Count, loggedInEmployee.Id); return ApiResponse.SuccessResponse(contactCategories, System.String.Format("{0} contact categories fetched successfully", contactCategories.Count), 200); } public async Task> GetContactCategoryById(Guid id, Employee loggedInEmployee, Guid tenantId) { var category = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); if (category != null) { ContactCategoryVM categoryVM = category.ToContactCategoryVMFromContactCategoryMaster(); _logger.LogInfo("Employee {EmployeeId} fetched contact category {ContactCategoryID}", loggedInEmployee.Id, category.Id); return ApiResponse.SuccessResponse(categoryVM, "Category fetched successfully", 200); } _logger.LogWarning("Employee {EmployeeId} attempted to fetch contact category {ContactCategoryID} but not found in database", loggedInEmployee.Id, id); return ApiResponse.ErrorResponse("Category not found", "Category not found", 404); } public async Task> DeleteContactCategory(Guid id, Employee loggedInEmployee, Guid tenantId) { ContactCategoryMaster? contactCategory = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); if (contactCategory != null) { List? existingContacts = await _context.Contacts.AsNoTracking().Where(c => c.ContactCategoryId == contactCategory.Id).ToListAsync(); if (existingContacts.Count > 0) { List? contacts = new List(); foreach (var contact in existingContacts) { contact.ContactCategoryId = null; contacts.Add(contact); } _context.Contacts.UpdateRange(contacts); } _context.ContactCategoryMasters.Remove(contactCategory); _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog { RefereanceId = id, UpdatedById = loggedInEmployee.Id, UpdateAt = DateTime.UtcNow }); await _context.SaveChangesAsync(); _logger.LogInfo("Employee {EmployeeId} deleted contact category {ContactCategoryId}", loggedInEmployee.Id, id); } _logger.LogWarning("Employee {EmployeeId} tries to delete Category {CategoryId} but not found in database", loggedInEmployee.Id, id); return ApiResponse.SuccessResponse(new { }, "Category deleted successfully", 200); } #endregion #region =================================================================== Contact Tag APIs =================================================================== public async Task> GetContactTags(Employee loggedInEmployee, Guid tenantId) { var taglist = await _context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync(); List contactTags = new List(); foreach (var tag in taglist) { ContactTagVM tagVm = tag.ToContactTagVMFromContactTagMaster(); contactTags.Add(tagVm); } _logger.LogInfo("{count} contact Tags are fetched by Employee with ID {LoggedInEmployeeId}", contactTags.Count, loggedInEmployee.Id); return ApiResponse.SuccessResponse(contactTags, System.String.Format("{0} contact tags fetched successfully", contactTags.Count), 200); } public async Task> CreateContactTag(CreateContactTagDto contactTagDto, Employee loggedInEmployee, Guid tenantId) { if (contactTagDto != null) { ContactTagMaster? existingContactTag = await _context.ContactTagMasters.FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Name.ToLower() == (contactTagDto.Name != null ? contactTagDto.Name.ToLower() : "")); if (existingContactTag == null) { ContactTagMaster contactTag = contactTagDto.ToContactTagMasterFromCreateContactTagDto(tenantId); _context.ContactTagMasters.Add(contactTag); await _context.SaveChangesAsync(); ContactTagVM tagVM = contactTag.ToContactTagVMFromContactTagMaster(); _logger.LogInfo("Employee ID {LoggedInEmployeeId} created a contact tag {ContactTagId}.", loggedInEmployee.Id, contactTag.Id); return ApiResponse.SuccessResponse(tagVM, "Tag Created Successfully", 200); } _logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to create an existing contact tag.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Tag already existed", "Tag already existed", 409); } _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> UpdateContactTag(Guid id, UpdateContactTagDto contactTagDto, Employee loggedInEmployee, Guid tenantId) { if (contactTagDto != null && contactTagDto.Id == id) { ContactTagMaster? contactTag = await _context.ContactTagMasters.AsNoTracking().FirstOrDefaultAsync(t => t.TenantId == tenantId && t.Id == contactTagDto.Id); if (contactTag != null) { contactTag = contactTagDto.ToContactTagMasterFromUpdateContactTagDto(tenantId); _context.ContactTagMasters.Update(contactTag); _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog { RefereanceId = contactTag.Id, UpdatedById = loggedInEmployee.Id, UpdateAt = DateTime.UtcNow }); await _context.SaveChangesAsync(); await _context.SaveChangesAsync(); ContactTagVM contactTagVm = contactTag.ToContactTagVMFromContactTagMaster(); _logger.LogInfo("Contact tag master {ConatctTagId} updated successfully by employee {EmployeeId}", contactTagVm.Id, loggedInEmployee.Id); return ApiResponse.SuccessResponse(contactTagVm, "Contact Tag master updated successfully", 200); } _logger.LogWarning("Contact Tag master {ContactTagId} not found in database", id); return ApiResponse.ErrorResponse("Contact Tag master not found", "Contact tag master not found", 404); } _logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", loggedInEmployee.Id); return ApiResponse.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400); } public async Task> DeleteContactTag(Guid id, Employee loggedInEmployee, Guid tenantId) { ContactTagMaster? contactTag = await _context.ContactTagMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId); if (contactTag != null) { List? tagMappings = await _context.ContactTagMappings.Where(t => t.ContactTagId == contactTag.Id).ToListAsync(); _context.ContactTagMasters.Remove(contactTag); if (tagMappings.Any()) { _context.ContactTagMappings.RemoveRange(tagMappings); } _context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog { RefereanceId = id, UpdatedById = loggedInEmployee.Id, UpdateAt = DateTime.UtcNow }); await _context.SaveChangesAsync(); _logger.LogInfo("Employee {EmployeeId} deleted contact tag {ContactTagId}", loggedInEmployee.Id, id); } _logger.LogWarning("Employee {EmployeeId} tries to delete Tag {ContactTagId} but not found in database", loggedInEmployee.Id, id); return ApiResponse.SuccessResponse(new { }, "Tag deleted successfully", 200); } #endregion #region =================================================================== Work Status APIs =================================================================== public async Task> GetWorkStatusList(Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("GetWorkStatusList called."); try { // Step 1: Check permission to view master data bool hasViewPermission = await _permission.HasPermission(PermissionsMaster.ViewMasters, loggedInEmployee.Id); if (!hasViewPermission) { _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have access", "Don't have access to take action", 403); } // Step 2: Fetch work statuses for the tenant var workStatusList = await _context.WorkStatusMasters .Where(ws => ws.TenantId == tenantId) .Select(ws => new { ws.Id, ws.Name, ws.Description, ws.IsSystem }) .ToListAsync(); _logger.LogInfo("{Count} work statuses fetched for tenantId: {TenantId}", workStatusList.Count, tenantId); // Step 3: Return successful response return ApiResponse.SuccessResponse( workStatusList, $"{workStatusList.Count} work status records fetched successfully", 200 ); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while fetching work status list"); return ApiResponse.ErrorResponse("An error occurred", "Unable to fetch work status list", 500); } } public async Task> CreateWorkStatus(CreateWorkStatusMasterDto createWorkStatusDto, Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("CreateWorkStatus called with Name: {Name}", createWorkStatusDto.Name ?? ""); try { // Step 1: Check if user has permission to manage master data var hasManageMasterPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManageMasterPermission) { _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have access", "Don't have access to take action", 403); } // Step 2: Check if work status with the same name already exists var existingWorkStatus = await _context.WorkStatusMasters .FirstOrDefaultAsync(ws => ws.Name == createWorkStatusDto.Name && ws.TenantId == tenantId); if (existingWorkStatus != null) { _logger.LogWarning("Work status already exists: {Name}", createWorkStatusDto.Name ?? ""); return ApiResponse.ErrorResponse("Work status already exists", "Work status already exists", 400); } // Step 3: Create new WorkStatusMaster entry var workStatus = new WorkStatusMaster { Name = createWorkStatusDto.Name?.Trim() ?? "", Description = createWorkStatusDto.Description?.Trim() ?? "", IsSystem = false, TenantId = tenantId }; _context.WorkStatusMasters.Add(workStatus); await _context.SaveChangesAsync(); _logger.LogInfo("Work status created successfully: {Id}, Name: {Name}", workStatus.Id, workStatus.Name); return ApiResponse.SuccessResponse(workStatus, "Work status created successfully", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while creating work status"); return ApiResponse.ErrorResponse("An error occurred", "Unable to create work status", 500); } } public async Task> UpdateWorkStatus(Guid id, UpdateWorkStatusMasterDto updateWorkStatusDto, Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("UpdateWorkStatus called for WorkStatus ID: {Id}, New Name: {Name}", id, updateWorkStatusDto.Name ?? ""); try { // Step 1: Validate input if (id == Guid.Empty || id != updateWorkStatusDto.Id) { _logger.LogWarning("Invalid ID provided for update. Route ID: {RouteId}, DTO ID: {DtoId}", id, updateWorkStatusDto.Id); return ApiResponse.ErrorResponse("Invalid data provided", "The provided work status ID is invalid", 400); } // Step 2: Check permissions var hasManageMasterPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManageMasterPermission) { _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have Manage Master permission.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access denied", "You do not have permission to update this work status", 403); } // Step 3: Retrieve the work status record var workStatus = await _context.WorkStatusMasters .FirstOrDefaultAsync(ws => ws.Id == id && ws.TenantId == tenantId); if (workStatus == null) { _logger.LogWarning("Work status not found for ID: {Id}", id); return ApiResponse.ErrorResponse("Work status not found", "No work status found with the provided ID", 404); } // Step 4: Check for duplicate name (optional) var isDuplicate = await _context.WorkStatusMasters .AnyAsync(ws => ws.Name == updateWorkStatusDto.Name && ws.Id != id && ws.TenantId == tenantId); if (isDuplicate) { _logger.LogWarning("Duplicate work status name '{Name}' detected during update. ID: {Id}", updateWorkStatusDto.Name ?? "", id); return ApiResponse.ErrorResponse("Work status with the same name already exists", "Duplicate name", 400); } // Step 5: Update fields workStatus.Name = updateWorkStatusDto.Name?.Trim() ?? ""; workStatus.Description = updateWorkStatusDto.Description?.Trim() ?? ""; await _context.SaveChangesAsync(); _logger.LogInfo("Work status updated successfully. ID: {Id}", id); return ApiResponse.SuccessResponse(workStatus, "Work status updated successfully", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while updating work status ID: {Id}", id); return ApiResponse.ErrorResponse("An error occurred", "Unable to update the work status at this time", 500); } } public async Task> DeleteWorkStatus(Guid id, Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("DeleteWorkStatus called for Id: {Id}", id); try { // Step 2: Check permission to manage master data var hasManageMasterPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManageMasterPermission) { _logger.LogWarning("Delete denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("You don't have access", "Access denied for deleting work status", 403); } // Step 3: Find the work status var workStatus = await _context.WorkStatusMasters .FirstOrDefaultAsync(ws => ws.Id == id && ws.TenantId == tenantId); if (workStatus == null) { _logger.LogWarning("Work status not found for Id: {Id}", id); return ApiResponse.ErrorResponse("Work status not found", "Work status not found", 404); } // Step 4: Check for dependencies in TaskAllocations bool hasDependency = await _context.TaskAllocations .AnyAsync(ta => ta.TenantId == tenantId && ta.WorkStatusId == id); if (hasDependency) { _logger.LogWarning("Cannot delete WorkStatus Id: {Id} due to existing task dependency", id); return ApiResponse.ErrorResponse( "Work status has a dependency in assigned tasks and cannot be deleted", "Deletion failed due to associated tasks", 400 ); } // Step 5: Delete and persist _context.WorkStatusMasters.Remove(workStatus); await _context.SaveChangesAsync(); _logger.LogInfo("Work status deleted successfully. Id: {Id}", id); return ApiResponse.SuccessResponse(new { }, "Work status deleted successfully", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while deleting WorkStatus Id: {Id}", id); return ApiResponse.ErrorResponse("An error occurred", "Unable to delete work status", 500); } } #endregion #region =================================================================== Expenses Type APIs =================================================================== public async Task> GetExpenseTypeListAsync(Employee loggedInEmployee, Guid tenantId, bool isActive) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to fetch the list of expense type from different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Featching the list of Expenses Type. var typeList = await _context.ExpensesTypeMaster.Where(et => et.TenantId == tenantId && et.IsActive == isActive).ToListAsync(); var response = _mapper.Map>(typeList); _logger.LogInfo("{Count} records of expense type have been fetched successfully by employee {EmployeeId}", response.Count, loggedInEmployee.Id); return ApiResponse.SuccessResponse(response, $"{response.Count} records of expense type have been fetched successfully.", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occured while fetching list of expense type list by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> CreateExpenseTypeAsync(ExpensesTypeMasterDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to add new expense type in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing EXPANSES TYPE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var expensesType = _mapper.Map(model); expensesType.TenantId = tenantId; _context.ExpensesTypeMaster.Add(expensesType); await _context.SaveChangesAsync(); _logger.LogInfo("New Expense Type {ExpensesTypeId} was added by employee {EmployeeId}", expensesType.Id, loggedInEmployee.Id); var response = _mapper.Map(expensesType); return ApiResponse.SuccessResponse(response, "Expense type craeted Successfully", 201); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while adding new expense type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while adding new expense type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> UpdateExpenseTypeAsync(Guid id, ExpensesTypeMasterDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to update expense type in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing EXPANSES TYPE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } // Validating the prvided data if (model.Id != id) { _logger.LogWarning("Employee {EmployeeId} provide different Ids in payload and path variable", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Invalid Data", "User has send invalid payload", 400); } var expensesType = await _context.ExpensesTypeMaster.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.Id.Value && et.TenantId == tenantId); // Checking if expense type exists if (expensesType == null) { _logger.LogWarning("Employee {EmployeeId} tries to update expense type, but not found in database", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Expense Type not found", "Expense Type not found", 404); } // Mapping ExpensesTypeMaster to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(expensesType); // Mapping ExpensesTypeMasterDto to ExpensesTypeMaster _mapper.Map(model, expensesType); _context.ExpensesTypeMaster.Update(expensesType); await _context.SaveChangesAsync(); _logger.LogInfo("Expense Type {ExpensesTypeId} was updated by employee {EmployeeId}", expensesType.Id, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = expensesType.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "ExpensesTypeMasterModificationLog"); // Mapping ExpensesTypeMaster to ExpensesTypeMasterVM var response = _mapper.Map(expensesType); return ApiResponse.SuccessResponse(response, "Expense type updated Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while updating expense type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while updating expense type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> DeleteExpenseTypeAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId) { string action = isActive ? "restore" : "delete"; try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to {Action} expense type in different tenant", loggedInEmployee.Id, action); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing EXPANSES TYPE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var expensesType = await _context.ExpensesTypeMaster.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId); // Checking if expense type exists if (expensesType == null) { _logger.LogWarning("Employee {EmployeeId} tries to {Action} expense type, but not found in database", loggedInEmployee.Id, action); return ApiResponse.ErrorResponse("Expense Type not found", "Expense Type not found", 404); } // Mapping ExpensesTypeMaster to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(expensesType); expensesType.IsActive = isActive; await _context.SaveChangesAsync(); _logger.LogInfo("Expense Type {ExpensesTypeId} was {Action}d by employee {EmployeeId}", expensesType.Id, action, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = expensesType.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "ExpensesTypeMasterModificationLog"); // Mapping ExpensesTypeMaster to ExpensesTypeMasterVM var response = _mapper.Map(expensesType); return ApiResponse.SuccessResponse(response, $"Expense type {action}d Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while {Action}ing expense type by employee {EmployeeId}", action, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while {Action}ing expense type by employee {EmployeeId}", action, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } #endregion #region =================================================================== Expenses Status APIs =================================================================== public async Task> GetExpensesStatusListAsync(Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to fetch the list of expense status from different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Featching the list of Expenses Status. var statusList = await _context.ExpensesStatusMaster.ToListAsync(); var response = _mapper.Map>(statusList); var statusIds = statusList.Select(s => s.Id).ToList(); var permissionStatusMapping = await _context.StatusPermissionMapping .Where(ps => statusIds.Contains(ps.StatusId)) .GroupBy(ps => ps.StatusId) .Select(g => new { StatusId = g.Key, PermissionIds = g.Select(ps => ps.PermissionId).ToList() }).ToListAsync(); foreach (var status in response) { status.PermissionIds = permissionStatusMapping.Where(ps => ps.StatusId == status.Id).Select(ps => ps.PermissionIds).FirstOrDefault(); } _logger.LogInfo("{Count} records of expense status have been fetched successfully by employee {EmployeeId}", response.Count, loggedInEmployee.Id); return ApiResponse.SuccessResponse(response, $"{response.Count} records of expense status have been fetched successfully.", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occured while fetching list of expense sattus list by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } #endregion #region =================================================================== Payment mode APIs =================================================================== public async Task> GetPaymentModeListAsync(Employee loggedInEmployee, Guid tenantId, bool isActive) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to fetch the list of payment modes from different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Featching the list of Payment Modes. var paymentModes = await _context.PaymentModeMatser.Where(pm => pm.TenantId == tenantId && pm.IsActive == isActive).ToListAsync(); var response = _mapper.Map>(paymentModes); _logger.LogInfo("{Count} records of payment modes have been fetched successfully by employee {EmployeeId}", response.Count, loggedInEmployee.Id); return ApiResponse.SuccessResponse(response, $"{response.Count} records of payment modes have been fetched successfully.", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occured while featching list of payment modes list by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured while featching list of payment modes list", ExceptionMapper(ex), 500); } } public async Task> CreatePaymentModeAsync(PaymentModeMatserDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to add new payment mode in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing PAYMENT MODE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } // Mapping the DTO to PaymentModeMatser Model var paymentMode = _mapper.Map(model); paymentMode.TenantId = tenantId; _context.PaymentModeMatser.Add(paymentMode); await _context.SaveChangesAsync(); _logger.LogInfo("New Payment Mode {PaymentModeId} was added by employee {EmployeeId}", paymentMode.Id, loggedInEmployee.Id); // Mapping the PaymentModeMatser Model to View Model var response = _mapper.Map(paymentMode); return ApiResponse.SuccessResponse(response, "Payment Mode craeted Successfully", 201); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while adding new payment mode by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while adding new payment mode by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> UpdatePaymentModeAsync(Guid id, PaymentModeMatserDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to update Payment Mode in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing PAYMENT MODE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } // Validating the prvided data if (model.Id != id) { _logger.LogWarning("Employee {EmployeeId} provide different Ids in payload and path variable", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Invalid Data", "User has send invalid payload", 400); } var paymentMode = await _context.PaymentModeMatser.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.Id.Value && et.TenantId == tenantId); // Checking if Payment Mode exists if (paymentMode == null) { _logger.LogWarning("Employee {EmployeeId} tries to update Payment Mode, but not found in database", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Payment Mode not found", "Payment Mode not found", 404); } // Mapping PaymentModeMatser to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(paymentMode); // Mapping PaymentModeMatserDto to PaymentModeMatser _mapper.Map(model, paymentMode); _context.PaymentModeMatser.Update(paymentMode); await _context.SaveChangesAsync(); _logger.LogInfo("Payment Mode {PaymentModeId} was updated by employee {EmployeeId}", paymentMode.Id, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = paymentMode.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "PaymentModeMasterModificationLog"); // Mapping PaymentModeMatser to PaymentModeMatserVM var response = _mapper.Map(paymentMode); return ApiResponse.SuccessResponse(response, "Payment Mode updated Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while updating Payment Mode by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while updating Payment Mode by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> DeletePaymentModeAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId) { string action = isActive ? "restore" : "delete"; try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to {Action} Payment Mode in different tenant", loggedInEmployee.Id, action); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing PAYMENT MODE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var paymentMode = await _context.PaymentModeMatser.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId); // Checking if Payment Mode exists if (paymentMode == null) { _logger.LogWarning("Employee {EmployeeId} tries to {Action} Payment Mode, but not found in database", loggedInEmployee.Id, action); return ApiResponse.ErrorResponse("Payment Mode not found", "Payment Mode not found", 404); } // Mapping PaymentModeMatser to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(paymentMode); paymentMode.IsActive = isActive; await _context.SaveChangesAsync(); _logger.LogInfo("Payment Mode {PaymentModeId} was {Action}d by employee {EmployeeId}", paymentMode.Id, action, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = paymentMode.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "PaymentModeMatserModificationLog"); // Mapping PaymentModeMatser to PaymentModeMatserVM var response = _mapper.Map(paymentMode); return ApiResponse.SuccessResponse(response, $"Payment Mode {action}d Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while {Action}ing Payment Mode by employee {EmployeeId}", action, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while {Action}ing Payment Mode by employee {EmployeeId}", action, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } #endregion #region =================================================================== Document Category APIs =================================================================== /// /// Fetches the list of Document Categories for a given tenant and optional entity type. /// Ensures tenant validation, mapping, and proper logging. /// /// Optional entity type filter (e.g., EmployeeEntity, ProjectEntity). /// Currently logged-in employee. /// Tenant Id context. /// ApiResponse containing the document categories or error details. public async Task> GetDocumentCategoryMasterListAsync(Guid? entityTypeId, Employee loggedInEmployee, Guid tenantId) { try { // ✅ Tenant validation if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Access denied. Employee {EmployeeId} (TenantId: {EmployeeTenantId}) attempted to fetch document categories for TenantId: {RequestedTenantId}", loggedInEmployee.Id, loggedInEmployee.TenantId, tenantId); return ApiResponse.ErrorResponse("Access Denied", "You do not have access to this information", 403); } // ✅ Build query IQueryable documentCategoryQuery = _context.DocumentCategoryMasters .AsNoTracking() // optimization: read-only .Where(dc => dc.TenantId == tenantId); // ✅ Apply optional filter if (entityTypeId.HasValue) { documentCategoryQuery = documentCategoryQuery.Where(dc => dc.EntityTypeId == entityTypeId.Value); } // ✅ Fetch and map var documentCategories = await documentCategoryQuery.ToListAsync(); var response = _mapper.Map>(documentCategories); _logger.LogInfo("{Count} document categories fetched successfully for TenantId: {TenantId} by Employee {EmployeeId}", response.Count, tenantId, loggedInEmployee.Id); return ApiResponse.SuccessResponse(response, $"{response.Count} document categories have been fetched successfully.", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while fetching document categories for TenantId: {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Server Error", "Server Error occured", 500); } } public async Task> CreateDocumentCategoryMasterAsync(CreateDocumentCategoryDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to add new Document Category in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing Document Category Master.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var oldExists = await _context.DocumentCategoryMasters .AnyAsync(dc => dc.Name == model.Name && dc.EntityTypeId == model.EntityTypeId && dc.TenantId == tenantId); if (oldExists) { _logger.LogWarning("Document Category of {Name} is already exists in database for {TenantId}", model.Name, tenantId); return ApiResponse.ErrorResponse("Document Category already exists.", "Document Category already exists in database", 409); } // Mapping the DTO to Document Category Master Model var documentCategory = _mapper.Map(model); documentCategory.CreatedAt = DateTime.UtcNow; documentCategory.TenantId = tenantId; _context.DocumentCategoryMasters.Add(documentCategory); await _context.SaveChangesAsync(); _logger.LogInfo("New Document Category {DocumentCategoryId} was added by employee {EmployeeId}", documentCategory.Id, loggedInEmployee.Id); // Mapping the Document Category Master Model to View Model var response = _mapper.Map(documentCategory); return ApiResponse.SuccessResponse(response, "Document Category craeted Successfully", 201); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while adding new Document Category by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while adding new Document Category by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> UpdateDocumentCategoryMasterAsync(Guid id, CreateDocumentCategoryDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to update Document Category in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing DOCUMENT CATEGORY MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } // Validating the prvided data if (model.Id != id) { _logger.LogWarning("Employee {EmployeeId} provide different Ids in payload and path variable", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Invalid Data", "User has send invalid payload", 400); } var categoryTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.DocumentCategoryMasters.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.Id.Value && et.TenantId == tenantId); }); var oldCategoryExistsTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.DocumentCategoryMasters.AnyAsync(dc => dc.Name == model.Name && dc.EntityTypeId == model.EntityTypeId && dc.TenantId == tenantId); }); await Task.WhenAll(categoryTask, oldCategoryExistsTask); var documentCategory = categoryTask.Result; var oldCategoryExists = oldCategoryExistsTask.Result; // Checking if Document Category exists if (documentCategory == null) { _logger.LogWarning("Employee {EmployeeId} tries to update Document Category, but not found in database", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Document Category not found", "Document Category not found", 404); } if (oldCategoryExists) { _logger.LogWarning("Document Category of {Name} is already exists in database for {TenantId} while updating document category", model.Name, tenantId); return ApiResponse.ErrorResponse("Document Category already exists.", "Document Category already exists in database", 409); } // Mapping DocumentCategoryMaster to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(documentCategory); // Mapping DocumentCategoryDto to DocumentCategoryMaster _mapper.Map(model, documentCategory); _context.DocumentCategoryMasters.Update(documentCategory); await _context.SaveChangesAsync(); _logger.LogInfo("Document Category {DocumentCategoryId} was updated by employee {EmployeeId}", documentCategory.Id, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = documentCategory.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "DocumentCategoryModificationLog"); // Mapping DocumentCategoryMaster to DocumentCategoryVM var response = _mapper.Map(documentCategory); return ApiResponse.SuccessResponse(response, "Document Category updated Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while updating Document Category by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while updating Document Category by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> DeleteDocumentCategoryMasterAsync(Guid id, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to delete Document Category in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing DOCUMENT CATEGORY MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var documentCategory = await _context.DocumentCategoryMasters.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId); // Checking if Document Category exists if (documentCategory == null) { _logger.LogWarning("Employee {EmployeeId} tries to delete Document Category, but not found in database", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Document Category not found", "Document Category not found", 404); } // Mapping DocumentCategoryMaster to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(documentCategory); _context.DocumentCategoryMasters.Remove(documentCategory); await _context.SaveChangesAsync(); _logger.LogInfo("Document Category {DocumentCategoryId} was deleted by employee {EmployeeId}", documentCategory.Id, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = documentCategory.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "DocumentCategoryModificationLog"); // Mapping DocumentCategoryMatser to DocumentCategoryVM var response = _mapper.Map(documentCategory); return ApiResponse.SuccessResponse(response, "Document Category deleted Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while deleteing Document Category by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while deleteing Document Category by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } #endregion #region =================================================================== Document Type APIs =================================================================== public async Task> GetDocumentTypeMasterListAsync(Guid? documentCategoryId, Employee loggedInEmployee, Guid tenantId) { try { // ✅ Tenant validation if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Access denied. Employee {EmployeeId} (TenantId: {EmployeeTenantId}) attempted to fetch document types for TenantId: {RequestedTenantId}", loggedInEmployee.Id, loggedInEmployee.TenantId, tenantId); return ApiResponse.ErrorResponse("Access Denied", "You do not have access to this information", 403); } // ✅ Build query IQueryable documentTypeQuery = _context.DocumentTypeMasters .AsNoTracking() // optimization: read-only .Where(dc => dc.TenantId == tenantId); // ✅ Apply optional filter if (documentCategoryId.HasValue) { documentTypeQuery = documentTypeQuery.Where(dc => dc.DocumentCategoryId == documentCategoryId.Value); } // ✅ Fetch and map var documentType = await documentTypeQuery.ToListAsync(); var response = _mapper.Map>(documentType); _logger.LogInfo("{Count} document type fetched successfully for TenantId: {TenantId} by Employee {EmployeeId}", response.Count, tenantId, loggedInEmployee.Id); return ApiResponse.SuccessResponse(response, $"{response.Count} document type have been fetched successfully.", 200); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while fetching document type for TenantId: {TenantId} by Employee {EmployeeId}", tenantId, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Server Error", "Server Error occured", 500); } } public async Task> CreateDocumentTypeMasterAsync(CreateDocumentTypeDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to add new Document Type in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing DOCUMENT TYPE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var oldExists = await _context.DocumentTypeMasters .AnyAsync(dt => dt.Name == model.Name && dt.DocumentCategoryId == model.DocumentCategoryId && dt.TenantId == tenantId); if (oldExists) { _logger.LogWarning("Document Type of {Name} is already exists in database for {TenantId} while creating new document type", model.Name, tenantId); return ApiResponse.ErrorResponse("Document Type already exists.", "Document Type already exists in database", 409); } // Mapping the DTO to Document Type Master Model var documentType = _mapper.Map(model); if (string.IsNullOrWhiteSpace(model.RegexExpression)) { documentType.IsValidationRequired = false; } documentType.IsSystem = false; documentType.IsActive = true; documentType.CreatedAt = DateTime.UtcNow; documentType.TenantId = tenantId; _context.DocumentTypeMasters.Add(documentType); await _context.SaveChangesAsync(); _logger.LogInfo("New Document Type {DocumentTypeId} was added by employee {EmployeeId}", documentType.Id, loggedInEmployee.Id); // Mapping the Document Type Master Model to View Model var response = _mapper.Map(documentType); return ApiResponse.SuccessResponse(response, "Document Type craeted Successfully", 201); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while adding new Document Type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while adding new Document Type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> UpdateDocumentTypeMasterAsync(Guid id, CreateDocumentTypeDto model, Employee loggedInEmployee, Guid tenantId) { try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to update Document Type in different tenant", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing DOCUMENT TYPE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } // Validating the prvided data if (model.Id != id) { _logger.LogWarning("Employee {EmployeeId} provide different Ids in payload and path variable", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Invalid Data", "User has send invalid payload", 400); } var documentTypeTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.DocumentTypeMasters.AsNoTracking().FirstOrDefaultAsync(et => et.Id == model.Id.Value && et.TenantId == tenantId); }); var oldTypeExistsTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.DocumentTypeMasters .AnyAsync(dt => dt.Name == model.Name && dt.DocumentCategoryId == model.DocumentCategoryId && dt.TenantId == tenantId); }); await Task.WhenAll(documentTypeTask, oldTypeExistsTask); var documentType = documentTypeTask.Result; var oldTypeExists = oldTypeExistsTask.Result; // Checking if Document Type exists if (documentType == null) { _logger.LogWarning("Employee {EmployeeId} tries to update Document Type, but not found in database", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Document Type not found", "Document Type not found", 404); } if (oldTypeExists) { _logger.LogWarning("Document Type of {Name} is already exists in database for {TenantId} while updating document Type", model.Name, tenantId); return ApiResponse.ErrorResponse("Document Type already exists.", "Document Type already exists in database", 409); } // Mapping DocumentTypeMaster to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(documentType); // Mapping DocumentTypeDto to DocumentTypeMaster _mapper.Map(model, documentType); _context.DocumentTypeMasters.Update(documentType); await _context.SaveChangesAsync(); _logger.LogInfo("Document Type {DocumentTypeId} was updated by employee {EmployeeId}", documentType.Id, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = documentType.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "DocumentTypeModificationLog"); // Mapping DocumentTypeMaster to DocumentTypeVM var response = _mapper.Map(documentType); return ApiResponse.SuccessResponse(response, "Document Type updated Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while updating Document Type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while updating Document Type by employee {EmployeeId}", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } public async Task> DeleteDocumentTypeMasterAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId) { string action = isActive ? "restore" : "delete"; try { // Validation if employee is taking action in same tenant if (tenantId != loggedInEmployee.TenantId) { _logger.LogWarning("Employee {EmployeeId} attempted to {Action} Document Type in different tenant", loggedInEmployee.Id, action); return ApiResponse.ErrorResponse("Access Denied", "User do not have access for this information", 403); } // Checking permssion for managing masters var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); if (!hasManagePermission) { _logger.LogWarning("Access DENIED for employee {EmployeeId} for managing DOCUMENT TYPE MASTER.", loggedInEmployee.Id); return ApiResponse.ErrorResponse("Access Denied.", "You do not have permission to Manage masters", 403); } var documentType = await _context.DocumentTypeMasters.FirstOrDefaultAsync(et => et.Id == id && et.TenantId == tenantId); // Checking if Document Type exists if (documentType == null) { _logger.LogWarning("Employee {EmployeeId} tries to {Action} Document Type, but not found in database", loggedInEmployee.Id, action); return ApiResponse.ErrorResponse("Document Type not found", "Document Type not found", 404); } // Mapping DocumentTypeMatser to BsonDocument var existingEntityBson = _updateLogHelper.EntityToBsonDocument(documentType); documentType.IsActive = isActive; await _context.SaveChangesAsync(); _logger.LogInfo("Document Type {DocumentTypeId} was {Action}d by employee {EmployeeId}", documentType.Id, action, loggedInEmployee.Id); // Saving the old entity in mongoDB var mongoDBTask = _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject { EntityId = documentType.Id.ToString(), UpdatedById = loggedInEmployee.Id.ToString(), OldObject = existingEntityBson, UpdatedAt = DateTime.UtcNow }, "DocumentTypeModificationLog"); // Mapping DocumentTypeMatser to DocumentTypeVM var response = _mapper.Map(documentType); return ApiResponse.SuccessResponse(response, $"Document Type {action}d Successfully", 200); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while {Action}ing Document Type by employee {EmployeeId}", action, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while {Action}ing Document Type by employee {EmployeeId}", action, loggedInEmployee.Id); return ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500); } } #endregion #region =================================================================== Helper Function =================================================================== private static object ExceptionMapper(Exception ex) { return new { Message = ex.Message, StackTrace = ex.StackTrace, Source = ex.Source, InnerException = new { Message = ex.InnerException?.Message, StackTrace = ex.InnerException?.StackTrace, Source = ex.InnerException?.Source, } }; } #endregion } }