using AutoMapper; using Marco.Pms.CacheHelper; using Marco.Pms.Model.AppMenu; using Marco.Pms.Model.Dtos.AppMenu; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.AppMenu; using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Marco.Pms.Services.Controllers { [Authorize] [ApiController] [Route("api/[controller]")] public class AppMenuController : ControllerBase { private readonly UserHelper _userHelper; private readonly SidebarMenuHelper _sideBarMenuHelper; private readonly IMapper _mapper; private readonly ILoggingService _logger; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly Guid tenantId; private static readonly Guid superTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26"); private static readonly Guid ProjectManagement = Guid.Parse("53176ebf-c75d-42e5-839f-4508ffac3def"); private static readonly Guid ExpenseManagement = Guid.Parse("a4e25142-449b-4334-a6e5-22f70e4732d7"); private static readonly Guid TaskManagement = Guid.Parse("9d4b5489-2079-40b9-bd77-6e1bf90bc19f"); private static readonly Guid EmployeeManagement = Guid.Parse("81ab8a87-8ccd-4015-a917-0627cee6a100"); private static readonly Guid AttendanceManagement = Guid.Parse("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"); private static readonly Guid MastersMangent = Guid.Parse("be3b3afc-6ccf-4566-b9b6-aafcb65546be"); private static readonly Guid DirectoryManagement = Guid.Parse("39e66f81-efc6-446c-95bd-46bff6cfb606"); private static readonly Guid TenantManagement = Guid.Parse("2f3509b7-160d-410a-b9b6-daadd96c986d"); public AppMenuController(UserHelper userHelper, SidebarMenuHelper sideBarMenuHelper, IMapper mapper, ILoggingService logger, IServiceScopeFactory serviceScopeFactory) { _userHelper = userHelper; _sideBarMenuHelper = sideBarMenuHelper; _mapper = mapper; _logger = logger; _serviceScopeFactory = serviceScopeFactory; tenantId = userHelper.GetTenantId(); } /// /// Creates a new sidebar menu section for the tenant. /// Only accessible by root users or for the super tenant. /// /// The data for the new menu section. /// HTTP response with result of the operation. [HttpPost("add/sidebar/menu-section")] public async Task CreateAppSideBarMenu([FromBody] CreateMenuSectionDto menuSectionDto) { // Step 1: Fetch logged-in user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; // Step 2: Authorization check if (!isRootUser || tenantId != superTenantId) { _logger.LogWarning("Access denied: Employee {EmployeeId} attempted to create sidebar menu in Tenant {TenantId}", loggedInEmployee.Id, tenantId); return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "User does not have permission.", 403)); } // Step 3: Map DTO to entity var sideMenuSection = _mapper.Map(menuSectionDto); sideMenuSection.TenantId = tenantId; try { // Step 4: Save entity using helper sideMenuSection = await _sideBarMenuHelper.CreateMenuSectionAsync(sideMenuSection); if (sideMenuSection == null) { _logger.LogWarning("Failed to create sidebar menu section. Tenant: {TenantId}, Request: {@MenuSectionDto}", tenantId, menuSectionDto); return BadRequest(ApiResponse.ErrorResponse("Invalid MenuSection", 400)); } // Step 5: Log success _logger.LogInfo("Sidebar menu created successfully. SectionId: {SectionId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}", sideMenuSection.Id, tenantId, loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(sideMenuSection, "Sidebar menu created successfully.", 201)); } catch (Exception ex) { // Step 6: Handle and log unexpected server errors _logger.LogError(ex, "Unexpected error occurred while creating sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}, Request: {@MenuSectionDto}", tenantId, loggedInEmployee.Id, menuSectionDto); return StatusCode(500, ApiResponse.ErrorResponse("Server Error", "An unexpected error occurred.", 500)); } } /// /// Updates an existing sidebar menu section for the tenant. /// Only accessible by root users or for the super tenant. /// /// The unique identifier of the section to update. /// The updated data for the sidebar menu section. /// HTTP response with the result of the operation. [HttpPut("edit/sidebar/menu-section/{sectionId}")] public async Task UpdateMenuSection(Guid sectionId, [FromBody] UpdateMenuSectionDto updatedSection) { // Step 1: Fetch logged-in user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; // Step 2: Authorization check if (!isRootUser && tenantId != superTenantId) { _logger.LogWarning("Access denied: User {UserId} attempted to update sidebar menu section {SectionId} in Tenant {TenantId}", loggedInEmployee.Id, sectionId, tenantId); return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "User does not have permission.", 403)); } // Step 3: Validate request if (sectionId == Guid.Empty || sectionId != updatedSection.Id) { _logger.LogWarning("Invalid update request. Tenant: {TenantId}, SectionId: {SectionId}, PayloadId: {PayloadId}, UserId: {UserId}", tenantId, sectionId, updatedSection.Id, loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Invalid section ID or mismatched payload.", 400)); } // Step 4: Map DTO to entity var menuSectionEntity = _mapper.Map(updatedSection); try { // Step 5: Perform update operation var result = await _sideBarMenuHelper.UpdateMenuSectionAsync(sectionId, menuSectionEntity); if (result == null) { _logger.LogWarning("Menu section not found for update. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}", sectionId, tenantId, loggedInEmployee.Id); return NotFound(ApiResponse.ErrorResponse("Menu section not found", 404)); } // Step 6: Successful update _logger.LogInfo("Menu section updated successfully. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}", sectionId, tenantId, loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(result, "Menu section updated successfully")); } catch (Exception ex) { // Step 7: Unexpected server error _logger.LogError(ex, "Failed to update menu section. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}, Payload: {@UpdatedSection}", sectionId, tenantId, loggedInEmployee.Id, updatedSection); return StatusCode(500, ApiResponse.ErrorResponse("Server error", "An unexpected error occurred while updating the menu section.", 500)); } } /// /// Adds a new menu item to an existing sidebar menu section. /// Only accessible by root users or for the super tenant. /// /// The unique identifier of the section the item will be added to. /// The details of the new menu item. /// HTTP response with the result of the operation. [HttpPost("add/sidebar/menus/{sectionId}/items")] public async Task AddMenuItem(Guid sectionId, [FromBody] CreateMenuItemDto newItemDto) { // Step 1: Fetch logged-in user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; // Step 2: Authorization check if (!isRootUser && tenantId != superTenantId) { _logger.LogWarning("Access denied: User {UserId} attempted to add menu item to section {SectionId} in Tenant {TenantId}", loggedInEmployee.Id, sectionId, tenantId); return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "User does not have permission.", 403)); } // Step 3: Input validation if (sectionId == Guid.Empty || newItemDto == null) { _logger.LogWarning("Invalid AddMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, UserId: {UserId}", tenantId, sectionId, loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Invalid section ID or menu item payload.", 400)); } try { // Step 4: Map DTO to entity var menuItemEntity = _mapper.Map(newItemDto); // Step 5: Perform Add operation var result = await _sideBarMenuHelper.AddMenuItemAsync(sectionId, menuItemEntity); if (result == null) { _logger.LogWarning("Menu section not found. Unable to add menu item. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}", sectionId, tenantId, loggedInEmployee.Id); return NotFound(ApiResponse.ErrorResponse("Menu section not found", 404)); } // Step 6: Successful addition _logger.LogInfo("Menu item added successfully. SectionId: {SectionId}, MenuItemId: {MenuItemId}, TenantId: {TenantId}, UserId: {UserId}", sectionId, result.Id, tenantId, loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(result, "Menu item added successfully")); } catch (Exception ex) { // Step 7: Handle unexpected errors _logger.LogError(ex, "Error occurred while adding menu item. SectionId: {SectionId}, TenantId: {TenantId}, UserId: {UserId}, Payload: {@NewItemDto}", sectionId, tenantId, loggedInEmployee.Id, newItemDto); return StatusCode(500, ApiResponse.ErrorResponse("Server error", "An unexpected error occurred while adding the menu item.", 500)); } } /// /// Updates an existing menu item inside a sidebar menu section. /// Only accessible by root users or within the super tenant. /// /// The ID of the sidebar menu section. /// The ID of the menu item to update. /// The updated menu item details. /// HTTP response with the result of the update operation. [HttpPut("edit/sidebar/{sectionId}/items/{itemId}")] public async Task UpdateMenuItem(Guid sectionId, Guid itemId, [FromBody] UpdateMenuItemDto updatedMenuItem) { // Step 1: Fetch logged-in user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; // Step 2: Authorization check if (!isRootUser && tenantId != superTenantId) { _logger.LogWarning("Access denied: User {UserId} attempted to update menu item {ItemId} in Section {SectionId}, Tenant {TenantId}", loggedInEmployee.Id, itemId, sectionId, tenantId); return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "User does not have permission.", 403)); } // Step 3: Input validation if (sectionId == Guid.Empty || itemId == Guid.Empty || updatedMenuItem == null || updatedMenuItem.Id != itemId) { _logger.LogWarning("Invalid UpdateMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}", tenantId, sectionId, itemId, loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Invalid section ID, item ID, or menu item payload.", 400)); } // Step 4: Map DTO to entity var menuItemEntity = _mapper.Map(updatedMenuItem); try { // Step 5: Perform update operation var result = await _sideBarMenuHelper.UpdateMenuItemAsync(sectionId, itemId, menuItemEntity); if (result == null) { _logger.LogWarning("Menu item not found or update failed. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}", tenantId, sectionId, itemId, loggedInEmployee.Id); return NotFound(ApiResponse.ErrorResponse("Menu item not found or update failed.", 404)); } // Step 6: Success log _logger.LogInfo("Menu item updated successfully. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}", tenantId, sectionId, itemId, loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(result, "Sidebar menu item updated successfully.")); } catch (Exception ex) { // ✅ Step 7: Handle server errors _logger.LogError(ex, "Error occurred while updating menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}, Payload: {@UpdatedMenuItem}", tenantId, sectionId, itemId, loggedInEmployee.Id, updatedMenuItem); return StatusCode( 500, ApiResponse.ErrorResponse("Server Error", "An unexpected error occurred while updating the menu item.", 500) ); } } /// /// Adds a new sub-menu item to an existing menu item inside a sidebar menu section. /// Only accessible by root users or within the super tenant. /// /// The ID of the sidebar menu section. /// The ID of the parent menu item. /// The details of the new sub-menu item. /// HTTP response with the result of the add operation. [HttpPost("add/sidebar/menus/{sectionId}/items/{itemId}/subitems")] public async Task AddSubMenuItem(Guid sectionId, Guid itemId, [FromBody] CreateSubMenuItemDto newSubItem) { // Step 1: Fetch logged-in user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; // Step 2: Authorization check if (!isRootUser && tenantId != superTenantId) { _logger.LogWarning("Access denied: User {UserId} attempted to add sub-menu item in Section {SectionId}, MenuItem {ItemId}, Tenant {TenantId}", loggedInEmployee.Id, sectionId, itemId, tenantId); return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "User does not have permission.", 403)); } // Step 3: Validate input if (sectionId == Guid.Empty || itemId == Guid.Empty || newSubItem == null) { _logger.LogWarning("Invalid AddSubMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}", tenantId, sectionId, itemId, loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Invalid section ID, item ID, or sub-menu item payload.", 400)); } try { // Step 4: Map DTO to entity var subMenuItemEntity = _mapper.Map(newSubItem); // Step 5: Perform add operation var result = await _sideBarMenuHelper.AddSubMenuItemAsync(sectionId, itemId, subMenuItemEntity); if (result == null) { _logger.LogWarning("Parent menu item not found. Failed to add sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}", tenantId, sectionId, itemId, loggedInEmployee.Id); return NotFound(ApiResponse.ErrorResponse("Parent menu item not found.", 404)); } // Step 6: Success logging _logger.LogInfo("Sub-menu item added successfully. Tenant: {TenantId}, SectionId: {SectionId}, ParentItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}", tenantId, sectionId, itemId, result.Id, loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(result, "Sub-menu item added successfully.")); } catch (Exception ex) { // Step 7: Handle unexpected errors _logger.LogError(ex, "Error occurred while adding sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, UserId: {UserId}, Payload: {@NewSubItem}", tenantId, sectionId, itemId, loggedInEmployee.Id, newSubItem); return StatusCode(500, ApiResponse.ErrorResponse("Server Error", "An unexpected error occurred while adding the sub-menu item.", 500)); } } /// /// Updates an existing sub-menu item inside a sidebar menu section. /// Only accessible by root users or within the super tenant. /// /// The ID of the sidebar menu section. /// The ID of the parent menu item. /// The ID of the sub-menu item to update. /// The updated sub-menu item details. /// HTTP response with the result of the update operation. [HttpPut("edit/sidebar/{sectionId}/items/{itemId}/subitems/{subItemId}")] public async Task UpdateSubmenuItem(Guid sectionId, Guid itemId, Guid subItemId, [FromBody] UpdateSubMenuItemDto updatedSubMenuItem) { // Step 1: Fetch logged-in user var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var isRootUser = loggedInEmployee.ApplicationUser?.IsRootUser ?? false; // Step 2: Authorization check if (!isRootUser && tenantId != superTenantId) { _logger.LogWarning("Access denied: User {UserId} attempted to update sub-menu {SubItemId} under MenuItem {ItemId} in Section {SectionId}, Tenant {TenantId}", loggedInEmployee.Id, subItemId, itemId, sectionId, tenantId); return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "User does not have permission.", 403)); } // Step 3: Input validation if (sectionId == Guid.Empty || itemId == Guid.Empty || subItemId == Guid.Empty || updatedSubMenuItem == null || updatedSubMenuItem.Id != subItemId) { _logger.LogWarning("Invalid UpdateSubMenuItem request. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}", tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id); return BadRequest(ApiResponse.ErrorResponse("Invalid section ID, menu item ID, sub-item ID, or payload mismatch.", 400)); } try { // Step 4: Map DTO to entity var subMenuEntity = _mapper.Map(updatedSubMenuItem); // Step 5: Perform update operation var result = await _sideBarMenuHelper.UpdateSubmenuItemAsync(sectionId, itemId, subItemId, subMenuEntity); if (result == null) { _logger.LogWarning("Sub-menu item not found or update failed. Tenant: {TenantId}, SectionId: {SectionId}, ItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}", tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id); return NotFound(ApiResponse.ErrorResponse("Sub-menu item not found.", 404)); } // Step 6: Log success _logger.LogInfo("Sub-menu item updated successfully. Tenant: {TenantId}, SectionId: {SectionId}, MenuItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}", tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id); return Ok(ApiResponse.SuccessResponse(result, "Sub-menu item updated successfully.")); } catch (Exception ex) { // Step 7: Handle unexpected errors _logger.LogError(ex, "Error occurred while updating sub-menu item. Tenant: {TenantId}, SectionId: {SectionId}, MenuItemId: {ItemId}, SubItemId: {SubItemId}, UserId: {UserId}, Payload: {@UpdatedSubMenuItem}", tenantId, sectionId, itemId, subItemId, loggedInEmployee.Id, updatedSubMenuItem); return StatusCode(500, ApiResponse.ErrorResponse("Server Error", "An unexpected error occurred while updating the sub-menu item.", 500)); } } /// /// Fetches the sidebar menu for the current tenant and filters items based on employee permissions. /// /// The sidebar menu with only the items/sub-items the employee has access to. [HttpGet("get/menu")] public async Task GetAppSideBarMenu() { // Step 1: Get logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var employeeId = loggedInEmployee.Id; using var scope = _serviceScopeFactory.CreateScope(); var _permissions = scope.ServiceProvider.GetRequiredService(); try { // Step 2: Fetch all menu sections for the tenant var menus = await _sideBarMenuHelper.GetAllMenuSectionsAsync(tenantId); foreach (var menu in menus) { var allowedItems = new List(); foreach (var item in menu.Items) { // --- Item permission check --- if (!item.PermissionIds.Any()) { allowedItems.Add(item); } else { // Convert permission string IDs to GUIDs var menuPermissionIds = item.PermissionIds .Select(Guid.Parse) .ToList(); bool isAllowed = await _permissions.HasPermissionAny(menuPermissionIds, employeeId); // If allowed, filter its submenus as well if (isAllowed) { if (item.Submenu?.Any() == true) { var allowedSubmenus = new List(); foreach (var subItem in item.Submenu) { if (!subItem.PermissionIds.Any()) { allowedSubmenus.Add(subItem); continue; } var subMenuPermissionIds = subItem.PermissionIds .Select(Guid.Parse) .ToList(); bool isSubItemAllowed = await _permissions.HasPermissionAny(subMenuPermissionIds, employeeId); if (isSubItemAllowed) { allowedSubmenus.Add(subItem); } } // Replace with filtered submenus item.Submenu = allowedSubmenus; } allowedItems.Add(item); } } } // Replace with filtered items menu.Items = allowedItems; } // Step 3: Log success _logger.LogInfo("Fetched sidebar menu successfully. Tenant: {TenantId}, EmployeeId: {EmployeeId}, SectionsReturned: {Count}", tenantId, employeeId, menus.Count); return Ok(ApiResponse.SuccessResponse(menus, "Sidebar menu fetched successfully")); } catch (Exception ex) { // Step 4: Handle unexpected errors _logger.LogError(ex, "Error occurred while fetching sidebar menu. Tenant: {TenantId}, EmployeeId: {EmployeeId}", tenantId, employeeId); return StatusCode(500, ApiResponse.ErrorResponse("Server Error", "An unexpected error occurred while fetching the sidebar menu.", 500)); } } /// /// Retrieves the master menu list based on enabled features for the current tenant. /// /// List of master menu items available for the tenant [HttpGet("get/master-list")] public async Task GetMasterList() { // Start logging scope for observability try { // Get currently logged-in employee var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); _logger.LogInfo("Fetching master list for EmployeeId: {EmployeeId}", loggedInEmployee.Id); using var scope = _serviceScopeFactory.CreateScope(); var generalHelper = scope.ServiceProvider.GetRequiredService(); // Define static master menus for each feature section var featureMenus = new Dictionary> { { EmployeeManagement, new List { new MasterMenuVM { Id = 1, Name = "Application Role" }, new MasterMenuVM { Id = 2, Name = "Job Role" } } }, { ProjectManagement, new List { new MasterMenuVM { Id = 3, Name = "Activity" }, new MasterMenuVM { Id = 4, Name = "Work Category" } } }, { DirectoryManagement, new List { new MasterMenuVM { Id = 5, Name = "Contact Category" }, new MasterMenuVM { Id = 6, Name = "Contact Tag" } } }, { ExpenseManagement, new List { new MasterMenuVM { Id = 7, Name = "Expense Type" }, new MasterMenuVM { Id = 8, Name = "Payment Mode" } } } }; if (tenantId == superTenantId) { _logger.LogInfo("MasterMenu count for TenantId {TenantId}: {Count}", tenantId, featureMenus.Count); return Ok(ApiResponse.SuccessResponse(featureMenus, "Successfully fetched the master table list", 200)); } // Fetch features enabled for tenant var featureIds = await generalHelper.GetFeatureIdsByTenentIdAsync(tenantId); _logger.LogInfo("Enabled features for TenantId: {TenantId} -> {FeatureIds}", tenantId, string.Join(",", featureIds)); // Aggregate menus based on enabled features var response = featureIds .Where(id => featureMenus.ContainsKey(id)) .SelectMany(id => featureMenus[id]) .ToList(); _logger.LogInfo("MasterMenu count for TenantId {TenantId}: {Count}", tenantId, response.Count); return Ok(ApiResponse.SuccessResponse(response, "Successfully fetched the master table list", 200)); } catch (Exception ex) { // Critical error tracking _logger.LogError(ex, "Error occurred while fetching master menu list for TenantId: {TenantId}", tenantId); return StatusCode(500, ApiResponse.ErrorResponse("An unexpected error occurred while fetching master menu list.")); } } } }