1117 lines
59 KiB
C#

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;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;
namespace Marco.Pms.Services.Helpers
{
public class MasterHelper
{
private readonly ApplicationDbContext _context;
private readonly ILoggingService _logger;
private readonly UserHelper _userHelper;
private readonly PermissionServices _permissionService;
private readonly Guid View_Master;
private readonly Guid Manage_Master;
private readonly Guid tenantId;
public MasterHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper, PermissionServices permissionServices)
{
_context = context;
_logger = logger;
_userHelper = userHelper;
_permissionService = permissionServices;
View_Master = Guid.Parse("5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d");
Manage_Master = Guid.Parse("588a8824-f924-4955-82d8-fc51956cf323");
tenantId = _userHelper.GetTenantId();
}
// -------------------------------- Services --------------------------------
public async Task<ApiResponse<object>> GetServices()
{
_logger.LogInfo("GetServices called");
try
{
// Step 1: Get current logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Fetch services for the tenant
var services = await _context.ServicesMasters
.Where(s => s.TenantId == tenantId && s.IsActive)
.Select(s => s.ToServiceMasterVMFromServiceMaster())
.ToListAsync();
_logger.LogInfo("Fetched {Count} service records for tenantId: {TenantId}", services.Count, tenantId);
return ApiResponse<object>.SuccessResponse(services, $"{services.Count} record(s) of services fetched successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching services");
return ApiResponse<object>.ErrorResponse("An error occurred while fetching services", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> CreateService(ServiceMasterDto serviceMasterDto)
{
_logger.LogInfo("CreateService called with Name: {ServiceName}", serviceMasterDto?.Name ?? "null");
try
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 1: Permission check
var hasManagePermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
if (!hasManagePermission)
{
_logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("You don't have access", "You don't have permission to take this action", 403);
}
// Step 2: Validate input
if (string.IsNullOrWhiteSpace(serviceMasterDto?.Name) ||
string.IsNullOrWhiteSpace(serviceMasterDto?.Description))
{
_logger.LogWarning("Invalid input data for service creation by employeeId: {EmployeeId}", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Invalid data", "Name and Description are required", 400);
}
// Step 3: Check for duplicate name
bool isExist = await _context.ServicesMasters
.AnyAsync(s => s.TenantId == tenantId && s.Name.ToLower() == serviceMasterDto.Name.ToLower());
if (isExist)
{
_logger.LogWarning("Duplicate service name '{ServiceName}' attempted by employeeId: {EmployeeId}", serviceMasterDto.Name, loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse(
$"Service with name '{serviceMasterDto.Name}' already exists",
$"Service with name '{serviceMasterDto.Name}' already exists", 400);
}
// Step 4: Save new service
ServicesMaster service = serviceMasterDto.ToServicesMasterFromServiceMasterDto(tenantId);
_context.ServicesMasters.Add(service);
await _context.SaveChangesAsync();
var response = service.ToServiceMasterVMFromServiceMaster();
_logger.LogInfo("New service '{ServiceName}' created successfully by employeeId: {EmployeeId}", service.Name, loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(response, "New service created successfully", 201);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating service");
return ApiResponse<object>.ErrorResponse("Failed to create service", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> UpdateService(Guid id, ServiceMasterDto serviceMasterDto)
{
_logger.LogInfo("UpdateService called for Id: {Id}", id);
try
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 1: Permission check
var hasPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
if (!hasPermission)
{
_logger.LogWarning("Access denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access denied", "You don't have permission to take this action", 403);
}
// Step 2: Input validation
if (serviceMasterDto == null || serviceMasterDto.Id != id ||
string.IsNullOrWhiteSpace(serviceMasterDto.Name) ||
string.IsNullOrWhiteSpace(serviceMasterDto.Description))
{
_logger.LogWarning("Invalid input data provided for UpdateService. Id: {Id}", id);
return ApiResponse<object>.ErrorResponse("Invalid input", "Please provide valid service data", 400);
}
// Step 3: Retrieve service
var service = await _context.ServicesMasters
.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<object>.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 = service.ToServiceMasterVMFromServiceMaster();
_logger.LogInfo("Service updated successfully. Id: {Id}, TenantId: {TenantId}", service.Id, tenantId);
return ApiResponse<object>.SuccessResponse(response, "Service updated successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while updating service Id: {Id}.", id);
return ApiResponse<object>.ErrorResponse("An error occurred while updating the service", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> DeleteService(Guid id, bool isActive)
{
_logger.LogInfo("DeleteService called with ServiceId: {ServiceId}, IsActive: {IsActive}", id, isActive);
try
{
// Step 1: Get current user and validate permission
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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<object>.ErrorResponse("Access denied", "You don't have permission to delete services", 403);
}
// Step 2: Check if the service exists
var service = await _context.ServicesMasters
.FirstOrDefaultAsync(s => s.Id == id && s.TenantId == tenantId);
if (service == null)
{
_logger.LogWarning("Service not found. ServiceId: {ServiceId}", id);
return ApiResponse<object>.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<object>.ErrorResponse("Cannot delete system-defined service", "This service is system-defined and cannot be deleted", 400);
}
// Step 3: Soft delete or restore
service.IsActive = isActive;
await _context.SaveChangesAsync();
var status = isActive ? "restored" : "deactivated";
_logger.LogInfo("Service {ServiceId} has been {Status} successfully by EmployeeId: {EmployeeId}", id, status, loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(new { }, $"Service {status} successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error occurred while deleting service {ServiceId}", id);
return ApiResponse<object>.ErrorResponse("Error deleting service", ex.Message, 500);
}
}
// -------------------------------- Activity Group --------------------------------
public async Task<ApiResponse<object>> GetActivityGroups()
{
_logger.LogInfo("GetActivityGroups called");
try
{
// Step 1: Get logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Fetch all activity groups for the tenant
var activityGroups = await _context.ActivityGroupMasters
.Include(ag => ag.ServicesMaster)
.Where(ag => ag.TenantId == tenantId && ag.IsActive)
.Select(ag => ag.ToActivityGroupMasterVMFromActivityGroupMaster(ag.ServicesMaster != null ? ag.ServicesMaster.Name : ""))
.ToListAsync();
_logger.LogInfo("{Count} activity group(s) fetched for tenantId: {TenantId}", activityGroups.Count, tenantId);
return ApiResponse<object>.SuccessResponse(activityGroups, $"{activityGroups.Count} record(s) of activity groups fetched successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching activity groups");
return ApiResponse<object>.ErrorResponse("An error occurred while fetching activity groups", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> CreateActivityGroup(ActivityGroupDto activityGroupDto)
{
_logger.LogInfo("CreateActivityGroup called with Name: {Name}", activityGroupDto?.Name ?? "null");
try
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 1: Check Manage Master permission
var hasPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
if (!hasPermission)
{
_logger.LogWarning("Access denied. EmployeeId: {EmployeeId} lacks Manage_Master permission", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access denied", "You don't have permission to take this action", 403);
}
// Step 2: Validate input
if (string.IsNullOrWhiteSpace(activityGroupDto?.Name) || string.IsNullOrWhiteSpace(activityGroupDto.Description))
{
_logger.LogWarning("Invalid input data for activity group by employeeId: {EmployeeId}", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Invalid input", "Name and Description are required", 400);
}
// Step 3: 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<object>.ErrorResponse(
$"Activity group with name '{activityGroupDto.Name}' already exists",
$"Activity group with name '{activityGroupDto.Name}' already exists", 400);
}
// Step 4: Map and persist
var activityGroup = activityGroupDto.ToActivityGroupMasterFromActivityGroupDto(tenantId);
_context.ActivityGroupMasters.Add(activityGroup);
await _context.SaveChangesAsync();
var service = await _context.ServicesMasters.FirstOrDefaultAsync(s => s.Id == activityGroup.ServiceId);
var response = activityGroup.ToActivityGroupMasterVMFromActivityGroupMaster(service?.Name ?? "");
_logger.LogInfo("New activity group '{Name}' created successfully by employeeId: {EmployeeId}", activityGroup.Name, loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(response, "New activity group created successfully", 201);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating activity group");
return ApiResponse<object>.ErrorResponse("Failed to create activity group", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> UpdateActivityGroup(Guid id, ActivityGroupDto activityGroupDto)
{
_logger.LogInfo("UpdateActivityGroup called for Id: {Id}", id);
try
{
// Step 1: Permission check
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var hasPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
if (!hasPermission)
{
_logger.LogWarning("Access denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access denied", "You don't have permission to take this action", 403);
}
// Step 2: Input validation
if (activityGroupDto == null || activityGroupDto.Id != id ||
string.IsNullOrWhiteSpace(activityGroupDto.Name) ||
string.IsNullOrWhiteSpace(activityGroupDto.Description))
{
_logger.LogWarning("Invalid input for activity group update. Id: {Id}", id);
return ApiResponse<object>.ErrorResponse("Invalid input", "Please provide valid data to update activity group", 400);
}
var service = await _context.ServicesMasters.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<object>.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.ServicesMaster)
.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<object>.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 = activityGroup.ToActivityGroupMasterVMFromActivityGroupMaster(service.Name ?? "");
_logger.LogInfo("Activity group updated successfully. Id: {Id}, TenantId: {TenantId}", activityGroup.Id, tenantId);
return ApiResponse<object>.SuccessResponse(response, "Activity group updated successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while updating activity group Id: {Id}", id);
return ApiResponse<object>.ErrorResponse("An error occurred while updating the activity group", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> DeleteActivityGroup(Guid id, bool isActive)
{
_logger.LogInfo("DeleteActivityGroup called with ActivityGroupId: {ActivityGroupId}, IsActive: {IsActive}", id, isActive);
try
{
// Step 1: Permission check
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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<object>.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<object>.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<object>.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<object>.SuccessResponse(new { }, $"Activity group {status} successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error occurred while deleting ActivityGroup {ActivityGroupId}", id);
return ApiResponse<object>.ErrorResponse("Error deleting activity group", ex.Message, 500);
}
}
// -------------------------------- Activity --------------------------------
public async Task<ApiResponse<object>> 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<object>.SuccessResponse(new List<ActivityVM>(), "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<ActivityCheckList>();
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<object>.SuccessResponse(activityVMs, $"{activityVMs.Count} activity records fetched successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred in GetActivitiesMaster");
return ApiResponse<object>.ErrorResponse("Failed to fetch activity records", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.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<CheckListVM> 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<object>.SuccessResponse(activityVM, "Activity created successfully", 201);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while creating activity");
return ApiResponse<object>.ErrorResponse("An error occurred while creating activity", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.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.LogWarning("Activity not found for ActivityId: {ActivityId}, TenantId: {TenantId}", id, tenantId);
return ApiResponse<object>.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<CheckListVM>();
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<object>.SuccessResponse(activityVM, "Activity updated successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception in UpdateActivity");
return ApiResponse<object>.ErrorResponse("Error updating activity", ex.Message, 500);
}
}
public async Task<ApiResponse<object>> 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<object>.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<object>.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<object>.SuccessResponse(new { }, $"Activity {status} successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error while deleting activity {ActivityId}", id);
return ApiResponse<object>.ErrorResponse("Error deleting activity", ex.Message, 500);
}
}
// -------------------------------- Contact Category --------------------------------
public async Task<ApiResponse<object>> CreateContactCategory(CreateContactCategoryDto contactCategoryDto)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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<object>.SuccessResponse(categoryVM, "Category Created Successfully", 200);
}
_logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to create an existing contact category.", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Category already existed", "Category already existed", 409);
}
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
}
public async Task<ApiResponse<object>> UpdateContactCategory(Guid id, UpdateContactCategoryDto contactCategoryDto)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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<object>.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<object>.ErrorResponse("Category not found", "Category not found", 404);
}
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
}
public async Task<ApiResponse<object>> GetContactCategoriesList()
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var categoryList = await _context.ContactCategoryMasters.Where(c => c.TenantId == tenantId).ToListAsync();
List<ContactCategoryVM> contactCategories = new List<ContactCategoryVM>();
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<object>.SuccessResponse(contactCategories, System.String.Format("{0} contact categories fetched successfully", contactCategories.Count), 200);
}
public async Task<ApiResponse<object>> GetContactCategoryById(Guid id)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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<object>.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<object>.ErrorResponse("Category not found", "Category not found", 404);
}
public async Task<ApiResponse<object>> DeleteContactCategory(Guid id)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
ContactCategoryMaster? contactCategory = await _context.ContactCategoryMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId);
if (contactCategory != null)
{
List<Contact>? existingContacts = await _context.Contacts.AsNoTracking().Where(c => c.ContactCategoryId == contactCategory.Id).ToListAsync();
if (existingContacts.Count > 0)
{
List<Contact>? contacts = new List<Contact>();
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<object>.SuccessResponse(new { }, "Category deleted successfully", 200);
}
// -------------------------------- Contact Tag --------------------------------
public async Task<ApiResponse<Object>> GetContactTags()
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var taglist = await _context.ContactTagMasters.Where(t => t.TenantId == tenantId).ToListAsync();
List<ContactTagVM> contactTags = new List<ContactTagVM>();
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<object>.SuccessResponse(contactTags, System.String.Format("{0} contact tags fetched successfully", contactTags.Count), 200);
}
public async Task<ApiResponse<object>> CreateContactTag(CreateContactTagDto contactTagDto)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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<object>.SuccessResponse(tagVM, "Tag Created Successfully", 200);
}
_logger.LogWarning("Employee ID {LoggedInEmployeeId} attempted to create an existing contact tag.", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Tag already existed", "Tag already existed", 409);
}
_logger.LogWarning("Employee with ID {LoggedInEmployeeId} sended empty payload", LoggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
}
public async Task<ApiResponse<object>> UpdateContactTag(Guid id, UpdateContactTagDto contactTagDto)
{
var tenantId = _userHelper.GetTenantId();
Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
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();
ContactTagVM contactTagVm = contactTag.ToContactTagVMFromContactTagMaster();
_logger.LogInfo("Contact tag master {ConatctTagId} updated successfully by employee {EmployeeId}", contactTagVm.Id, LoggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(contactTagVm, "Contact Tag master updated successfully", 200);
}
_logger.LogWarning("Contact Tag master {ContactTagId} not found in database", id);
return ApiResponse<object>.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<object>.ErrorResponse("User Send empty Payload", "User Send empty Payload", 400);
}
public async Task<ApiResponse<object>> DeleteContactTag(Guid id)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
ContactTagMaster? contactTag = await _context.ContactTagMasters.FirstOrDefaultAsync(c => c.Id == id && c.TenantId == tenantId);
if (contactTag != null)
{
List<ContactTagMapping>? 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<object>.SuccessResponse(new { }, "Tag deleted successfully", 200);
}
// -------------------------------- Work Status --------------------------------
public async Task<ApiResponse<object>> GetWorkStatusList()
{
_logger.LogInfo("GetWorkStatusList called.");
try
{
// Step 1: Get tenant and logged-in employee info
Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Check permission to view master data
bool hasViewPermission = await _permissionService.HasPermission(PermissionsMaster.ViewMasters, loggedInEmployee.Id);
if (!hasViewPermission)
{
_logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("You don't have access", "Don't have access to take action", 403);
}
// Step 3: 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 4: Return successful response
return ApiResponse<object>.SuccessResponse(
workStatusList,
$"{workStatusList.Count} work status records fetched successfully",
200
);
}
catch (Exception ex)
{
_logger.LogWarning("Error occurred while fetching work status list : {Error}", ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to fetch work status list", 500);
}
}
public async Task<ApiResponse<object>> CreateWorkStatus(CreateWorkStatusMasterDto createWorkStatusDto)
{
_logger.LogInfo("CreateWorkStatus called with Name: {Name}", createWorkStatusDto.Name ?? "");
try
{
// Step 1: Get tenant and logged-in employee
Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Check if user has permission to manage master data
var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id);
if (!hasManageMasterPermission)
{
_logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("You don't have access", "Don't have access to take action", 403);
}
// Step 3: 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<object>.ErrorResponse("Work status already exists", "Work status already exists", 400);
}
// Step 4: 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<object>.SuccessResponse(workStatus, "Work status created successfully", 200);
}
catch (Exception ex)
{
_logger.LogWarning("Error occurred while creating work status : {Error}", ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to create work status", 500);
}
}
public async Task<ApiResponse<object>> UpdateWorkStatus(Guid id, UpdateWorkStatusMasterDto updateWorkStatusDto)
{
_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<object>.ErrorResponse("Invalid data provided", "The provided work status ID is invalid", 400);
}
// Step 2: Get tenant and logged-in employee
Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 3: Check permissions
var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id);
if (!hasManageMasterPermission)
{
_logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have Manage Master permission.", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access denied", "You do not have permission to update this work status", 403);
}
// Step 4: 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<object>.ErrorResponse("Work status not found", "No work status found with the provided ID", 404);
}
// Step 5: 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<object>.ErrorResponse("Work status with the same name already exists", "Duplicate name", 400);
}
// Step 6: 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<object>.SuccessResponse(workStatus, "Work status updated successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while updating work status ID: {Id}", id);
return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to update the work status at this time", 500);
}
}
public async Task<ApiResponse<object>> DeleteWorkStatus(Guid id)
{
_logger.LogInfo("DeleteWorkStatus called for Id: {Id}", id);
try
{
// Step 1: Get current tenant and logged-in employee
Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Check permission to manage master data
var hasManageMasterPermission = await _permissionService.HasPermission(PermissionsMaster.ManageMasters, loggedInEmployee.Id);
if (!hasManageMasterPermission)
{
_logger.LogWarning("Delete denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id);
return ApiResponse<object>.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<object>.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<object>.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<object>.SuccessResponse(new { }, "Work status deleted successfully", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while deleting WorkStatus Id: {Id}", id);
return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to delete work status", 500);
}
}
}
}