Added the caching project report API and added expiry in workItems in cache

This commit is contained in:
ashutosh.nehete 2025-07-09 12:39:27 +05:30
parent 3e316ef388
commit 669500e57e
12 changed files with 411 additions and 158 deletions

View File

@ -106,6 +106,7 @@ namespace Marco.Pms.CacheHelper
workAreaMongoList.Add(new WorkAreaMongoDB workAreaMongoList.Add(new WorkAreaMongoDB
{ {
Id = wa.Id.ToString(), Id = wa.Id.ToString(),
FloorId = wa.FloorId.ToString(),
AreaName = wa.AreaName, AreaName = wa.AreaName,
PlannedWork = waPlanned, PlannedWork = waPlanned,
CompletedWork = waCompleted CompletedWork = waCompleted
@ -118,6 +119,7 @@ namespace Marco.Pms.CacheHelper
floorMongoList.Add(new FloorMongoDB floorMongoList.Add(new FloorMongoDB
{ {
Id = floor.Id.ToString(), Id = floor.Id.ToString(),
BuildingId = floor.BuildingId.ToString(),
FloorName = floor.FloorName, FloorName = floor.FloorName,
PlannedWork = floorPlanned, PlannedWork = floorPlanned,
CompletedWork = floorCompleted, CompletedWork = floorCompleted,
@ -131,6 +133,7 @@ namespace Marco.Pms.CacheHelper
buildingMongoList.Add(new BuildingMongoDB buildingMongoList.Add(new BuildingMongoDB
{ {
Id = building.Id.ToString(), Id = building.Id.ToString(),
ProjectId = building.ProjectId.ToString(),
BuildingName = building.Name, BuildingName = building.Name,
Description = building.Description, Description = building.Description,
PlannedWork = buildingPlanned, PlannedWork = buildingPlanned,
@ -477,7 +480,59 @@ namespace Marco.Pms.CacheHelper
var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions); var result = await _projetCollection.UpdateOneAsync(filter, update, updateOptions);
} }
public async Task<WorkAreaInfoMongoDB?> GetBuildingAndFloorByWorkAreaIdFromCache(Guid workAreaId)
{
var pipeline = new[]
{
new BsonDocument("$unwind", "$Buildings"),
new BsonDocument("$unwind", "$Buildings.Floors"),
new BsonDocument("$unwind", "$Buildings.Floors.WorkAreas"),
new BsonDocument("$match", new BsonDocument("Buildings.Floors.WorkAreas._id", workAreaId.ToString())),
new BsonDocument("$project", new BsonDocument
{
{ "_id", 0 },
{ "ProjectId", "$_id" },
{ "ProjectName", "$Name" },
{ "PlannedWork", "$PlannedWork" },
{ "CompletedWork", "$CompletedWork" },
{
"Building", new BsonDocument
{
{ "_id", "$Buildings._id" },
{ "BuildingName", "$Buildings.BuildingName" },
{ "Description", "$Buildings.Description" },
{ "PlannedWork", "$Buildings.PlannedWork" },
{ "CompletedWork", "$Buildings.CompletedWork" }
}
},
{
"Floor", new BsonDocument
{
{ "_id", "$Buildings.Floors._id" },
{ "FloorName", "$Buildings.Floors.FloorName" },
{ "PlannedWork", "$Buildings.Floors.PlannedWork" },
{ "CompletedWork", "$Buildings.Floors.CompletedWork" }
}
},
{ "WorkArea", "$Buildings.Floors.WorkAreas" }
})
};
var result = await _projetCollection.Aggregate<WorkAreaInfoMongoDB>(pipeline).FirstOrDefaultAsync();
if (result == null)
return null;
return result;
}
public async Task<List<WorkItemMongoDB>> GetWorkItemsByWorkAreaIdsFromCache(List<Guid> workAreaIds)
{
var stringWorkAreaIds = workAreaIds.Select(wa => wa.ToString()).ToList();
var filter = Builders<WorkItemMongoDB>.Filter.In(w => w.WorkAreaId, stringWorkAreaIds);
var workItems = await _taskCollection // replace with your actual collection name
.Find(filter)
.ToListAsync();
return workItems;
}
// ------------------------------------------------------- WorkItem ------------------------------------------------------- // ------------------------------------------------------- WorkItem -------------------------------------------------------
@ -485,12 +540,14 @@ namespace Marco.Pms.CacheHelper
{ {
var activityIds = workItems.Select(wi => wi.ActivityId).ToList(); var activityIds = workItems.Select(wi => wi.ActivityId).ToList();
var workCategoryIds = workItems.Select(wi => wi.WorkCategoryId).ToList(); var workCategoryIds = workItems.Select(wi => wi.WorkCategoryId).ToList();
var workItemIds = workItems.Select(wi => wi.Id).ToList();
// fetching Activity master // fetching Activity master
var activities = await _context.ActivityMasters.Where(a => activityIds.Contains(a.Id)).ToListAsync() ?? new List<ActivityMaster>(); var activities = await _context.ActivityMasters.Where(a => activityIds.Contains(a.Id)).ToListAsync() ?? new List<ActivityMaster>();
// Fetching Work Category // Fetching Work Category
var workCategories = await _context.WorkCategoryMasters.Where(wc => workCategoryIds.Contains(wc.Id)).ToListAsync() ?? new List<WorkCategoryMaster>(); var workCategories = await _context.WorkCategoryMasters.Where(wc => workCategoryIds.Contains(wc.Id)).ToListAsync() ?? new List<WorkCategoryMaster>();
var task = await _context.TaskAllocations.Where(t => workItemIds.Contains(t.WorkItemId) && t.AssignmentDate == DateTime.UtcNow).ToListAsync();
var todaysAssign = task.Sum(t => t.PlannedTask);
foreach (WorkItem workItem in workItems) foreach (WorkItem workItem in workItems)
{ {
var activity = activities.FirstOrDefault(a => a.Id == workItem.ActivityId) ?? new ActivityMaster(); var activity = activities.FirstOrDefault(a => a.Id == workItem.ActivityId) ?? new ActivityMaster();
@ -501,10 +558,11 @@ namespace Marco.Pms.CacheHelper
Builders<WorkItemMongoDB>.Update.Set(r => r.WorkAreaId, workItem.WorkAreaId.ToString()), Builders<WorkItemMongoDB>.Update.Set(r => r.WorkAreaId, workItem.WorkAreaId.ToString()),
Builders<WorkItemMongoDB>.Update.Set(r => r.ParentTaskId, (workItem.ParentTaskId != null ? workItem.ParentTaskId.ToString() : null)), Builders<WorkItemMongoDB>.Update.Set(r => r.ParentTaskId, (workItem.ParentTaskId != null ? workItem.ParentTaskId.ToString() : null)),
Builders<WorkItemMongoDB>.Update.Set(r => r.PlannedWork, workItem.PlannedWork), Builders<WorkItemMongoDB>.Update.Set(r => r.PlannedWork, workItem.PlannedWork),
Builders<WorkItemMongoDB>.Update.Set(r => r.TodaysAssigned, 0), Builders<WorkItemMongoDB>.Update.Set(r => r.TodaysAssigned, todaysAssign),
Builders<WorkItemMongoDB>.Update.Set(r => r.CompletedWork, workItem.CompletedWork), Builders<WorkItemMongoDB>.Update.Set(r => r.CompletedWork, workItem.CompletedWork),
Builders<WorkItemMongoDB>.Update.Set(r => r.Description, workItem.Description), Builders<WorkItemMongoDB>.Update.Set(r => r.Description, workItem.Description),
Builders<WorkItemMongoDB>.Update.Set(r => r.TaskDate, workItem.TaskDate), Builders<WorkItemMongoDB>.Update.Set(r => r.TaskDate, workItem.TaskDate),
Builders<WorkItemMongoDB>.Update.Set(r => r.ExpireAt, DateTime.UtcNow.Date.AddDays(1)),
Builders<WorkItemMongoDB>.Update.Set(r => r.ActivityMaster, new ActivityMasterMongoDB Builders<WorkItemMongoDB>.Update.Set(r => r.ActivityMaster, new ActivityMasterMongoDB
{ {
Id = activity.Id.ToString(), Id = activity.Id.ToString(),
@ -520,6 +578,16 @@ namespace Marco.Pms.CacheHelper
); );
var options = new UpdateOptions { IsUpsert = true }; var options = new UpdateOptions { IsUpsert = true };
var result = await _taskCollection.UpdateOneAsync(filter, updates, options); var result = await _taskCollection.UpdateOneAsync(filter, updates, options);
if (result.UpsertedId != null)
{
var indexKeys = Builders<WorkItemMongoDB>.IndexKeys.Ascending(x => x.ExpireAt);
var indexOptions = new CreateIndexOptions
{
ExpireAfter = TimeSpan.Zero // required for fixed expiration time
};
var indexModel = new CreateIndexModel<WorkItemMongoDB>(indexKeys, indexOptions);
await _taskCollection.Indexes.CreateOneAsync(indexModel);
}
} }
} }
public async Task<List<WorkItemMongoDB>> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId) public async Task<List<WorkItemMongoDB>> GetWorkItemDetailsByWorkAreaFromCache(Guid workAreaId)

View File

@ -7,12 +7,16 @@
public string? Description { get; set; } public string? Description { get; set; }
public double PlannedWork { get; set; } public double PlannedWork { get; set; }
public double CompletedWork { get; set; } public double CompletedWork { get; set; }
public string ProjectId { get; set; } = string.Empty;
public List<FloorMongoDB> Floors { get; set; } = new List<FloorMongoDB>(); public List<FloorMongoDB> Floors { get; set; } = new List<FloorMongoDB>();
} }
public class BuildingMongoDBVM public class BuildingMongoDBVM
{ {
public string Id { get; set; } = string.Empty; public string Id { get; set; } = string.Empty;
public string? Name { get; set; } public string? BuildingName { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public double PlannedWork { get; set; }
public double CompletedWork { get; set; }
public string ProjectId { get; set; } = string.Empty;
} }
} }

View File

@ -3,6 +3,7 @@
public class FloorMongoDB public class FloorMongoDB
{ {
public string Id { get; set; } = string.Empty; public string Id { get; set; } = string.Empty;
public string BuildingId { get; set; } = string.Empty;
public string? FloorName { get; set; } public string? FloorName { get; set; }
public double PlannedWork { get; set; } public double PlannedWork { get; set; }
public double CompletedWork { get; set; } public double CompletedWork { get; set; }
@ -12,6 +13,9 @@
public class FloorMongoDBVM public class FloorMongoDBVM
{ {
public string Id { get; set; } = string.Empty; public string Id { get; set; } = string.Empty;
public string BuildingId { get; set; } = string.Empty;
public string? FloorName { get; set; } public string? FloorName { get; set; }
public double PlannedWork { get; set; }
public double CompletedWork { get; set; }
} }
} }

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.MongoDBModels
{
public class WorkAreaInfoMongoDB
{
public string ProjectId { get; set; } = string.Empty;
public string? ProjectName { get; set; }
public BuildingMongoDBVM? Building { get; set; }
public FloorMongoDBVM? Floor { get; set; }
public WorkAreaMongoDB? WorkArea { get; set; }
public double CompletedWork { get; set; }
public double PlannedWork { get; set; }
}
}

View File

@ -3,6 +3,7 @@
public class WorkAreaMongoDB public class WorkAreaMongoDB
{ {
public string Id { get; set; } = string.Empty; public string Id { get; set; } = string.Empty;
public string FloorId { get; set; } = string.Empty;
public string? AreaName { get; set; } public string? AreaName { get; set; }
public double PlannedWork { get; set; } public double PlannedWork { get; set; }
public double CompletedWork { get; set; } public double CompletedWork { get; set; }

View File

@ -12,5 +12,6 @@
public double CompletedWork { get; set; } = 0; public double CompletedWork { get; set; } = 0;
public string? Description { get; set; } public string? Description { get; set; }
public DateTime TaskDate { get; set; } public DateTime TaskDate { get; set; }
public DateTime ExpireAt { get; set; } = DateTime.UtcNow.Date.AddDays(1);
} }
} }

View File

@ -237,6 +237,10 @@ namespace MarcoBMS.Services.Controllers
.Include(c => c.ProjectStatus) .Include(c => c.ProjectStatus)
.FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id); .FirstOrDefaultAsync(c => c.TenantId == tenantId && c.Id == id);
projectVM = GetProjectViewModel(project); projectVM = GetProjectViewModel(project);
if (project != null)
{
await _cache.AddProjectDetails(project);
}
} }
else else
{ {

View File

@ -1,12 +1,10 @@
using System.Data; using System.Data;
using System.Globalization;
using Marco.Pms.DataAccess.Data; using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Dtos.Mail; using Marco.Pms.Model.Dtos.Mail;
using Marco.Pms.Model.Employees; using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Mail; using Marco.Pms.Model.Mail;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Report; using Marco.Pms.Services.Helpers;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -26,13 +24,15 @@ namespace Marco.Pms.Services.Controllers
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly IWebHostEnvironment _env; private readonly IWebHostEnvironment _env;
public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env) private readonly ReportHelper _reportHelper;
public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env, ReportHelper reportHelper)
{ {
_context = context; _context = context;
_emailSender = emailSender; _emailSender = emailSender;
_logger = logger; _logger = logger;
_userHelper = userHelper; _userHelper = userHelper;
_env = env; _env = env;
_reportHelper = reportHelper;
} }
[HttpPost("set-mail")] [HttpPost("set-mail")]
@ -151,7 +151,6 @@ namespace Marco.Pms.Services.Controllers
/// <returns>An ApiResponse indicating the success or failure of retrieving statistics and sending the email.</returns> /// <returns>An ApiResponse indicating the success or failure of retrieving statistics and sending the email.</returns>
private async Task<ApiResponse<object>> GetProjectStatistics(Guid projectId, List<string> recipientEmails, string body, string subject, Guid tenantId) private async Task<ApiResponse<object>> GetProjectStatistics(Guid projectId, List<string> recipientEmails, string body, string subject, Guid tenantId)
{ {
DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date;
if (projectId == Guid.Empty) if (projectId == Guid.Empty)
{ {
@ -159,161 +158,15 @@ namespace Marco.Pms.Services.Controllers
return ApiResponse<object>.ErrorResponse("Provided empty Project ID.", "Provided empty Project ID.", 400); return ApiResponse<object>.ErrorResponse("Provided empty Project ID.", "Provided empty Project ID.", 400);
} }
var project = await _context.Projects
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == projectId);
if (project == null) var statisticReport = await _reportHelper.GetDailyProjectReport(projectId, tenantId);
if (statisticReport == null)
{ {
_logger.LogWarning("User attempted to fetch project progress for project ID {ProjectId} but not found.", projectId); _logger.LogWarning("User attempted to fetch project progress for project ID {ProjectId} but not found.", projectId);
return ApiResponse<object>.ErrorResponse("Project not found.", "Project not found.", 404); return ApiResponse<object>.ErrorResponse("Project not found.", "Project not found.", 404);
} }
var statisticReport = new ProjectStatisticReport
{
Date = reportDate,
ProjectName = project.Name ?? "",
TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture)
};
// Preload relevant data
var projectAllocations = await _context.ProjectAllocations
.Include(p => p.Employee)
.Where(p => p.ProjectId == project.Id && p.IsActive)
.ToListAsync();
var assignedEmployeeIds = projectAllocations.Select(p => p.EmployeeId).ToHashSet();
var attendances = await _context.Attendes
.AsNoTracking()
.Where(a => a.ProjectID == project.Id && a.InTime != null && a.InTime.Value.Date == reportDate)
.ToListAsync();
var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet();
var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet();
var regularizationIds = attendances
.Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
.Select(a => a.EmployeeID).Distinct().ToHashSet();
// Preload buildings, floors, areas
var buildings = await _context.Buildings.Where(b => b.ProjectId == project.Id).ToListAsync();
var buildingIds = buildings.Select(b => b.Id).ToList();
var floors = await _context.Floor.Where(f => buildingIds.Contains(f.BuildingId)).ToListAsync();
var floorIds = floors.Select(f => f.Id).ToList();
var areas = await _context.WorkAreas.Where(a => floorIds.Contains(a.FloorId)).ToListAsync();
var areaIds = areas.Select(a => a.Id).ToList();
var workItems = await _context.WorkItems
.Include(w => w.ActivityMaster)
.Where(w => areaIds.Contains(w.WorkAreaId))
.ToListAsync();
var itemIds = workItems.Select(i => i.Id).ToList();
var tasks = await _context.TaskAllocations
.Where(t => itemIds.Contains(t.WorkItemId))
.ToListAsync();
var taskIds = tasks.Select(t => t.Id).ToList();
var taskMembers = await _context.TaskMembers
.Include(m => m.Employee)
.Where(m => taskIds.Contains(m.TaskAllocationId))
.ToListAsync();
// Aggregate data
double totalPlannedWork = workItems.Sum(w => w.PlannedWork);
double totalCompletedWork = workItems.Sum(w => w.CompletedWork);
var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList();
var reportPending = tasks.Where(t => t.ReportedDate == null).ToList();
double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask);
double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask);
var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList();
var jobRoles = await _context.JobRoles
.Where(j => j.TenantId == project.TenantId && jobRoleIds.Contains(j.Id))
.ToListAsync();
// Team on site
var teamOnSite = jobRoles
.Select(role =>
{
var count = projectAllocations.Count(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId));
return new TeamOnSite { RoleName = role.Name, NumberofEmployees = count };
})
.OrderByDescending(t => t.NumberofEmployees)
.ToList();
// Task details
var performedTasks = todayAssignedTasks.Select(task =>
{
var workItem = workItems.FirstOrDefault(w => w.Id == task.WorkItemId);
var area = areas.FirstOrDefault(a => a.Id == workItem?.WorkAreaId);
var floor = floors.FirstOrDefault(f => f.Id == area?.FloorId);
var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId);
string activityName = workItem?.ActivityMaster?.ActivityName ?? "";
string location = $"{building?.Name} > {floor?.FloorName}</span><br/><span style=\"color: gray; font-size: small; padding-left: 10px;\"> {floor?.FloorName}-{area?.AreaName}";
double pending = (workItem?.PlannedWork ?? 0) - (workItem?.CompletedWork ?? 0);
var taskTeam = taskMembers
.Where(m => m.TaskAllocationId == task.Id)
.Select(m =>
{
string name = $"{m.Employee?.FirstName ?? ""} {m.Employee?.LastName ?? ""}";
var role = jobRoles.FirstOrDefault(r => r.Id == m.Employee?.JobRoleId);
return new TaskTeam { Name = name, RoleName = role?.Name ?? "" };
})
.ToList();
return new PerformedTask
{
Activity = activityName,
Location = location,
AssignedToday = task.PlannedTask,
CompletedToday = task.CompletedTask,
Pending = pending,
Comment = task.Description,
Team = taskTeam
};
}).ToList();
// Attendance details
var performedAttendance = attendances.Select(att =>
{
var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID);
var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId);
string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}";
return new PerformedAttendance
{
Name = name,
RoleName = role?.Name ?? "",
InTime = att.InTime ?? DateTime.UtcNow,
OutTime = att.OutTime,
Comment = att.Comment
};
}).ToList();
// Fill report
statisticReport.TodaysAttendances = checkedInEmployeeIds.Count;
statisticReport.TotalEmployees = assignedEmployeeIds.Count;
statisticReport.RegularizationPending = regularizationIds.Count;
statisticReport.CheckoutPending = checkoutPendingIds.Count;
statisticReport.TotalPlannedWork = totalPlannedWork;
statisticReport.TotalCompletedWork = totalCompletedWork;
statisticReport.TotalPlannedTask = totalPlannedTask;
statisticReport.TotalCompletedTask = totalCompletedTask;
statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0;
statisticReport.TodaysAssignTasks = todayAssignedTasks.Count;
statisticReport.ReportPending = reportPending.Count;
statisticReport.TeamOnSite = teamOnSite;
statisticReport.PerformedTasks = performedTasks;
statisticReport.PerformedAttendance = performedAttendance;
// Send Email // Send Email
var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, subject, statisticReport); var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, subject, statisticReport);
var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee(); var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee();

