331 lines
14 KiB
C#
331 lines
14 KiB
C#
using System.Data;
|
|
using System.Globalization;
|
|
using Marco.Pms.DataAccess.Data;
|
|
using Marco.Pms.Model.Dtos.Attendance;
|
|
using Marco.Pms.Model.Dtos.Mail;
|
|
using Marco.Pms.Model.Employees;
|
|
using Marco.Pms.Model.Mail;
|
|
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 ILoggingService _logger;
|
|
private readonly UserHelper _userHelper;
|
|
private readonly IWebHostEnvironment _env;
|
|
public ReportController(ApplicationDbContext context, IEmailSender emailSender, ILoggingService logger, UserHelper userHelper, IWebHostEnvironment env)
|
|
{
|
|
_context = context;
|
|
_emailSender = emailSender;
|
|
_logger = logger;
|
|
_userHelper = userHelper;
|
|
_env = env;
|
|
}
|
|
|
|
[HttpPost("set-mail")]
|
|
public async Task<IActionResult> AddMailDetails([FromBody] MailDetailsDto mailDetailsDto)
|
|
{
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
MailDetails mailDetails = new MailDetails
|
|
{
|
|
ProjectId = mailDetailsDto.ProjectId,
|
|
Recipient = mailDetailsDto.Recipient,
|
|
Schedule = mailDetailsDto.Schedule,
|
|
MailListId = mailDetailsDto.MailListId,
|
|
Subject = mailDetailsDto.Subject,
|
|
TenantId = tenantId
|
|
};
|
|
_context.MailDetails.Add(mailDetails);
|
|
await _context.SaveChangesAsync();
|
|
return Ok("Success");
|
|
}
|
|
|
|
[HttpPost("mail-template")]
|
|
public async Task<IActionResult> AddMailTemplate([FromBody] MailTemeplateDto mailTemeplateDto)
|
|
{
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
MailingList mailingList = new MailingList
|
|
{
|
|
Title = mailTemeplateDto.Title,
|
|
Body = mailTemeplateDto.Body,
|
|
Keywords = mailTemeplateDto.Keywords,
|
|
TenantId = tenantId
|
|
};
|
|
_context.MailingList.Add(mailingList);
|
|
await _context.SaveChangesAsync();
|
|
return Ok("Success");
|
|
}
|
|
|
|
[HttpGet("project-statistics")]
|
|
public async Task<IActionResult> SendProjectReport()
|
|
{
|
|
Guid tenantId = _userHelper.GetTenantId();
|
|
|
|
// Use AsNoTracking() for read-only queries to improve performance
|
|
List<MailDetails> mailDetails = await _context.MailDetails
|
|
.AsNoTracking()
|
|
.Include(m => m.MailBody)
|
|
.Where(m => m.TenantId == tenantId)
|
|
.ToListAsync();
|
|
|
|
int successCount = 0;
|
|
int notFoundCount = 0;
|
|
int invalidIdCount = 0;
|
|
|
|
var groupedMails = mailDetails
|
|
.GroupBy(m => new { m.ProjectId, m.MailListId })
|
|
.Select(g => new
|
|
{
|
|
ProjectId = g.Key.ProjectId,
|
|
MailListId = g.Key.MailListId,
|
|
Recipients = g.Select(m => m.Recipient).Distinct().ToList(),
|
|
MailBody = g.FirstOrDefault()?.MailBody?.Body ?? ""
|
|
})
|
|
.ToList();
|
|
|
|
var semaphore = new SemaphoreSlim(1);
|
|
|
|
// Using Task.WhenAll to send reports concurrently for better performance
|
|
var sendTasks = groupedMails.Select(async mailDetail =>
|
|
{
|
|
await semaphore.WaitAsync();
|
|
try
|
|
{
|
|
var response = await GetProjectStatistics(mailDetail.ProjectId, mailDetail.Recipients, mailDetail.MailBody, tenantId);
|
|
if (response.StatusCode == 200)
|
|
Interlocked.Increment(ref successCount);
|
|
else if (response.StatusCode == 404)
|
|
Interlocked.Increment(ref notFoundCount);
|
|
else if (response.StatusCode == 400)
|
|
Interlocked.Increment(ref invalidIdCount);
|
|
}
|
|
finally
|
|
{
|
|
semaphore.Release();
|
|
}
|
|
}).ToList();
|
|
|
|
await Task.WhenAll(sendTasks);
|
|
//var response = await GetProjectStatistics(Guid.Parse("2618eb89-2823-11f0-9d9e-bc241163f504"), "ashutosh.nehete@marcoaiot.com", tenantId);
|
|
|
|
|
|
_logger.LogInfo(
|
|
"Emails of project reports sent for tenant {TenantId}. Successfully sent: {SuccessCount}, Projects not found: {NotFoundCount}, Invalid IDs: {InvalidIdsCount}",
|
|
tenantId, successCount, notFoundCount, invalidIdCount);
|
|
|
|
return Ok(ApiResponse<object>.SuccessResponse(
|
|
new { },
|
|
$"Reports sent successfully: {successCount}. Projects not found: {notFoundCount}. Invalid IDs: {invalidIdCount}.",
|
|
200));
|
|
}
|
|
/// <summary>
|
|
/// Retrieves project statistics for a given project ID and sends an email report.
|
|
/// </summary>
|
|
/// <param name="projectId">The ID of the project.</param>
|
|
/// <param name="recipientEmail">The email address of the recipient.</param>
|
|
/// <returns>An ApiResponse indicating the success or failure of retrieving statistics and sending the email.</returns>
|
|
public async Task<ApiResponse<object>> GetProjectStatistics(Guid projectId, List<string> recipientEmails, string body, Guid tenantId)
|
|
{
|
|
DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date;
|
|
|
|
if (projectId == Guid.Empty)
|
|
{
|
|
_logger.LogError("Provided empty project ID while fetching project report.");
|
|
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)
|
|
{
|
|
_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);
|
|
}
|
|
|
|
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 jobRoles = await _context.JobRoles
|
|
.Where(j => j.TenantId == project.TenantId)
|
|
.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
|
|
var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, statisticReport);
|
|
var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee();
|
|
|
|
List<MailLog> mailLogs = new List<MailLog>();
|
|
foreach (var recipientEmail in recipientEmails)
|
|
{
|
|
mailLogs.Add(
|
|
new MailLog
|
|
{
|
|
ProjectId = projectId,
|
|
EmailId = recipientEmail,
|
|
Body = emailBody,
|
|
EmployeeId = employee.Id,
|
|
TimeStamp = DateTime.UtcNow,
|
|
TenantId = tenantId
|
|
});
|
|
}
|
|
|
|
_context.MailLogs.AddRange(mailLogs);
|
|
|
|
await _context.SaveChangesAsync();
|
|
return ApiResponse<object>.SuccessResponse(statisticReport, "Email sent successfully", 200);
|
|
}
|
|
}
|
|
}
|