diff --git a/Marco.Pms.Services/Controllers/MasterController.cs b/Marco.Pms.Services/Controllers/MasterController.cs index 7b9baeb..7a6a48d 100644 --- a/Marco.Pms.Services/Controllers/MasterController.cs +++ b/Marco.Pms.Services/Controllers/MasterController.cs @@ -38,6 +38,78 @@ namespace Marco.Pms.Services.Controllers tenantId = userHelper.GetTenantId(); } + #region =================================================================== Services APIs =================================================================== + + [HttpGet("service/list")] + public async Task GetServices() + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.GetServicesAsync(loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpPost("service/create")] + public async Task CreateService([FromBody] ServiceMasterDto serviceMasterDto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.CreateServiceAsync(serviceMasterDto, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpPut("service/edit/{id}")] + public async Task UpdateService(Guid id, [FromBody] ServiceMasterDto serviceMasterDto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.UpdateServiceAsync(id, serviceMasterDto, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpDelete("service/delete/{id}")] + public async Task DeleteService(Guid id, [FromQuery] bool active = false) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.DeleteServiceAsync(id, active, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + #endregion + + #region =================================================================== Activity Group APIs =================================================================== + + [HttpGet("activity-group/list")] + public async Task GetActivityGroups() + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.GetActivityGroupsAsync(loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpPost("activity-group/create")] + public async Task CreateActivityGroup([FromBody] ActivityGroupDto activityGroupDto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.CreateActivityGroupAsync(activityGroupDto, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + + [HttpPut("activity-group/edit/{id}")] + public async Task UpdateActivityGroup(Guid id, [FromBody] ActivityGroupDto activityGroupDto) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.UpdateActivityGroupAsync(id, activityGroupDto, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + + } + + [HttpDelete("activity-group/delete/{id}")] + public async Task DeleteActivityGroup(Guid id, [FromQuery] bool active = false) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _masterService.DeleteActivityGroupAsync(id, active, loggedInEmployee, tenantId); + return StatusCode(response.StatusCode, response); + } + #endregion + #region =================================================================== Activity APIs =================================================================== [HttpGet] diff --git a/Marco.Pms.Services/Service/MasterService.cs b/Marco.Pms.Services/Service/MasterService.cs index 9de13fc..538cb6d 100644 --- a/Marco.Pms.Services/Service/MasterService.cs +++ b/Marco.Pms.Services/Service/MasterService.cs @@ -3,6 +3,7 @@ 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.Activities; using Marco.Pms.Model.Dtos.DocumentManager; using Marco.Pms.Model.Dtos.Master; using Marco.Pms.Model.Employees; @@ -11,6 +12,7 @@ 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.Activities; using Marco.Pms.Model.ViewModels.DocumentManager; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Services.Helpers; @@ -49,6 +51,645 @@ namespace Marco.Pms.Services.Service _cache = cache; } + #region =================================================================== Services APIs =================================================================== + public async Task> GetServicesAsync(Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("GetServices called"); + + try + { + // Step 1: Fetch services for the tenant + var services = await _context.ServiceMasters + .Where(s => s.TenantId == tenantId && s.IsActive) + .Select(s => _mapper.Map(s)) + .ToListAsync(); + + _logger.LogInfo("Fetched {Count} service records for tenantId: {TenantId}", services.Count, tenantId); + + return ApiResponse.SuccessResponse(services, $"{services.Count} record(s) of services fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching services"); + return ApiResponse.ErrorResponse("An error occurred while fetching services", ex.Message, 500); + } + } + public async Task> CreateServiceAsync(ServiceMasterDto serviceMasterDto, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("CreateService called with Name: {ServiceName}", serviceMasterDto.Name); + + try + { + // Step 1: Permission check + var hasManagePermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasManagePermission) + { + _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("You don't have access", "You don't have permission to take this action", 403); + } + + // Step 2: Check for duplicate name + bool isExist = await _context.ServiceMasters + .AnyAsync(s => s.TenantId == tenantId && s.Name == serviceMasterDto.Name); + + if (isExist) + { + _logger.LogWarning("Duplicate service name '{ServiceName}' attempted by employeeId: {EmployeeId}", serviceMasterDto.Name, loggedInEmployee.Id); + return ApiResponse.ErrorResponse( + $"Service with name '{serviceMasterDto.Name}' already exists", + $"Service with name '{serviceMasterDto.Name}' already exists", 400); + } + + // Step 3: Save new service + ServiceMaster service = _mapper.Map(serviceMasterDto); + service.TenantId = tenantId; + service.IsActive = true; + service.IsSystem = false; + _context.ServiceMasters.Add(service); + await _context.SaveChangesAsync(); + + var response = _mapper.Map(service); + + _logger.LogInfo("New service '{ServiceName}' created successfully by employeeId: {EmployeeId}", service.Name, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(response, "New service created successfully", 201); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating service"); + return ApiResponse.ErrorResponse("Failed to create service", ex.Message, 500); + } + } + public async Task> UpdateServiceAsync(Guid id, ServiceMasterDto serviceMasterDto, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("UpdateService called for Id: {Id}", id); + + try + { + + // Step 1: Permission check + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasPermission) + { + _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access denied", "You don't have permission to take this action", 403); + } + + // Step 2: Input validation + if (serviceMasterDto.Id != id) + { + _logger.LogWarning("Invalid input data provided for UpdateService. Id: {Id}", id); + return ApiResponse.ErrorResponse("Invalid input", "Please provide valid service data", 400); + } + + // Step 3: Retrieve service + var service = await _context.ServiceMasters + .FirstOrDefaultAsync(s => s.Id == id && s.TenantId == tenantId && s.IsActive); + + if (service == null) + { + _logger.LogWarning("Service not found for Id: {Id}, Tenant: {TenantId}", id, tenantId); + return ApiResponse.ErrorResponse("Service not found", "The requested service does not exist", 404); + } + + // Step 4: Update and save + service.Name = serviceMasterDto.Name.Trim(); + service.Description = serviceMasterDto.Description.Trim(); + + await _context.SaveChangesAsync(); + + var response = _mapper.Map(service); + + _logger.LogInfo("Service updated successfully. Id: {Id}, TenantId: {TenantId}", service.Id, tenantId); + return ApiResponse.SuccessResponse(response, "Service updated successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while updating service Id: {Id}.", id); + return ApiResponse.ErrorResponse("An error occurred while updating the service", ex.Message, 500); + } + } + public async Task> DeleteServiceAsync(Guid id, bool active, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("DeleteService called with ServiceId: {ServiceId}, IsActive: {IsActive}", id, active); + + try + { + // Step 1: Get validate permission + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, 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 delete services", 403); + } + + // Step 2: Check if the service exists + var service = await _context.ServiceMasters + .FirstOrDefaultAsync(s => s.Id == id && s.TenantId == tenantId); + + if (service == null) + { + _logger.LogWarning("Service not found. ServiceId: {ServiceId}", id); + return ApiResponse.ErrorResponse("Service not found", "Service not found or already deleted", 404); + } + + // Protect system-defined service + if (service.IsSystem) + { + _logger.LogWarning("Attempt to delete system-defined service. ServiceId: {ServiceId}", id); + return ApiResponse.ErrorResponse("Cannot delete system-defined service", "This service is system-defined and cannot be deleted", 400); + } + + // Step 3: Soft delete or restore + service.IsActive = active; + await _context.SaveChangesAsync(); + + var status = active ? "restored" : "deactivated"; + _logger.LogInfo("Service {ServiceId} has been {Status} successfully by EmployeeId: {EmployeeId}", id, status, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(new { }, $"Service {status} successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error occurred while deleting service {ServiceId}", id); + return ApiResponse.ErrorResponse("Error deleting service", ex.Message, 500); + } + } + #endregion + + #region =================================================================== Activity Group APIs =================================================================== + + public async Task> GetActivityGroupsAsync(Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("GetActivityGroups called"); + + try + { + // Step 1: Fetch all activity groups for the tenant + var activityGroups = await _context.ActivityGroupMasters + .Include(ag => ag.Service) + .Where(ag => ag.TenantId == tenantId && ag.IsActive) + .Select(ag => _mapper.Map(ag)) + .ToListAsync(); + + _logger.LogInfo("{Count} activity group(s) fetched for tenantId: {TenantId}", activityGroups.Count, tenantId); + + return ApiResponse.SuccessResponse(activityGroups, $"{activityGroups.Count} record(s) of activity groups fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching activity groups"); + return ApiResponse.ErrorResponse("An error occurred while fetching activity groups", ex.Message, 500); + } + } + public async Task> CreateActivityGroupAsync(ActivityGroupDto activityGroupDto, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("CreateActivityGroup called with Name: {Name}", activityGroupDto.Name); + + try + { + // Step 1: Check Manage Master permission + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasPermission) + { + _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} lacks Manage_Master permission", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access denied", "You don't have permission to take this action", 403); + } + + // Step 2: Check for duplicate name within ActivityGroupMasters + bool isExist = await _context.ActivityGroupMasters + .AnyAsync(ag => ag.TenantId == tenantId && ag.Name.ToLower() == activityGroupDto.Name.ToLower()); + + if (isExist) + { + _logger.LogWarning("Duplicate activity group name '{Name}' attempted by employeeId: {EmployeeId}", activityGroupDto.Name, loggedInEmployee.Id); + return ApiResponse.ErrorResponse( + $"Activity group with name '{activityGroupDto.Name}' already exists", + $"Activity group with name '{activityGroupDto.Name}' already exists", 400); + } + + // Step 3: Map and persist + var activityGroup = _mapper.Map(activityGroupDto); + activityGroup.TenantId = tenantId; + activityGroup.IsActive = true; + activityGroup.IsSystem = false; + + _context.ActivityGroupMasters.Add(activityGroup); + await _context.SaveChangesAsync(); + + var service = await _context.ServiceMasters.FirstOrDefaultAsync(s => s.Id == activityGroup.ServiceId); + + var response = _mapper.Map(activityGroup); + + _logger.LogInfo("New activity group '{Name}' created successfully by employeeId: {EmployeeId}", activityGroup.Name, loggedInEmployee.Id); + return ApiResponse.SuccessResponse(response, "New activity group created successfully", 201); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating activity group"); + return ApiResponse.ErrorResponse("Failed to create activity group", ex.Message, 500); + } + } + public async Task> UpdateActivityGroupAsync(Guid id, ActivityGroupDto activityGroupDto, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("UpdateActivityGroup called for Id: {Id}", id); + + try + { + // Step 1: Permission check + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id); + if (!hasPermission) + { + _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access denied", "You don't have permission to take this action", 403); + } + + // Step 2: Input validation + if (activityGroupDto.Id != id) + { + _logger.LogWarning("Invalid input for activity group update. Id: {Id}", id); + return ApiResponse.ErrorResponse("Invalid input", "Please provide valid data to update activity group", 400); + } + + var service = await _context.ServiceMasters.FirstOrDefaultAsync(s => s.Id == activityGroupDto.ServiceId && s.IsActive); + + if (service == null) + { + _logger.LogWarning("User tries to update activity group, but service not found"); + return ApiResponse.ErrorResponse("Invalid service ID", "Please provide valid service ID to update activity group", 400); + } + + // Step 3: Retrieve existing activity group + var activityGroup = await _context.ActivityGroupMasters + .Include(ag => ag.Service) + .FirstOrDefaultAsync(ag => ag.Id == id && ag.TenantId == tenantId && ag.IsActive); + + if (activityGroup == null) + { + _logger.LogWarning("Activity group not found. Id: {Id}, TenantId: {TenantId}", id, tenantId); + return ApiResponse.ErrorResponse("Activity group not found", "No such activity group exists", 404); + } + + // Step 4: Update and save + activityGroup.Name = activityGroupDto.Name.Trim(); + activityGroup.Description = activityGroupDto.Description.Trim(); + + await _context.SaveChangesAsync(); + + var response = _mapper.Map(activityGroup); + + _logger.LogInfo("Activity group updated successfully. Id: {Id}, TenantId: {TenantId}", activityGroup.Id, tenantId); + return ApiResponse.SuccessResponse(response, "Activity group updated successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while updating activity group Id: {Id}", id); + return ApiResponse.ErrorResponse("An error occurred while updating the activity group", ex.Message, 500); + } + } + public async Task> DeleteActivityGroupAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("DeleteActivityGroup called with ActivityGroupId: {ActivityGroupId}, IsActive: {IsActive}", id, isActive); + + try + { + // Step 1: Permission check + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, 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 delete activity groups", 403); + } + + // Step 2: Fetch the activity group + var activityGroup = await _context.ActivityGroupMasters + .FirstOrDefaultAsync(ag => ag.Id == id && ag.TenantId == tenantId); + + if (activityGroup == null) + { + _logger.LogWarning("ActivityGroup not found. Id: {ActivityGroupId}", id); + return ApiResponse.ErrorResponse("Activity group not found", "Activity group not found or already deleted", 404); + } + + //Protect system-defined activity group + if (activityGroup.IsSystem) + { + _logger.LogWarning("Attempt to delete system-defined activity group. Id: {ActivityGroupId}", id); + return ApiResponse.ErrorResponse("Cannot delete system-defined activity group", "This activity group is system-defined and cannot be deleted", 400); + } + + // Step 3: Perform soft delete or restore + activityGroup.IsActive = isActive; + await _context.SaveChangesAsync(); + + var status = isActive ? "restored" : "deactivated"; + _logger.LogInfo("ActivityGroup {ActivityGroupId} has been {Status} by EmployeeId: {EmployeeId}", id, status, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(new { }, $"Activity group {status} successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error occurred while deleting ActivityGroup {ActivityGroupId}", id); + return ApiResponse.ErrorResponse("Error deleting activity group", ex.Message, 500); + } + } + + #endregion + + #region =================================================================== Activity APIs =================================================================== + public async Task> GetActivitiesMasterAsync(Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("GetActivitiesMaster called"); + + try + { + // Step 1: Fetch all active activities for the tenant + var activities = await _context.ActivityMasters + .Include(c => c.ActivityGroup) + .ThenInclude(ag => ag!.Service) + .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 response = _mapper.Map(activity); + response.CheckLists = _mapper.Map>(checklistForActivity); + return response; + + }).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(ex, "Error occurred in GetActivitiesMaster"); + return ApiResponse.ErrorResponse("Failed to fetch activity records", ex.Message, 500); + } + } + public async Task> CreateActivityAsync(CreateActivityMasterDto createActivity, Employee loggedInEmployee, Guid tenantId) + { + _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.Service) + .FirstOrDefaultAsync(ag => ag.Id == createActivity.ActitvityGroupId && ag.TenantId == tenantId); + 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); + } + + // Step 2: Check permissions + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, 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 = _mapper.Map(createActivity); + activityMaster.TenantId = 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 => + { + var response = _mapper.Map(c); + response.ActivityId = activityMaster.Id; + response.IsChecked = false; + response.TenantId = tenantId; + return response; + }) + .ToList(); + + _context.ActivityCheckLists.AddRange(activityCheckLists); + await _context.SaveChangesAsync(); + + checkListVMs = activityCheckLists + .Select(c => + { + var response = _mapper.Map(c); + return response; + }) + .ToList(); + } + + // Step 5: Prepare final response + var activityVM = _mapper.Map(activityMaster); + activityVM.CheckLists = checkListVMs; + + _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(ex, "Error occurred while creating activity"); + return ApiResponse.ErrorResponse("An error occurred while creating activity", ex.Message, 500); + } + } + public async Task> UpdateActivityAsync(Guid id, CreateActivityMasterDto createActivity, Employee loggedInEmployee, Guid tenantId) + { + _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 hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, 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.Service).FirstOrDefaultAsync(ag => ag.Id == createActivity.ActitvityGroupId && ag.IsActive); + + if (activityGroup == null) + { + _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.ActivityGroup) + .ThenInclude(ag => ag!.Service) + .FirstOrDefaultAsync(a => a.Id == id && a.IsActive && a.TenantId == tenantId); + + if (activity == null) + { + _logger.LogWarning("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(); + activity.ActivityGroupId = 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 => + { + var response = _mapper.Map(c); + response.ActivityId = activity.Id; + response.IsChecked = false; + response.TenantId = tenantId; + return response; + }) + .ToList(); + + var toUpdate = updates + .Select(c => + { + var response = _mapper.Map(c); + response.ActivityId = activity.Id; + response.TenantId = tenantId; + return response; + }) + .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 => _mapper.Map(c)) + .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 = _mapper.Map(activity); + activityVM.CheckLists = updatedChecklistVMs; + + _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(ex, "Exception in UpdateActivity"); + return ApiResponse.ErrorResponse("Error updating activity", ex.Message, 500); + } + } + public async Task> DeleteActivityAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("DeleteActivity called with ActivityId: {ActivityId}, IsActive: {IsActive}", id, isActive); + + try + { + // Step 1: Validate permission + var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageMasters, 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(ex, "Unexpected error while deleting activity {ActivityId}", id); + return ApiResponse.ErrorResponse("Error deleting activity", ex.Message, 500); + } + } + + #endregion + #region =================================================================== Contact Category APIs =================================================================== public async Task> CreateContactCategory(CreateContactCategoryDto contactCategoryDto, Employee loggedInEmployee, Guid tenantId) diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs index 6ef4310..bc3db69 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IMasterService.cs @@ -7,6 +7,20 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces { public interface IMasterService { + #region =================================================================== Service Master APIs =================================================================== + Task> GetServicesAsync(Employee loggedInEmployee, Guid tenantId); + Task> CreateServiceAsync(ServiceMasterDto serviceMasterDto, Employee loggedInEmployee, Guid tenantId); + Task> UpdateServiceAsync(Guid id, ServiceMasterDto serviceMasterDto, Employee loggedInEmployee, Guid tenantId); + Task> DeleteServiceAsync(Guid id, bool active, Employee loggedInEmployee, Guid tenantId); + + #endregion + #region =================================================================== Activity Group Master APIs =================================================================== + Task> GetActivityGroupsAsync(Employee loggedInEmployee, Guid tenantId); + Task> CreateActivityGroupAsync(ActivityGroupDto activityGroupDto, Employee loggedInEmployee, Guid tenantId); + Task> UpdateActivityGroupAsync(Guid id, ActivityGroupDto activityGroupDto, Employee loggedInEmployee, Guid tenantId); + Task> DeleteActivityGroupAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId); + + #endregion #region =================================================================== Contact Category APIs =================================================================== Task> CreateContactCategory(CreateContactCategoryDto contactCategoryDto, Employee loggedInEmployee, Guid tenantId); Task> UpdateContactCategory(Guid id, UpdateContactCategoryDto contactCategoryDto, Employee loggedInEmployee, Guid tenantId); @@ -14,6 +28,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> GetContactCategoryById(Guid id, Employee loggedInEmployee, Guid tenantId); Task> DeleteContactCategory(Guid id, Employee loggedInEmployee, Guid tenantId); #endregion + #region =================================================================== Contact Tag APIs =================================================================== Task> GetContactTags(Employee loggedInEmployee, Guid tenantId); Task> CreateContactTag(CreateContactTagDto contactTagDto, Employee loggedInEmployee, Guid tenantId); @@ -21,6 +36,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> DeleteContactTag(Guid id, Employee loggedInEmployee, Guid tenantId); #endregion + #region =================================================================== Work Status APIs =================================================================== Task> GetWorkStatusList(Employee loggedInEmployee, Guid tenantId); Task> CreateWorkStatus(CreateWorkStatusMasterDto createWorkStatusDto, Employee loggedInEmployee, Guid tenantId);