238 lines
12 KiB
C#
238 lines
12 KiB
C#
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<OtRegisterModel> records,
|
|
int departmentId,
|
|
string userFullName,
|
|
string departmentName,
|
|
int userStateId,
|
|
int weekendId,
|
|
List<CalendarModel> 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<OtRegisterModel> 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<CalendarModel> 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
|
|
}
|
|
|
|
}
|
|
}
|