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 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 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 SendProjectReport() { Guid tenantId = _userHelper.GetTenantId(); // Use AsNoTracking() for read-only queries to improve performance List 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.SuccessResponse( new { }, $"Reports sent successfully: {successCount}. Projects not found: {notFoundCount}. Invalid IDs: {invalidIdCount}.", 200)); } /// /// Retrieves project statistics for a given project ID and sends an email report. /// /// The ID of the project. /// The email address of the recipient. /// An ApiResponse indicating the success or failure of retrieving statistics and sending the email. private async Task> GetProjectStatistics(Guid projectId, List 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.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.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}
{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 mailLogs = new List(); 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.SuccessResponse(statisticReport, "Email sent successfully", 200); } } }