View File

@ -19,7 +19,7 @@ COPY ["Marco.Pms.Services/Marco.Pms.Services.csproj", "Marco.Pms.Services/"]
COPY ["Marco.Pms.DataAccess/Marco.Pms.DataAccess.csproj", "Marco.Pms.DataAccess/"] COPY ["Marco.Pms.DataAccess/Marco.Pms.DataAccess.csproj", "Marco.Pms.DataAccess/"]
COPY ["Marco.Pms.Model/Marco.Pms.Model.csproj", "Marco.Pms.Model/"] COPY ["Marco.Pms.Model/Marco.Pms.Model.csproj", "Marco.Pms.Model/"]
COPY ["Marco.Pms.Utility/Marco.Pms.Utility.csproj", "Marco.Pms.Utility/"] COPY ["Marco.Pms.Utility/Marco.Pms.Utility.csproj", "Marco.Pms.Utility/"]
COPY ["Marco.Pms.Utility/Marco.Pms.CacheHelper.csproj", "Marco.Pms.CacheHelper/"] COPY ["Marco.Pms.CacheHelper/Marco.Pms.CacheHelper.csproj", "Marco.Pms.CacheHelper/"]
RUN dotnet restore "./Marco.Pms.Services/Marco.Pms.Services.csproj" RUN dotnet restore "./Marco.Pms.Services/Marco.Pms.Services.csproj"
COPY . . COPY . .
WORKDIR "/src/Marco.Pms.Services" WORKDIR "/src/Marco.Pms.Services"

