Merge pull request 'Ashutosh_Task#316_Daily_Project_Report' (#74) from Ashutosh_Task#316_Daily_Project_Report into Issue_May_4W

Reviewed-on: #74
This commit is contained in:
Vikas Nale 2025-05-26 06:12:16 +00:00
commit 235ca073ce
12 changed files with 959 additions and 659 deletions

View File

@ -0,0 +1,12 @@
namespace Marco.Pms.Model.ViewModels.Report
{
public class PerformedAttendance
{
public string? Name { get; set; }
public string? RoleName { get; set; }
public DateTime InTime { get; set; }
public DateTime? OutTime { get; set; }
public string? Comment { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.ViewModels.Report
{
public class PerformedTask
{
public required string Activity { get; set; }
public required string Location { get; set; }
public double AssignedToday { get; set; }
public double Pending { get; set; }
public double CompletedToday { get; set; }
public List<TaskTeam> Team { get; set; } = new List<TaskTeam>();
public string? Comment { get; set; }
}
}

View File

@ -0,0 +1,26 @@
namespace Marco.Pms.Model.ViewModels.Report
{
public class ProjectStatisticReport
{
public DateTime Date { get; set; }
public required string ProjectName { get; set; }
public required string TimeStamp { get; set; }
public int TodaysAttendances { get; set; }
public int TotalEmployees { get; set; }
public int RegularizationPending { get; set; }
public int CheckoutPending { get; set; }
public double TotalPlannedWork { get; set; }
public double TotalCompletedWork { get; set; }
public double TotalPlannedTask { get; set; }
public double TotalCompletedTask { get; set; }
public double CompletionStatus { get; set; }
public int ReportPending { get; set; }
public int TodaysAssignTasks { get; set; }
public List<TeamOnSite> TeamOnSite { get; set; } = new List<TeamOnSite>();
public List<PerformedTask> PerformedTasks { get; set; } = new List<PerformedTask>();
public List<PerformedAttendance> PerformedAttendance { get; set; } = new List<PerformedAttendance>();
}
}

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.ViewModels.Report
{
public class TaskTeam
{
public string? Name { get; set; }
public string? RoleName { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace Marco.Pms.Model.ViewModels.Report
{
public class TeamOnSite
{
public string? RoleName { get; set; }
public int NumberofEmployees { get; set; }
}
}

View File

@ -48,7 +48,7 @@ namespace Marco.Pms.Services.Controllers
if (industry != null && industry.Name != null)
{
InquiryEmailObject inquiryEmailObject = inquiryDto.ToInquiryEmailObjectFromInquiriesDto(industry.Name);
string emails = _configuration["MaliingList:RequestDemoRecivers"] ?? "";
string emails = _configuration["MailingList:RequestDemoReceivers"] ?? "";
List<string> result = emails
.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(item => item.Trim())

View File

@ -0,0 +1,218 @@
using System.Globalization;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Roles;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Report;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MongoDB.Driver;
namespace Marco.Pms.Services.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ReportController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IEmailSender _emailSender;
private readonly IConfiguration _configuration;
private readonly ILoggingService _logger;
private readonly UserHelper _userHelper;
public ReportController(ApplicationDbContext context, IEmailSender emailSender, IConfiguration configuration, ILoggingService logger, UserHelper userHelper)
{
_context = context;
_emailSender = emailSender;
_configuration = configuration;
_logger = logger;
_userHelper = userHelper;
}
[HttpGet("project-statistics/{id}")]
public async Task<IActionResult> GetProjectStatistics(Guid id, [FromQuery] string? date)
{
DateTime reportDate = DateTime.UtcNow;
if (date != null && DateTime.TryParse(date, out reportDate) == false)
{
_logger.LogError("User sent Invalid from Date while featching project report");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
}
if (id == Guid.Empty)
{
_logger.LogError("Provided empty project ID while fetching project report");
return BadRequest(ApiResponse<object>.ErrorResponse("Provided empty ProjectID", "Provided empty ProjectID", 400));
}
Project? project = await _context.Projects.FirstOrDefaultAsync(p => p.Id == id);
if (project == null)
{
_logger.LogWarning("User attempted to fetch project progress of project ID {ProjectId} but not found in database", id);
return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 404));
}
ProjectStatisticReport statisticReport = new ProjectStatisticReport
{
Date = reportDate,
ProjectName = project.Name ?? "",
TimeStamp = DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss", CultureInfo.InvariantCulture)
};
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).ToList();
var attendances = await _context.Attendes.AsNoTracking().Where(a => a.ProjectID == project.Id && a.InTime != null && a.InTime.Value.Date == reportDate.Date).ToListAsync();
var checkedInEmployeeIds = attendances.Select(p => p.EmployeeID).Distinct().ToList();
var checkedOutPendingEmployeeIds = attendances.Where(a => a.ProjectID == project.Id && a.InTime != null && a.InTime.Value.Date == reportDate.Date && a.OutTime == null).Select(p => p.EmployeeID).Distinct().ToList();
var regularizationEmployeeIds = attendances.Where(a => a.ProjectID == project.Id && a.InTime != null && a.InTime.Value.Date == reportDate.Date && a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE).Select(p => p.EmployeeID).Distinct().ToList();
var buildings = await _context.Buildings.Where(b => b.ProjectId == project.Id).ToListAsync();
var buildingIds = buildings.Select(b => b.Id).Distinct().ToList();
var floors = await _context.Floor.Where(f => buildingIds.Contains(f.BuildingId)).ToListAsync();
var floorIds = floors.Select(f => f.Id).Distinct().ToList();
var areas = await _context.WorkAreas.Where(a => floorIds.Contains(a.FloorId)).ToListAsync();
var areaIds = areas.Select(a => a.Id).Distinct().ToList();
var workItems = await _context.WorkItems.Include(i => i.ActivityMaster).Where(i => areaIds.Contains(i.WorkAreaId)).ToListAsync();
var itemIds = workItems.Select(i => i.Id).Distinct().ToList();
var tasks = await _context.TaskAllocations.Where(t => itemIds.Contains(t.WorkItemId)).ToListAsync();
var taskIds = tasks.Select(t => t.Id).Distinct().ToList();
var taskMembers = await _context.TaskMembers.Include(m => m.Employee).Where(m => taskIds.Contains(m.TaskAllocationId)).ToListAsync();
double totalPlannedWork = 0;
double totalCompletedWork = 0;
foreach (var items in workItems)
{
totalPlannedWork += items.PlannedWork;
totalCompletedWork += items.CompletedWork;
}
var todayAssigned = tasks.Where(t => t.AssignmentDate.Date == reportDate.Date).ToList();
var reportPending = tasks.Where(t => t.ReportedDate == null).ToList();
double totalPlannedTask = 0;
double totalCompletedTask = 0;
foreach (var items in todayAssigned)
{
totalPlannedTask += items.PlannedTask;
totalCompletedTask += items.CompletedTask;
}
var jobRoles = await _context.JobRoles.Where(r => r.TenantId == project.TenantId).ToListAsync();
List<TeamOnSite> teamOnSite = new List<TeamOnSite>();
foreach (var role in jobRoles)
{
int numberOfEmployees = 0;
var roleassigned = projectAllocations.Where(p => p.JobRoleId == role.Id && checkedInEmployeeIds.Contains(p.EmployeeId)).ToList();
if (roleassigned.Count > 0)
{
numberOfEmployees = roleassigned.Count;
}
TeamOnSite team = new TeamOnSite
{
RoleName = role.Name,
NumberofEmployees = numberOfEmployees,
};
teamOnSite.Add(team);
}
List<PerformedTask> performedTasks = new List<PerformedTask>();
var todaysTask = tasks.Where(t => t.AssignmentDate.Date == reportDate.Date).ToList();
foreach (var task in todaysTask)
{
WorkItem workItem = workItems.FirstOrDefault(i => i.Id == task.WorkItemId) ?? new WorkItem();
string activityName = (workItem.ActivityMaster != null ? workItem.ActivityMaster.ActivityName : "") ?? "";
WorkArea workArea = areas.FirstOrDefault(a => a.Id == workItem.WorkAreaId) ?? new WorkArea();
string areaName = workArea.AreaName ?? "";
Floor floor = floors.FirstOrDefault(f => f.Id == workArea.FloorId) ?? new Floor();
string floorName = floor.FloorName ?? "";
Building building = buildings.FirstOrDefault(b => b.Id == floor.BuildingId) ?? new Building();
string buildingName = building.Name ?? "";
string location = $"{buildingName} > {floorName} </span><br/><span style=\"color: gray; font-size: small; padding-left: 10px;\"> {floorName}-{areaName}";
double pending = workItem.PlannedWork - workItem.CompletedWork;
PerformedTask performedTask = new PerformedTask
{
Activity = activityName,
Location = location,
AssignedToday = task.PlannedTask,
Pending = pending,
CompletedToday = task.CompletedTask,
Comment = task.Description
};
var taskTeams = taskMembers.Where(m => m.TaskAllocationId == task.Id).ToList();
List<TaskTeam> Team = new List<TaskTeam>();
foreach (var team in taskTeams)
{
string firstName = (team.Employee != null ? team.Employee.FirstName : "") ?? "";
string lastName = (team.Employee != null ? team.Employee.LastName : "") ?? "";
string name = $"{firstName} {lastName}";
JobRole role = jobRoles.FirstOrDefault(r => r.Id == (team.Employee != null ? team.Employee.JobRoleId : Guid.Empty)) ?? new JobRole();
string roleName = role.Name ?? "";
TaskTeam taskTeam = new TaskTeam
{
Name = name,
RoleName = roleName,
};
Team.Add(taskTeam);
}
performedTask.Team = Team;
performedTasks.Add(performedTask);
}
List<PerformedAttendance> performedAttendances = new List<PerformedAttendance>();
foreach (var attendance in attendances)
{
ProjectAllocation projectAllocation = projectAllocations.FirstOrDefault(p => p.EmployeeId == attendance.EmployeeID) ?? new ProjectAllocation();
JobRole role = jobRoles.FirstOrDefault(r => r.Id == projectAllocation.JobRoleId) ?? new JobRole();
string firstName = (projectAllocation.Employee != null ? projectAllocation.Employee.FirstName : "") ?? "";
string lastName = (projectAllocation.Employee != null ? projectAllocation.Employee.LastName : "") ?? "";
string name = $"{firstName} {lastName}";
PerformedAttendance performedAttendance = new PerformedAttendance
{
Name = name,
RoleName = role.Name ?? "",
InTime = attendance.InTime ?? DateTime.UtcNow,
OutTime = attendance.OutTime,
Comment = attendance.Comment
};
performedAttendances.Add(performedAttendance);
}
statisticReport.TodaysAttendances = checkedInEmployeeIds.Count;
statisticReport.TotalEmployees = assignedEmployeeIds.Count;
statisticReport.RegularizationPending = regularizationEmployeeIds.Count;
statisticReport.CheckoutPending = checkedOutPendingEmployeeIds.Count;
statisticReport.TotalPlannedWork = totalPlannedWork;
statisticReport.TotalCompletedWork = totalCompletedWork;
statisticReport.TotalPlannedTask = totalPlannedTask;
statisticReport.TotalCompletedTask = totalCompletedTask;
statisticReport.CompletionStatus = totalCompletedWork / totalPlannedWork;
statisticReport.TodaysAssignTasks = todayAssigned.Count;
statisticReport.ReportPending = reportPending.Count;
statisticReport.TeamOnSite = teamOnSite.OrderByDescending(c => c.NumberofEmployees).ToList();
statisticReport.PerformedTasks = performedTasks;
statisticReport.PerformedAttendance = performedAttendances;
string emails = _configuration["MailingList:ProjectStatisticsReceivers"] ?? "";
List<string> result = emails
.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(item => item.Trim())
.ToList();
await _emailSender.SendProjectStatisticsEmail(result, statisticReport);
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Email sent successfully", 200));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
using MailKit.Net.Smtp;
using System.Globalization;
using System.Text;
using MailKit.Net.Smtp;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Report;
using Microsoft.Extensions.Options;
using MimeKit;
@ -109,6 +112,92 @@ namespace MarcoBMS.Services.Service
}
public async Task SendProjectStatisticsEmail(List<string> toEmails, ProjectStatisticReport report)
{
//List<string> toEmails = [
// "ashutosh.nehete@marcoaiot.com"
//];
var date = report.Date.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture);
var replacements = new Dictionary<string, string>
{
{"DATE",date },
{"PROJECT_NAME",report.ProjectName },
{"TIMESTAMP",report.TimeStamp },
{"TODAYS_ATTENDANCES", report.TodaysAttendances.ToString("N0")},
{"TOTAL_EMPLOYEES",report.TotalEmployees.ToString("N0") },
{"TODAYS_PLANNED",report.TotalPlannedTask.ToString("N0") },
{"TODAYS_COMPLETED",report.TotalCompletedTask.ToString("N0") },
{"REGULRIZATION_PENDING", report.RegularizationPending.ToString("N0") },
{"CHECKOUT_PRNDING",report.CheckoutPending.ToString("N0") },
{"TOTAL_PLANNED",report.TotalPlannedWork.ToString("N0") },
{"TOTAL_COMPLETED",report.TotalCompletedWork.ToString() },
{"PROJECT_STATUS",report.CompletionStatus.ToString("P") },
{"TODAYS_ASSIGNED",report.TodaysAssignTasks.ToString("N0") },
{"REPORT_PENDING",report.ReportPending.ToString("N0") }
};
string emailBody = await GetEmailTemplate("project-report", replacements);
//string emailBody = await GetEmailTemplate("test-project", replacements);
var teamHtml = new StringBuilder();
teamHtml.Append("<tr style=\"vertical-align:middle\">");
int flag = 0;
foreach (var item in report.TeamOnSite)
{
if (flag == 6)
{
teamHtml.Append("</tr>");
teamHtml.Append("<tr style=\"vertical-align:middle\">");
flag = 0;
}
teamHtml.AppendFormat("<td class=\"team\" style=\"text-align:center\"><div style=\"border: 1px solid #d5d5d5; border-radius: 10px; margin: 10px 10px; padding: 10px;\"><div style=\"font-size: 15px; color: #525b75 \">{0}</div> <div style=\"font-size: 20px; color: #003cc7; margin: 20px !important; font-weight: bold; \">{1}</div></div></td>", item.RoleName, item.NumberofEmployees);
flag += 1;
}
teamHtml.Append("</tr>");
emailBody = emailBody.Replace("{{TEAM_ON_SITE}}", teamHtml.ToString());
var taskHtml = new StringBuilder();
if (report.PerformedTasks.Count > 0)
{
foreach (var task in report.PerformedTasks)
{
taskHtml.AppendFormat("<tr>\r\n<td style=\"text-align:left;\">\r\n<span style=\"padding-left: 10px; text-align: left;\">\r\n {0} \r\n</span><br />\r\n<span style=\"color: gray; font-size: small; padding-left: 10px;\"> {1} </span>\r\n</td>\r\n<td style=\"text-align:center\">\r\n {2} / {3} \r\n</td>\r\n<td style=\"text-align:center\">\r\n {4} \r\n</td>\r\n<td style=\"text-align:center\"> {5} </td>\r\n <td style=\"padding-left: 10px; text-align: left;\">\r\n",
task.Activity, task.Location, task.AssignedToday, task.Pending, task.CompletedToday, report.Date.ToString("dd-MMM-yyy"));
foreach (var member in task.Team)
{
taskHtml.AppendFormat(" {0} &nbsp; \r\n<span style=\"color: gray; font-size: small; padding-left: 10px;\">({1})</span>\r\n<br />",
member.Name, member.RoleName);
}
taskHtml.AppendFormat("</td>\r\n<td style=\"padding-left: 10px; max-width: 150px; text-align: left;\"> {0} </td>\r\n</tr>", task.Comment);
}
}
else
{
taskHtml.Append("<tr><td style=\"padding: 10px 0px 5px;\" colspan=\"6\"><span style=\"line-height: 25.2px; color: #666666;margin:10px\">No Activities (Tasks) Performed Today</span></td></tr>");
}
emailBody = emailBody.Replace("{{PERFORMED_TASK}}", taskHtml.ToString());
var attendanceHtml = new StringBuilder();
if (report.PerformedAttendance.Count > 0)
{
foreach (var attendance in report.PerformedAttendance)
{
attendanceHtml.AppendFormat("<tr>\r\n<td style=\"text-align:left\">\r\n<span style=\"padding-left:10px;\"> {0} </span>\r\n</td>\r\n<td style=\"text-align:center\"> {1} </td>\r\n<td style=\"text-align:center\"> {2} </td>\r\n<td style=\"text-align:center\"> {3} </td>\r\n<td style=\"padding-left:10px; max-width:150px\"> {4} </td>\r\n</tr>",
attendance.Name, attendance.RoleName, attendance.InTime.ToString("dd-MMM-yyyy h:mm tt", CultureInfo.InvariantCulture), (attendance.OutTime != null ? attendance.OutTime.Value.ToString("dd-MMM-yyyy h:mm tt", CultureInfo.InvariantCulture) : ""), attendance.Comment);
}
}
else
{
attendanceHtml.Append("<tr><td style=\"padding: 10px 0px 5px;\" colspan=\"5\"><span style=\"line-height: 25.2px; color: #666666;margin:10px\">No Attendance Performed Today</span></td></tr>");
}
emailBody = emailBody.Replace("{{PERFORMED_ATTENDANCE}}", attendanceHtml.ToString());
string subject = $"DPR - {date} - {report.ProjectName}";
await SendEmailAsync(toEmails, subject, emailBody);
}
public async Task SendEmailAsync(List<string> toEmails, string subject, string body)
{
var email = new MimeMessage();

View File

@ -1,4 +1,5 @@
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Report;
namespace MarcoBMS.Services.Service
{
@ -9,5 +10,6 @@ namespace MarcoBMS.Services.Service
Task SendResetPasswordSuccessEmail(string toEmail, string userName);
Task SendRequestDemoEmail(List<string> toEmails, InquiryEmailObject demoEmailObject);
Task SendEmailAsync(List<string> toEmails, string subject, string body);
Task SendProjectStatisticsEmail(List<string> toEmails, ProjectStatisticReport report);
}
}

View File

@ -7,15 +7,22 @@
"ConnectionStrings": {
//"DefaultConnectionString": "Server=localhost;port=3306;User ID=root;Password=root;Database=MarcoBMS2"
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSDev"
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSGuid"
},
"SmtpSettings": {
"SmtpServer": "smtp.gmail.com",
"Port": 587,
"SenderName": "MarcoIoIT",
"SenderName": "MarcoAIOT",
"SenderEmail": "marcoioitsoft@gmail.com",
"Password": "qrtq wfuj hwpp fhqr"
},
//"SmtpSettings": {
// "SmtpServer": "mail.marcoaiot.com",
// "Port": 587,
// "SenderName": "MarcoAIOT",
// "SenderEmail": "ashutosh.nehete@marcoaiot.com",
// "Password": "Reset@123"
//},
"AppSettings": {
"WebFrontendUrl": "http://localhost:5173",
"ImagesBaseUrl": "http://localhost:5173"
@ -27,8 +34,9 @@
"ExpiresInMinutes": 60,
"RefreshTokenExpiresInDays": 7
},
"MaliingList": {
"RequestDemoRecivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
"MailingList": {
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com",
"ProjectStatisticsReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
},
"AWS": {
"AccessKey": "AKIARZDBH3VDMSUUY2FX",

View File

@ -10,7 +10,7 @@
"SmtpSettings": {
"SmtpServer": "smtp.gmail.com",
"Port": 587,
"SenderName": "MarcoIoIT",
"SenderName": "MarcoAIOT",
"SenderEmail": "marcoioitsoft@gmail.com",
"Password": "qrtq wfuj hwpp fhqr"
},
@ -25,8 +25,9 @@
"ExpiresInMinutes": 60,
"RefreshTokenExpiresInDays": 7
},
"MaliingList": {
"RequestDemoRecivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
"MailingList": {
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com",
"ProjectStatisticsReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
},
"AWS": {
"AccessKey": "AKIARZDBH3VDMSUUY2FX",