diff --git a/Marco.Pms.Services/Helpers/MasterHelper.cs b/Marco.Pms.Services/Helpers/MasterHelper.cs index 323fa79..917206f 100644 --- a/Marco.Pms.Services/Helpers/MasterHelper.cs +++ b/Marco.Pms.Services/Helpers/MasterHelper.cs @@ -1,10 +1,13 @@ using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Directory; +using Marco.Pms.Model.Dtos.Activities; 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.Utilities; +using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; @@ -405,6 +408,278 @@ namespace Marco.Pms.Services.Helpers } } + // -------------------------------- Activity -------------------------------- + public async Task> GetActivitiesMaster() + { + _logger.LogInfo("GetActivitiesMaster called"); + + try + { + + // Step 1: Fetch all active activities for the tenant + var activities = await _context.ActivityMasters + .Include(c => c.ServicesMaster) + .Include(c => c.ActivityGroupMaster) + .Where(c => c.TenantId == tenantId && c.IsActive) + .ToListAsync(); + + if (activities.Count == 0) + { + _logger.LogWarning("No active activities found for tenantId: {TenantId}", tenantId); + return ApiResponse.SuccessResponse(new List(), "No activity records found", 200); + } + + // Step 2: Fetch all checklists for those activities in a single query to avoid N+1 + var activityIds = activities.Select(a => a.Id).ToList(); + var checkLists = await _context.ActivityCheckLists + .Where(c => c.TenantId == tenantId && activityIds.Contains(c.ActivityId)) + .ToListAsync(); + + // Step 3: Group checklists by activity + var groupedChecklists = checkLists + .GroupBy(c => c.ActivityId) + .ToDictionary(g => g.Key, g => g.ToList()); + + // Step 4: Map to ViewModel + var activityVMs = activities.Select(activity => + { + var checklistForActivity = groupedChecklists.ContainsKey(activity.Id) + ? groupedChecklists[activity.Id] + : new List(); + + var checklistVMs = checklistForActivity + .Select(cl => cl.ToCheckListVMFromActivityCheckList(activity.Id, false)) + .ToList(); + + return activity.ToActivityVMFromActivityMaster(checklistVMs, activity.ServicesMaster?.Name, activity.ActivityGroupMaster?.Name); + }).ToList(); + + _logger.LogInfo("{Count} activity records fetched successfully for tenantId: {TenantId}", activityVMs.Count, tenantId); + + return ApiResponse.SuccessResponse(activityVMs, $"{activityVMs.Count} activity records fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError("Error occurred in GetActivitiesMaster: {Error}", ex.Message); + return ApiResponse.ErrorResponse("Failed to fetch activity records", ex.Message, 500); + } + } + public async Task> CreateActivity(CreateActivityMasterDto createActivity) + { + _logger.LogInfo("CreateActivity called with ActivityName: {Name}", createActivity?.ActivityName ?? "null"); + + try + { + // Step 1: Validate input + if (createActivity == null) + { + _logger.LogWarning("Null request body received in CreateActivity"); + return ApiResponse.ErrorResponse("Invalid input", "Activity data is required", 400); + } + + var activityGroup = await _context.ActivityGroupMasters + .Include(ag => ag.ServicesMaster) + .FirstOrDefaultAsync(ag => ag.Id == createActivity.ActitvityGroupId && ag.ServiceId == createActivity.ServiceId); + if (activityGroup == null) + { + _logger.LogWarning("User tried to create new activity, but not found activity group"); + return ApiResponse.ErrorResponse("Invalid input", "Activity data is required", 400); + } + + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + + // Step 2: Check permissions + var hasPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id); + if (!hasPermission) + { + _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have Manage_Master permission.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access denied", "You don't have permission to perform this action", 403); + } + + // Step 3: Convert DTO to entity and add activity + var activityMaster = createActivity.ToActivityMasterFromCreateActivityMasterDto(tenantId); + _context.ActivityMasters.Add(activityMaster); + await _context.SaveChangesAsync(); + + List checkListVMs = new(); + + // Step 4: Handle checklist items if present + if (createActivity.CheckList?.Any() == true) + { + var activityCheckLists = createActivity.CheckList + .Select(c => c.ToActivityCheckListFromCreateCheckListDto(tenantId, activityMaster.Id)) + .ToList(); + + _context.ActivityCheckLists.AddRange(activityCheckLists); + await _context.SaveChangesAsync(); + + checkListVMs = activityCheckLists + .Select(c => c.ToCheckListVMFromActivityCheckList(activityMaster.Id, false)) + .ToList(); + } + + // Step 5: Prepare final response + var activityVM = activityMaster.ToActivityVMFromActivityMaster(checkListVMs, activityGroup.ServicesMaster?.Name, activityGroup.Name); + + _logger.LogInfo("Activity '{Name}' created successfully for tenant {TenantId}", activityMaster.ActivityName, tenantId); + return ApiResponse.SuccessResponse(activityVM, "Activity created successfully", 201); + } + catch (Exception ex) + { + _logger.LogError("Error occurred while creating activity: {Message}", ex.Message); + return ApiResponse.ErrorResponse("An error occurred while creating activity", ex.Message, 500); + } + } + public async Task> UpdateActivity(Guid id, CreateActivityMasterDto createActivity) + { + _logger.LogInfo("UpdateActivity called for ActivityId: {ActivityId}", id); + + try + { + // Step 1: Validate input + if (createActivity == null || string.IsNullOrWhiteSpace(createActivity.ActivityName) || string.IsNullOrWhiteSpace(createActivity.UnitOfMeasurement)) + { + _logger.LogWarning("Invalid activity update input for ActivityId: {ActivityId}", id); + return ApiResponse.ErrorResponse("Invalid input", "ActivityName and UnitOfMeasurement are required", 400); + } + + // Step 2: Check permissions + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var hasPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id); + if (!hasPermission) + { + _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access denied", "You don't have permission to update activities", 403); + } + + // Step 3: Validate service, activity group, and activity existence + var activityGroup = await _context.ActivityGroupMasters.Include(ag => ag.ServicesMaster).FirstOrDefaultAsync(ag => ag.Id == createActivity.ActitvityGroupId && ag.IsActive); + + if (activityGroup == null || activityGroup.ServiceId != createActivity.ServiceId) + { + _logger.LogWarning("User tries to update activity, but cannot able found activity group or service"); + return ApiResponse.ErrorResponse("Invalid activity group ID or service ID", "Please provide valid activity group ID or service ID to update activity group", 400); + } + + var activity = await _context.ActivityMasters + .Include(a => a.ServicesMaster) + .Include(a => a.ActivityGroupMaster) + .FirstOrDefaultAsync(a => a.Id == id && a.IsActive && a.TenantId == tenantId); + + if (activity == null) + { + _logger.LogError("Activity not found for ActivityId: {ActivityId}, TenantId: {TenantId}", id, tenantId); + return ApiResponse.ErrorResponse("Activity not found", "Activity not found", 404); + } + + // Step 4: Update activity core data + activity.ActivityName = createActivity.ActivityName.Trim(); + activity.UnitOfMeasurement = createActivity.UnitOfMeasurement.Trim(); + if (activity.ServiceId == null) + { + activity.ServiceId = createActivity.ServiceId; + } + activity.ActitvityGroupId = createActivity.ActitvityGroupId; + + await _context.SaveChangesAsync(); + + // Step 5: Handle checklist updates + var existingChecklists = await _context.ActivityCheckLists + .AsNoTracking() + .Where(c => c.ActivityId == activity.Id) + .ToListAsync(); + + var updatedChecklistVMs = new List(); + + if (createActivity.CheckList != null && createActivity.CheckList.Any()) + { + var incomingCheckIds = createActivity.CheckList.Where(c => c.Id != null).Select(c => c.Id!.Value).ToHashSet(); + + // Prepare lists + var newChecks = createActivity.CheckList.Where(c => c.Id == null); + var updates = createActivity.CheckList.Where(c => c.Id != null && existingChecklists.Any(ec => ec.Id == c.Id)); + var deletes = existingChecklists.Where(ec => !incomingCheckIds.Contains(ec.Id)).ToList(); + + var toAdd = newChecks + .Select(c => c.ToActivityCheckListFromCreateCheckListDto(tenantId, activity.Id)) + .ToList(); + + var toUpdate = updates + .Select(c => c.ToActivityCheckListFromCreateCheckListDto(tenantId, activity.Id)) + .ToList(); + + _context.ActivityCheckLists.AddRange(toAdd); + _context.ActivityCheckLists.UpdateRange(toUpdate); + _context.ActivityCheckLists.RemoveRange(deletes); + + await _context.SaveChangesAsync(); + + // Prepare view model + updatedChecklistVMs = toAdd.Concat(toUpdate) + .Select(c => c.ToCheckListVMFromActivityCheckList(activity.Id, false)) + .ToList(); + } + else if (existingChecklists.Any()) + { + // If no checklist provided, remove all existing + _context.ActivityCheckLists.RemoveRange(existingChecklists); + await _context.SaveChangesAsync(); + } + + // Step 6: Prepare response + var activityVM = activity.ToActivityVMFromActivityMaster(updatedChecklistVMs, activityGroup.ServicesMaster?.Name, activityGroup.Name); + + _logger.LogInfo("Activity updated successfully. ActivityId: {ActivityId}, TenantId: {TenantId}", activity.Id, tenantId); + return ApiResponse.SuccessResponse(activityVM, "Activity updated successfully", 200); + } + catch (Exception ex) + { + _logger.LogError("Exception in UpdateActivity: {Message}", ex.Message); + return ApiResponse.ErrorResponse("Error updating activity", ex.Message, 500); + } + } + public async Task> DeleteActivity(Guid id, bool isActive) + { + _logger.LogInfo("DeleteActivity called with ActivityId: {ActivityId}, IsActive: {IsActive}", id, isActive); + + try + { + // Step 1: Validate permission + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var hasPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id); + + if (!hasPermission) + { + _logger.LogWarning("Access denied for EmployeeId: {EmployeeId}", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access denied", "You don't have permission to delete activities", 403); + } + + // Step 2: Fetch the activity + var activity = await _context.ActivityMasters + .FirstOrDefaultAsync(a => a.Id == id && a.TenantId == tenantId); + + if (activity == null) + { + _logger.LogWarning("Activity not found. ActivityId: {ActivityId}", id); + return ApiResponse.ErrorResponse("Activity not found", "Activity not found or already deleted", 404); + } + + // Step 3: Perform soft delete/restore + activity.IsActive = isActive; + await _context.SaveChangesAsync(); + + string status = isActive ? "restored" : "deactivated"; + _logger.LogInfo("Activity {ActivityId} {Status} successfully by EmployeeId: {EmployeeId}", id, status, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(new { }, $"Activity {status} successfully", 200); + } + catch (Exception ex) + { + _logger.LogError("Unexpected error while deleting activity {ActivityId} : {Error}", id, ex.Message); + return ApiResponse.ErrorResponse("Error deleting activity", ex.Message, 500); + } + } + // -------------------------------- Contact Category -------------------------------- public async Task> CreateContactCategory(CreateContactCategoryDto contactCategoryDto) {