View File

@ -120,6 +120,36 @@ namespace Marco.Pms.Services.Helpers
_logger.LogWarning("Error occured while updating planned work and completed work in building infra form Cache: {Error}", ex.Message); _logger.LogWarning("Error occured while updating planned work and completed work in building infra form Cache: {Error}", ex.Message);
} }
} }
public async Task<WorkAreaInfoMongoDB?> GetBuildingAndFloorByWorkAreaId(Guid workAreaId)
{
try
{
var response = await _projectCache.GetBuildingAndFloorByWorkAreaIdFromCache(workAreaId);
return response;
}
catch (Exception ex)
{
_logger.LogWarning("Error occured while fetching workArea Details using its ID form Cache: {Error}", ex.Message);
return null;
}
}
public async Task<List<WorkItemMongoDB>?> GetWorkItemsByWorkAreaIds(List<Guid> workAreaIds)
{
try
{
var response = await _projectCache.GetWorkItemsByWorkAreaIdsFromCache(workAreaIds);
if (response.Count > 0)
{
return response;
}
return null;
}
catch (Exception ex)
{
_logger.LogWarning("Error occured while fetching workItems list using workArea IDs list form Cache: {Error}", ex.Message);
return null;
}
}
// ------------------------------------------------------- WorkItem ------------------------------------------------------- // ------------------------------------------------------- WorkItem -------------------------------------------------------

