Added the firebase to project created and update API

This commit is contained in:
ashutosh.nehete 2025-08-19 16:04:56 +05:30
parent f6ce8dd4f6
commit 455cbc5cd4
3 changed files with 392 additions and 1 deletions

View File

@ -2,6 +2,7 @@
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Projects;
using Marco.Pms.Services.Service.ServiceInterfaces;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;
@ -669,6 +670,7 @@ namespace Marco.Pms.Services.Service
}
// Project Controller
//Project Infra
public async Task SendModifyTaskMeaasgeAsync(List<Guid> workItemIds, string name, bool IsExist, Guid tenantId)
{
@ -771,7 +773,7 @@ namespace Marco.Pms.Services.Service
{
notificationFirebase = new Notification
{
Title = $"New Task Updated - {project?.ProjectName}",
Title = $"Task Updated - {project?.ProjectName}",
Body = $"{name} updated tasks {activityName} at {location}."
};
}
@ -1130,6 +1132,316 @@ namespace Marco.Pms.Services.Service
}
}
public async Task SendDeleteTaskMeaasgeAsync(Guid workItemId, string name, Guid tenantId)
{
using var scope = _serviceScopeFactory.CreateScope();
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
try
{
await using var _context = await _dbContextFactory.CreateDbContextAsync();
var workItem = await _context.WorkItems
.Include(wi => wi.ActivityMaster)
.Include(wi => wi.WorkArea)
.ThenInclude(wa => wa!.Floor)
.ThenInclude(f => f!.Building)
.FirstOrDefaultAsync(wi => workItemId == wi.Id &&
wi.TenantId == tenantId &&
wi.ActivityMaster != null &&
wi.WorkArea != null &&
wi.WorkArea.Floor != null &&
wi.WorkArea.Floor.Building != null);
if (workItem != null)
{
return;
}
var projectId = workItem!.WorkArea!.Floor!.Building!.ProjectId;
var projectTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ProjectAllocations
.Include(pa => pa.Project)
.Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
.GroupBy(pa => pa.ProjectId)
.Select(g => new
{
ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
}).FirstOrDefaultAsync();
});
var viewTaskRoleTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.RolePermissionMappings
.Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewTask)
.Select(rp => rp.ApplicationRoleId).ToListAsync();
});
var viewProjectInfraRoleTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.RolePermissionMappings
.Where(rp => rp.FeaturePermissionId == PermissionsMaster.ViewProjectInfra)
.Select(rp => rp.ApplicationRoleId).ToListAsync();
});
var manageProjectsRoleTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.RolePermissionMappings
.Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
.Select(rp => rp.ApplicationRoleId).ToListAsync();
});
await Task.WhenAll(projectTask, viewTaskRoleTask, manageProjectsRoleTask, viewProjectInfraRoleTask);
var viewTaskRoleIds = viewTaskRoleTask.Result;
var manageProjectsRoleIds = manageProjectsRoleTask.Result;
var viewProjectInfraRoleIds = viewProjectInfraRoleTask.Result;
var project = projectTask.Result;
var activityName = workItem.ActivityMaster!.ActivityName;
var buildingName = workItem.WorkArea.Floor.Building.Name;
var FloorName = workItem.WorkArea.Floor.FloorName;
var AreaName = workItem.WorkArea.AreaName;
var location = $"{buildingName} > {FloorName} > {AreaName}";
var buildingId = workItem.WorkArea.Floor.Building.Id;
var floorId = workItem.WorkArea.Floor.Id;
var areaId = workItem.WorkArea.Id;
List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();
var employeeIds = await _context.EmployeeRoleMappings
.Where(er =>
(projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) &&
(viewTaskRoleIds.Contains(er.RoleId) || viewProjectInfraRoleIds.Contains(er.RoleId)))
.Select(er => er.EmployeeId)
.ToListAsync();
Notification notificationFirebase = new Notification
{
Title = $"Task Deleted - {project?.ProjectName}",
Body = $"{name} deleted tasks {activityName} at {location}."
};
var data = new Dictionary<string, string>()
{
{ "Keyword", "Task_Modified" },
{ "ProjectId", projectId.ToString() },
{ "BuildingId", buildingId.ToString() },
{ "FloorId", floorId.ToString() },
{ "AreaId", areaId.ToString() },
};
// List of device registration tokens to send the message to
var registrationTokensForNotification = await _context.FCMTokenMappings.Where(ft => employeeIds.Contains(ft.EmployeeId)).Select(ft => ft.FcmToken).ToListAsync();
await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while get data for sending notification");
}
}
//Project Allocation
public async Task SendProjectAllocationMessageAsync(List<ProjectAllocation> projectAllocations, string name, Guid tenantId)
{
using var scope = _serviceScopeFactory.CreateScope();
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
try
{
await using var _context = await _dbContextFactory.CreateDbContextAsync();
foreach (var projectAllocation in projectAllocations)
{
var projectId = projectAllocation.ProjectId;
var projectTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ProjectAllocations
.Include(pa => pa.Project)
.Where(pa => pa.ProjectId == projectId && pa.IsActive && pa.Project != null)
.GroupBy(pa => pa.ProjectId)
.Select(g => new
{
ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
}).FirstOrDefaultAsync();
});
var manageTeamRoleTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.RolePermissionMappings
.Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageTeam)
.Select(rp => rp.ApplicationRoleId).ToListAsync();
});
var manageProjectsRoleTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.RolePermissionMappings
.Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
.Select(rp => rp.ApplicationRoleId).ToListAsync();
});
await Task.WhenAll(projectTask, manageTeamRoleTask, manageProjectsRoleTask);
var manageTeamRoleIds = manageTeamRoleTask.Result;
var manageProjectsRoleIds = manageProjectsRoleTask.Result;
var project = projectTask.Result;
List<Guid> projectAssignedEmployeeIds = project?.EmployeeIds ?? new List<Guid>();
var employeeIds = await _context.EmployeeRoleMappings
.Where(er =>
(projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)) && manageTeamRoleIds.Contains(er.RoleId))
.Select(er => er.EmployeeId)
.ToListAsync();
Notification notificationFirebase;
if (projectAllocation.IsActive)
{
notificationFirebase = new Notification
{
Title = $"Assigned to Project - {project?.ProjectName}",
Body = $"You have been assigned to the project \"{project?.ProjectName}\"."
};
}
else
{
notificationFirebase = new Notification
{
Title = $"Removed from Project - {project?.ProjectName}",
Body = $"{name} has removed you from the project \"{project?.ProjectName}\"."
};
}
var data = new Dictionary<string, string>()
{
{ "Keyword", "Team_Modefied" },
{ "ProjectId", projectId.ToString() }
};
// List of device registration tokens to send the message to
var registrationTokensForNotificationTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
var registrationTokensForNotification = await dbContext.FCMTokenMappings.Where(ft => projectAllocation.EmployeeId == ft.EmployeeId).Select(ft => ft.FcmToken).ToListAsync();
await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
});
var registrationTokensForDataTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
var registrationTokensForData = await dbContext.FCMTokenMappings.Where(ft => employeeIds.Contains(ft.EmployeeId)).Select(ft => ft.FcmToken).ToListAsync();
await SendMessageToMultipleDevicesOnlyDataAsync(registrationTokensForData, data);
});
await Task.WhenAll(registrationTokensForNotificationTask, registrationTokensForDataTask);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while get data for sending notification");
}
}
//Project Management
public async Task SendModifyProjectMessageAsync(Project project, string name, bool IsExist, Guid tenantId)
{
using var scope = _serviceScopeFactory.CreateScope();
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
try
{
await using var _context = await _dbContextFactory.CreateDbContextAsync();
var projectTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.ProjectAllocations
.Include(pa => pa.Project)
.Where(pa => pa.ProjectId == project.Id && pa.IsActive && pa.Project != null)
.GroupBy(pa => pa.ProjectId)
.Select(g => new
{
ProjectName = g.Select(pa => pa.Project!.Name).FirstOrDefault(),
EmployeeIds = g.Select(pa => pa.EmployeeId).Distinct().ToList()
}).FirstOrDefaultAsync();
});
var manageProjectsRoleTask = Task.Run(async () =>
{
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
return await dbContext.RolePermissionMappings
.Where(rp => rp.FeaturePermissionId == PermissionsMaster.ManageProject)
.Select(rp => rp.ApplicationRoleId).ToListAsync();
});
await Task.WhenAll(projectTask, manageProjectsRoleTask);
var manageProjectsRoleIds = manageProjectsRoleTask.Result;
var projectVM = projectTask.Result;
List<Guid> projectAssignedEmployeeIds = projectVM?.EmployeeIds ?? new List<Guid>();
var employeeIds = await _context.EmployeeRoleMappings
.Where(er =>
(projectAssignedEmployeeIds.Contains(er.EmployeeId) || manageProjectsRoleIds.Contains(er.RoleId)))
.Select(er => er.EmployeeId)
.ToListAsync();
Notification notificationFirebase;
if (IsExist)
{
notificationFirebase = new Notification
{
Title = $"Project Updated - {project.Name}",
Body = $"{name} updated the project \"{project.Name}\"."
};
}
else
{
notificationFirebase = new Notification
{
Title = $"Project Created - {project.Name}",
Body = $"A new project \"{project.Name}\" has been created by {name}."
};
}
var data = new Dictionary<string, string>()
{
{ "Keyword", "Project_Modefied" },
{ "ProjectId", project.Id.ToString() }
};
// List of device registration tokens to send the message to
var registrationTokensForNotification = await _context.FCMTokenMappings.Where(ft => employeeIds.Contains(ft.EmployeeId)).Select(ft => ft.FcmToken).ToListAsync();
await SendMessageToMultipleDevicesWithDataAsync(registrationTokensForNotification, notificationFirebase, data);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occured while get data for sending notification");
}
}
#region =================================================================== Helper Functions ===================================================================
@ -1140,6 +1452,7 @@ namespace Marco.Pms.Services.Service
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
try
{
registrationTokens = registrationTokens.Distinct().ToList();
var message = new MulticastMessage()
{
Tokens = registrationTokens,
@ -1191,6 +1504,7 @@ namespace Marco.Pms.Services.Service
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
try
{
registrationTokens = registrationTokens.Distinct().ToList();
var message = new MulticastMessage()
{
Tokens = registrationTokens,
@ -1241,6 +1555,7 @@ namespace Marco.Pms.Services.Service
var _logger = scope.ServiceProvider.GetRequiredService<ILoggingService>();
try
{
registrationTokens = registrationTokens.Distinct().ToList();
var message = new MulticastMessage()
{
Tokens = registrationTokens,

View File

@ -349,6 +349,9 @@ namespace Marco.Pms.Services.Service
public async Task<ApiResponse<object>> CreateProjectAsync(CreateProjectDto projectDto, Guid tenantId, Employee loggedInEmployee)
{
using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
// 1. Prepare data without I/O
var loggedInUserId = loggedInEmployee.Id;
var project = _mapper.Map<Project>(projectDto);
@ -385,6 +388,18 @@ namespace Marco.Pms.Services.Service
_logger.LogError(ex, "Project {ProjectId} was created, but a post-creation side-effect (caching/notification) failed. ", project.Id);
}
_ = Task.Run(async () =>
{
// --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
await _firebase.SendModifyProjectMessageAsync(project, name, false, tenantId);
});
// 4. Return a success response to the user as soon as the critical data is saved.
return ApiResponse<object>.SuccessResponse(_mapper.Map<ProjectDto>(project), "Project created successfully.", 200);
}
@ -398,6 +413,9 @@ namespace Marco.Pms.Services.Service
/// <returns>An ApiResponse confirming the update or an appropriate error.</returns>
public async Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee)
{
using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
try
{
// --- Step 1: Fetch the Existing Entity from the Database ---
@ -449,6 +467,18 @@ namespace Marco.Pms.Services.Service
// 4a. Update Cache
await UpdateCacheInBackground(existingProject);
_ = Task.Run(async () =>
{
// --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
await _firebase.SendModifyProjectMessageAsync(existingProject, name, true, tenantId);
});
// --- Step 5: Return Success Response Immediately ---
// The client gets a fast response without waiting for caching or SignalR.
return ApiResponse<object>.SuccessResponse(projectDto, "Project updated successfully.", 200);
@ -618,6 +648,9 @@ namespace Marco.Pms.Services.Service
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
public async Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> allocationsDto, Guid tenantId, Employee loggedInEmployee)
{
using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
// --- Step 1: Input Validation ---
if (allocationsDto == null || !allocationsDto.Any())
{
@ -712,6 +745,17 @@ namespace Marco.Pms.Services.Service
// --- Step 5: Map results and return success ---
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
_ = Task.Run(async () =>
{
// --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
await _firebase.SendProjectAllocationMessageAsync(processedAllocations, name, tenantId);
});
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Allocations managed successfully.", 200);
}
@ -799,6 +843,9 @@ namespace Marco.Pms.Services.Service
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
public async Task<ApiResponse<List<ProjectAllocationVM>>> AssigneProjectsToEmployeeAsync(List<ProjectsAllocationDto> allocationsDto, Guid employeeId, Guid tenantId, Employee loggedInEmployee)
{
using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
// --- Step 1: Input Validation ---
if (allocationsDto == null || !allocationsDto.Any() || employeeId == Guid.Empty)
{
@ -892,6 +939,17 @@ namespace Marco.Pms.Services.Service
// --- Step 6: Map results using AutoMapper and return success ---
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
_ = Task.Run(async () =>
{
// --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
await _firebase.SendProjectAllocationMessageAsync(processedAllocations, name, tenantId);
});
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Assignments managed successfully.", 200);
}
@ -1417,6 +1475,9 @@ namespace Marco.Pms.Services.Service
public async Task<ServiceResponse> DeleteProjectTaskAsync(Guid id, Guid tenantId, Employee loggedInEmployee)
{
using var scope = _serviceScopeFactory.CreateScope();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
// 1. Fetch the task and its parent data in a single query.
// This is still a major optimization, avoiding a separate query for the floor/building.
WorkItem? task = await _context.WorkItems
@ -1489,7 +1550,17 @@ namespace Marco.Pms.Services.Service
cacheTasks.Add(_cache.DeleteProjectByIdAsync(building.ProjectId));
}
await Task.WhenAll(cacheTasks);
_ = Task.Run(async () =>
{
// --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var name = $"{loggedInEmployee.FirstName} {loggedInEmployee.LastName}";
await _firebase.SendDeleteTaskMeaasgeAsync(task.Id, name, tenantId);
});
// 7. Return the final success response.
return new ServiceResponse
{

View File

@ -1,4 +1,5 @@
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Projects;
namespace Marco.Pms.Services.Service.ServiceInterfaces
{
@ -14,5 +15,9 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task SendModifyWorkAreaMeaasgeAsync(Guid workAreaId, string name, bool IsExist, Guid tenantId);
Task SendModifyFloorMeaasgeAsync(Guid floorId, string name, bool IsExist, Guid tenantId);
Task SendModifyBuildingMeaasgeAsync(Guid buildingId, string name, bool IsExist, Guid tenantId);
Task SendDeleteTaskMeaasgeAsync(Guid workItemId, string name, Guid tenantId);
Task SendProjectAllocationMessageAsync(List<ProjectAllocation> projectAllocations, string name, Guid tenantId);
Task SendModifyProjectMessageAsync(Project project, string name, bool IsExist, Guid tenantId);
}
}