using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using System.IO; using System.Collections.Generic; using PSTW_CentralSystem.Areas.OTcalculate.Models; using System; using System.Linq; namespace PSTW_CentralSystem.Areas.OTcalculate.Services { public class OvertimePdfService { public MemoryStream GenerateOvertimeTablePdf( List records, int departmentId, string userFullName, string departmentName, int userStateId, int weekendId, List publicHolidays, bool isAdminUser = false, byte[]? logoImage = null) { records = records.OrderBy(r => r.OtDate).ToList(); var stream = new MemoryStream(); Document.Create(container => { container.Page(page => { page.Size(PageSizes.A4.Landscape()); page.Margin(30); page.Content().Column(column => { // Header column.Item().Row(row => { row.RelativeItem(2).Column(col => { if (logoImage != null) { col.Item().Container().Height(36).Image(logoImage, ImageScaling.FitArea); col.Spacing(10); } col.Item().Text($"Name: {userFullName}").FontSize(9).SemiBold(); col.Item().Text($"Department: {departmentName}").FontSize(9).Italic(); col.Item().Text($"Overtime Record: {GetMonthYearString(records)}").FontSize(9).Italic(); }); row.RelativeItem(1).AlignRight().Text($"Generated: {DateTime.Now:dd MMM yyyy HH:mm}") .FontSize(9).FontColor(Colors.Grey.Medium); }); column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2); // Table column.Item().Table(table => { // Columns table.ColumnsDefinition(columns => { columns.RelativeColumn(0.7f); // Day columns.RelativeColumn(1.1f); // Date columns.RelativeColumn(0.8f); // Office From columns.RelativeColumn(0.8f); // Office To columns.RelativeColumn(0.8f); // Office Break columns.RelativeColumn(0.9f); // After From columns.RelativeColumn(0.9f); // After To columns.RelativeColumn(0.9f); // After Break columns.RelativeColumn(); // Total OT columns.RelativeColumn(); // Break Hours columns.RelativeColumn(); // Net OT if (departmentId == 2 || isAdminUser) columns.RelativeColumn(); // Station columns.RelativeColumn(2.7f); // Description }); // Header table.Header(header => { header.Cell().RowSpan(2).Background("#e0f7da").Border(0.25f).Padding(5).Text("Days").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#d0ead2").Border(0.25f).Padding(5).Text("Date").FontSize(9).Bold().AlignCenter(); header.Cell().ColumnSpan(3).Background("#dceefb").Border(0.25f).Padding(5).Text("Office Hours\n(8:30 - 17:30)").FontSize(9).Bold().AlignCenter(); header.Cell().ColumnSpan(3).Background("#edf2f7").Border(0.25f).Padding(5).Text("After Office Hours\n(17:30 - 8:30)").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Total OT\nHours").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Break Hours\n(min)").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Net OT\nHours").FontSize(9).Bold().AlignCenter(); if (departmentId == 2 || isAdminUser) header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(5).Text("Station").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(5).Text("Description").FontSize(9).Bold().AlignCenter(); // Subheaders for Office/After header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("Break (min)").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("Break (min)").FontSize(9).Bold().AlignCenter(); }); // Data double totalOTSum = 0; int totalBreakSum = 0; TimeSpan totalNetOt = TimeSpan.Zero; bool alternate = false; if (!records.Any()) { uint colspan = (uint)(departmentId == 2 ? 13 : 12); table.Cell().ColumnSpan(colspan).Border(0.5f).Padding(10).AlignCenter() .Text("No records found for selected month and year.") .FontSize(10).FontColor(Colors.Grey.Darken2).Italic(); } else { foreach (var r in records) { var totalOT = CalculateTotalOT(r); var totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 0); var netOT = totalOT - TimeSpan.FromMinutes(totalBreak); totalOTSum += totalOT.TotalHours; totalBreakSum += totalBreak; totalNetOt += netOT; string rowBg = alternate ? "#f9f9f9" : "#ffffff"; alternate = !alternate; string backgroundColor = GetDayCellBackgroundColor(r.OtDate, userStateId, publicHolidays, weekendId); void AddCell(string value, bool center = true, string? bg = null) { var text = table.Cell().Background(bg ?? rowBg).Border(0.25f).Padding(5).Text(value).FontSize(9); if (center) text.AlignCenter(); else text.AlignLeft(); } AddCell(r.OtDate.ToString("ddd"), true, backgroundColor); AddCell(r.OtDate.ToString("dd/MM/yyyy")); AddCell(FormatTime(r.OfficeFrom)); AddCell(FormatTime(r.OfficeTo)); AddCell(r.OfficeBreak > 0 ? $"{r.OfficeBreak}" : ""); AddCell(FormatTime(r.AfterFrom)); AddCell(FormatTime(r.AfterTo)); AddCell(r.AfterBreak > 0 ? $"{r.AfterBreak}" : ""); AddCell(totalOT > TimeSpan.Zero ? $"{totalOT:hh\\:mm}" : ""); AddCell(totalBreak > 0 ? $"{totalBreak}" : ""); AddCell(netOT > TimeSpan.Zero ? $"{netOT:hh\\:mm}" : ""); if (departmentId == 2 || isAdminUser) AddCell(r.Stations?.StationName ?? ""); table.Cell().Background(rowBg).Border(0.25f).Padding(5) .Text(r.OtDescription ?? "-").FontSize(9).WrapAnywhere().LineHeight(1.2f); } // Totals row int totalCols = departmentId == 2 ? 13 : 12; int spanCols = departmentId == 2 ? 9 : 8; for (int i = 0; i < spanCols; i++) table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5) .Text(i == 0 ? "TOTAL" : "").Bold().FontSize(9).AlignCenter(); table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5) .Text($"{TimeSpan.FromHours(totalOTSum):hh\\:mm}").Bold().FontSize(9).AlignCenter(); table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5) .Text($"{TimeSpan.FromMinutes(totalBreakSum):hh\\:mm}").Bold().FontSize(9).AlignCenter(); table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5) .Text($"{totalNetOt:hh\\:mm}").Bold().FontSize(9).AlignCenter(); if (departmentId == 2 || isAdminUser) table.Cell().Background("#d8d1f5").Border(0.80f); table.Cell().Background("#d8d1f5").Border(0.80f); } }); }); }); }).GeneratePdf(stream); stream.Position = 0; return stream; } private TimeSpan CalculateTotalOT(OtRegisterModel r) { TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero); TimeSpan after = (r.AfterTo ?? TimeSpan.Zero) - (r.AfterFrom ?? TimeSpan.Zero); if (after < TimeSpan.Zero) after += TimeSpan.FromHours(24); return office + after; } private string FormatTime(TimeSpan? time) { if (time == null || time == TimeSpan.Zero) return ""; return time.Value.ToString(@"hh\:mm"); } private string GetMonthYearString(List records) { if (records == null || !records.Any()) return "No Data"; var firstDate = records.First().OtDate; return $"{firstDate:MMMM yyyy}"; } private string GetDayCellBackgroundColor(DateTime date, int userStateId, List publicHolidays, int weekendId) { if (publicHolidays.Any(h => h.HolidayDate.Date == date.Date)) return "#ffc0cb"; // Pink var dayOfWeek = date.DayOfWeek; if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) || (weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday))) return "#add8e6"; // Blue return "#ffffff"; // Default } } }