View File

@ -0,0 +1,274 @@
using System.Globalization;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.MongoDBModels;
using Marco.Pms.Model.ViewModels.Report;
using Microsoft.EntityFrameworkCore;
namespace Marco.Pms.Services.Helpers
{
public class ReportHelper
{
private readonly ApplicationDbContext _context;
private readonly CacheUpdateHelper _cache;
public ReportHelper(CacheUpdateHelper cache, ApplicationDbContext context)
{
_cache = cache;
_context = context;
}
public async Task<ProjectStatisticReport?> GetDailyProjectReport(Guid projectId, Guid tenantId)
{
// await _cache.GetBuildingAndFloorByWorkAreaId();
DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date;
var project = await _cache.GetProjectDetails(projectId);
if (project == null)
{
var projectSQL = await _context.Projects
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == projectId && p.TenantId == tenantId);
if (projectSQL != null)
{
project = new ProjectMongoDB
{
Id = projectSQL.Id.ToString(),
Name = projectSQL.Name,
ShortName = projectSQL.ShortName,
ProjectAddress = projectSQL.ProjectAddress,
ContactPerson = projectSQL.ContactPerson
};
await _cache.AddProjectDetails(projectSQL);
}
}
if (project != null)
{
var statisticReport = new ProjectStatisticReport
{
Date = reportDate,
ProjectName = project.Name ?? "",
TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture)
};
// Preload relevant data
var projectAllocations = await _context.ProjectAllocations
.Include(p => p.Employee)
.Where(p => p.ProjectId == projectId && p.IsActive)
.ToListAsync();
var assignedEmployeeIds = projectAllocations.Select(p => p.EmployeeId).ToHashSet();
var attendances = await _context.Attendes
.AsNoTracking()
.Where(a => a.ProjectID == projectId && a.InTime != null && a.InTime.Value.Date == reportDate)
.ToListAsync();
var checkedInEmployeeIds = attendances.Select(a => a.EmployeeID).Distinct().ToHashSet();
var checkoutPendingIds = attendances.Where(a => a.OutTime == null).Select(a => a.EmployeeID).Distinct().ToHashSet();
var regularizationIds = attendances
.Where(a => a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
.Select(a => a.EmployeeID).Distinct().ToHashSet();
// Preload buildings, floors, areas
List<BuildingMongoDBVM>? buildings = null;
List<FloorMongoDBVM>? floors = null;
List<WorkAreaMongoDB>? areas = null;
List<WorkItemMongoDB>? workItems = null;
// Fetch Buildings
buildings = project.Buildings
.Select(b => new BuildingMongoDBVM
{
Id = b.Id,
ProjectId = b.ProjectId,
BuildingName = b.BuildingName,
Description = b.Description
}).ToList();
if (buildings == null)
{
buildings = await _context.Buildings
.Where(b => b.ProjectId == projectId)
.Select(b => new BuildingMongoDBVM
{
Id = b.Id.ToString(),
ProjectId = b.ProjectId.ToString(),
BuildingName = b.Name,
Description = b.Description
})
.ToListAsync();
}
// fetch Floors
floors = project.Buildings
.SelectMany(b => b.Floors.Select(f => new FloorMongoDBVM
{
Id = f.Id.ToString(),
BuildingId = f.BuildingId,
FloorName = f.FloorName
})).ToList();
if (floors == null)
{
var buildingIds = buildings.Select(b => Guid.Parse(b.Id)).ToList();
floors = await _context.Floor
.Where(f => buildingIds.Contains(f.BuildingId))
.Select(f => new FloorMongoDBVM
{
Id = f.Id.ToString(),
BuildingId = f.BuildingId.ToString(),
FloorName = f.FloorName
})
.ToListAsync();
}
// fetch Work Areas
areas = project.Buildings
.SelectMany(b => b.Floors)
.SelectMany(f => f.WorkAreas).ToList();
if (areas == null)
{
var floorIds = floors.Select(f => Guid.Parse(f.Id)).ToList();
areas = await _context.WorkAreas
.Where(a => floorIds.Contains(a.FloorId))
.Select(wa => new WorkAreaMongoDB
{
Id = wa.Id.ToString(),
FloorId = wa.FloorId.ToString(),
AreaName = wa.AreaName,
})
.ToListAsync();
}
var areaIds = areas.Select(a => Guid.Parse(a.Id)).ToList();
// fetch Work Items
workItems = await _cache.GetWorkItemsByWorkAreaIds(areaIds);
if (workItems == null)
{
workItems = await _context.WorkItems
.Include(w => w.ActivityMaster)
.Where(w => areaIds.Contains(w.WorkAreaId))
.Select(wi => new WorkItemMongoDB
{
Id = wi.Id.ToString(),
WorkAreaId = wi.WorkAreaId.ToString(),
PlannedWork = wi.PlannedWork,
CompletedWork = wi.CompletedWork,
Description = wi.Description,
TaskDate = wi.TaskDate,
ActivityMaster = new ActivityMasterMongoDB
{
ActivityName = wi.ActivityMaster != null ? wi.ActivityMaster.ActivityName : null,
UnitOfMeasurement = wi.ActivityMaster != null ? wi.ActivityMaster.UnitOfMeasurement : null
}
})
.ToListAsync();
}
var itemIds = workItems.Select(i => Guid.Parse(i.Id)).ToList();
var tasks = await _context.TaskAllocations
.Where(t => itemIds.Contains(t.WorkItemId))
.ToListAsync();
var taskIds = tasks.Select(t => t.Id).ToList();
var taskMembers = await _context.TaskMembers
.Include(m => m.Employee)
.Where(m => taskIds.Contains(m.TaskAllocationId))
.ToListAsync();
// Aggregate data
double totalPlannedWork = workItems.Sum(w => w.PlannedWork);
double totalCompletedWork = workItems.Sum(w => w.CompletedWork);
var todayAssignedTasks = tasks.Where(t => t.AssignmentDate.Date == reportDate).ToList();
var reportPending = tasks.Where(t => t.ReportedDate == null).ToList();
double totalPlannedTask = todayAssignedTasks.Sum(t => t.PlannedTask);
double totalCompletedTask = todayAssignedTasks.Sum(t => t.CompletedTask);
var jobRoleIds = projectAllocations.Select(pa => pa.JobRoleId).ToList();
var jobRoles = await _context.JobRoles
.Where(j => j.TenantId == tenantId && jobRoleIds.Contains(j.Id))
.ToListAsync();
// Team on site
var teamOnSite = jobRoles
.Select(role =>
{
var count = projectAllocations.Count(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId));
return new TeamOnSite { RoleName = role.Name, NumberofEmployees = count };
})
.OrderByDescending(t => t.NumberofEmployees)
.ToList();
// Task details
var performedTasks = todayAssignedTasks.Select(task =>
{
var workItem = workItems.FirstOrDefault(w => w.Id == task.WorkItemId.ToString());
var area = areas.FirstOrDefault(a => a.Id == workItem?.WorkAreaId);
var floor = floors.FirstOrDefault(f => f.Id == area?.FloorId);
var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId);
string activityName = workItem?.ActivityMaster?.ActivityName ?? "";
string location = $"{building?.BuildingName} > {floor?.FloorName}</span><br/><span style=\"color: gray; font-size: small; padding-left: 10px;\"> {floor?.FloorName}-{area?.AreaName}";
double pending = (workItem?.PlannedWork ?? 0) - (workItem?.CompletedWork ?? 0);
var taskTeam = taskMembers
.Where(m => m.TaskAllocationId == task.Id)
.Select(m =>
{
string name = $"{m.Employee?.FirstName ?? ""} {m.Employee?.LastName ?? ""}";
var role = jobRoles.FirstOrDefault(r => r.Id == m.Employee?.JobRoleId);
return new TaskTeam { Name = name, RoleName = role?.Name ?? "" };
})
.ToList();
return new PerformedTask
{
Activity = activityName,
Location = location,
AssignedToday = task.PlannedTask,
CompletedToday = task.CompletedTask,
Pending = pending,
Comment = task.Description,
Team = taskTeam
};
}).ToList();
// Attendance details
var performedAttendance = attendances.Select(att =>
{
var alloc = projectAllocations.FirstOrDefault(p => p.EmployeeId == att.EmployeeID);
var role = jobRoles.FirstOrDefault(r => r.Id == alloc?.JobRoleId);
string name = $"{alloc?.Employee?.FirstName ?? ""} {alloc?.Employee?.LastName ?? ""}";
return new PerformedAttendance
{
Name = name,
RoleName = role?.Name ?? "",
InTime = att.InTime ?? DateTime.UtcNow,
OutTime = att.OutTime,
Comment = att.Comment
};
}).ToList();
// Fill report
statisticReport.TodaysAttendances = checkedInEmployeeIds.Count;
statisticReport.TotalEmployees = assignedEmployeeIds.Count;
statisticReport.RegularizationPending = regularizationIds.Count;
statisticReport.CheckoutPending = checkoutPendingIds.Count;
statisticReport.TotalPlannedWork = totalPlannedWork;
statisticReport.TotalCompletedWork = totalCompletedWork;
statisticReport.TotalPlannedTask = totalPlannedTask;
statisticReport.TotalCompletedTask = totalCompletedTask;
statisticReport.CompletionStatus = totalPlannedWork > 0 ? totalCompletedWork / totalPlannedWork : 0;
statisticReport.TodaysAssignTasks = todayAssignedTasks.Count;
statisticReport.ReportPending = reportPending.Count;
statisticReport.TeamOnSite = teamOnSite;
statisticReport.PerformedTasks = performedTasks;
statisticReport.PerformedAttendance = performedAttendance;
return statisticReport;
}
return null;
}
}
}

View File

@ -137,6 +137,7 @@ builder.Services.AddScoped<EmployeeHelper>();
builder.Services.AddScoped<ProjectsHelper>(); builder.Services.AddScoped<ProjectsHelper>();
builder.Services.AddScoped<DirectoryHelper>(); builder.Services.AddScoped<DirectoryHelper>();
builder.Services.AddScoped<MasterHelper>(); builder.Services.AddScoped<MasterHelper>();
builder.Services.AddScoped<ReportHelper>();
builder.Services.AddScoped<CacheUpdateHelper>(); builder.Services.AddScoped<CacheUpdateHelper>();
builder.Services.AddScoped<ProjectCache>(); builder.Services.AddScoped<ProjectCache>();
builder.Services.AddScoped<EmployeeCache>(); builder.Services.AddScoped<EmployeeCache>();