1294 lines
66 KiB
C#
1294 lines
66 KiB
C#
using QuestPDF.Fluent;
|
|
using QuestPDF.Helpers;
|
|
using QuestPDF.Infrastructure;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using PSTW_CentralSystem.Areas.OTcalculate.Models;
|
|
using PSTW_CentralSystem.Models; // Ensure this is included for CalendarModel, StateModel etc.
|
|
using Microsoft.EntityFrameworkCore;
|
|
using PSTW_CentralSystem.DBContext;
|
|
|
|
namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|
{
|
|
public class OvertimePDF
|
|
{
|
|
private readonly CentralSystemContext _centralDbContext;
|
|
|
|
public OvertimePDF(CentralSystemContext centralDbContext)
|
|
{
|
|
_centralDbContext = centralDbContext;
|
|
}
|
|
|
|
public MemoryStream GenerateOvertimeTablePdf(
|
|
List<OtRegisterModel> records,
|
|
UserModel user,
|
|
decimal userRate,
|
|
DateTime? selectedMonth = null,
|
|
byte[]? logoImage = null,
|
|
bool isHoU = false,
|
|
string? flexiHour = null,
|
|
List<ApprovalSignatureData>? approvedSignatures = null)
|
|
{
|
|
bool isAdminUser = IsAdmin(user.Id);
|
|
bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser;
|
|
|
|
DateTime displayMonth = selectedMonth ??
|
|
(records.Any() ? records.First().OtDate : DateTime.Now);
|
|
|
|
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
|
|
|
|
// Fetch user setting here once
|
|
var userSetting = _centralDbContext.Hrusersetting
|
|
.Include(us => us.State)
|
|
.FirstOrDefault(us => us.UserId == user.Id);
|
|
|
|
// Fetch actual public holiday objects including their names for the current month/year and user's state
|
|
var publicHolidaysForUser = _centralDbContext.Holidays
|
|
.Where(h => userSetting != null && h.StateId == userSetting.State.StateId && h.HolidayDate.Month == displayMonth.Month && h.HolidayDate.Year == displayMonth.Year)
|
|
.OrderBy(h => h.HolidayDate) // Order by date for display
|
|
.ToList();
|
|
|
|
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 =>
|
|
{
|
|
// Top Section: Logo, User Info, Generated Date
|
|
column.Item().Row(row =>
|
|
{
|
|
row.RelativeItem(2).Column(col =>
|
|
{
|
|
if (logoImage != null)
|
|
{
|
|
col.Item().PaddingBottom(10).Container().Height(25).Image(logoImage, ImageScaling.FitArea);
|
|
}
|
|
|
|
// Option 1: Inline with Clear Labels for user details
|
|
col.Item().PaddingBottom(2).Text(text =>
|
|
{
|
|
text.Span("Name: ").SemiBold().FontSize(9);
|
|
text.Span($"{user.FullName} |").FontSize(9);
|
|
|
|
text.Span(" Department: ").SemiBold().FontSize(9);
|
|
text.Span($"{user.Department?.DepartmentName ?? "N/A"} |").FontSize(9);
|
|
|
|
if (!string.IsNullOrEmpty(flexiHour))
|
|
{
|
|
text.Span(" Flexi Hour: ").SemiBold().FontSize(9);
|
|
text.Span($"{flexiHour}").FontSize(9);
|
|
}
|
|
});
|
|
});
|
|
|
|
row.RelativeItem(1).AlignRight().Text($"Generated: {DateTime.Now:dd MMM yyyy HH:mm}")
|
|
.FontSize(9).FontColor(Colors.Grey.Medium);
|
|
});
|
|
|
|
// Overtime Record title remains separate for clarity
|
|
column.Item().PaddingTop(3).Row(row =>
|
|
{
|
|
row.RelativeItem(2).Text($"Overtime Record: {displayMonth:MMMM yyyy}").FontSize(9).Italic();
|
|
|
|
// Legend (remains on the same line as Overtime Record)
|
|
row.RelativeItem(1).AlignRight().Column(legendCol =>
|
|
{
|
|
legendCol.Item().Element(e => e.ShowEntire().Row(subRow =>
|
|
{
|
|
subRow.AutoItem().Text(text =>
|
|
{
|
|
text.Span(" ").BackgroundColor("#add8e6").FontSize(9);
|
|
text.Span(" Weekend").FontSize(8).FontColor(Colors.Black);
|
|
});
|
|
}));
|
|
legendCol.Item().Element(e => e.ShowEntire().Row(subRow =>
|
|
{
|
|
subRow.AutoItem().Text(text =>
|
|
{
|
|
text.Span(" ").BackgroundColor("#ffc0cb").FontSize(9);
|
|
text.Span(" Public Holiday").FontSize(8).FontColor(Colors.Black);
|
|
});
|
|
}));
|
|
});
|
|
});
|
|
|
|
|
|
column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2);
|
|
|
|
// Pass the fetched publicHolidays to ComposeTable
|
|
column.Item().Element(container => ComposeTable(container, records, user, userRate, showStationColumn, allDatesInMonth, isHoU, publicHolidaysForUser.Select(h => h.HolidayDate.Date).ToList()));
|
|
|
|
// Approval Signatures and Remarks section
|
|
column.Item().PaddingTop(20).Element(container =>
|
|
{
|
|
container.ShowEntire().Row(row =>
|
|
{
|
|
// Left side - Approval Signatures
|
|
row.RelativeItem().Element(e => e.ShowEntire().Column(approvalCol =>
|
|
{
|
|
if (approvedSignatures != null && approvedSignatures.Any())
|
|
{
|
|
approvalCol.Item().Element(e => e.ShowEntire().Row(approvalsRow =>
|
|
{
|
|
approvalsRow.Spacing(20);
|
|
|
|
foreach (var approval in approvedSignatures)
|
|
{
|
|
approvalsRow.RelativeItem().Element(e => e.ShowEntire().Column(individualApprovalColumn =>
|
|
{
|
|
individualApprovalColumn.Item().Text("Approved by:").FontSize(9).SemiBold().AlignCenter();
|
|
|
|
individualApprovalColumn.Item().PaddingTop(1)
|
|
.Height(50)
|
|
.AlignCenter()
|
|
.AlignMiddle()
|
|
.Text(approval.ApproverName)
|
|
.FontSize(15)
|
|
.FontColor(Colors.Black)
|
|
.FontFamily("Brush Script MT");
|
|
|
|
individualApprovalColumn.Item().PaddingTop(1)
|
|
.Text(approval.ApproverName).FontSize(9).SemiBold().AlignCenter();
|
|
|
|
if (approval.ApprovedDate.HasValue)
|
|
{
|
|
individualApprovalColumn.Item().PaddingTop(2)
|
|
.Text(approval.ApprovedDate.Value.ToString("dd MMMM yyyy")) // Changed format to avoid special characters
|
|
.FontSize(8).AlignCenter();
|
|
}
|
|
}));
|
|
}
|
|
}));
|
|
}
|
|
}));
|
|
|
|
// Right side - Remarks and Public Holidays
|
|
row.RelativeItem().Element(e => e.ShowEntire().Column(remarksCol =>
|
|
{
|
|
remarksCol.Item().Element(e => e.ShowEntire().Row(remarksRow =>
|
|
{
|
|
// Public Holidays List as a mini-table
|
|
remarksRow.RelativeItem(2).Element(e => e.ShowEntire().AlignRight().Column(holidayListCol =>
|
|
{
|
|
holidayListCol.Item().Text("Public Holidays:").FontSize(9).SemiBold();
|
|
holidayListCol.Item().Table(table =>
|
|
{
|
|
table.ColumnsDefinition(columns =>
|
|
{
|
|
columns.RelativeColumn(1); // For Date
|
|
columns.RelativeColumn(2); // For Holiday Name
|
|
});
|
|
|
|
table.Header(header =>
|
|
{
|
|
header.Cell().Border(0.25f).Padding(2).Text("Date").FontSize(7).Bold().AlignCenter();
|
|
header.Cell().Border(0.25f).Padding(2).Text("Holiday Name").FontSize(7).Bold().AlignCenter();
|
|
});
|
|
|
|
if (publicHolidaysForUser.Any())
|
|
{
|
|
foreach (var holiday in publicHolidaysForUser)
|
|
{
|
|
table.Cell().Border(0.25f).Padding(2).Text($"{holiday.HolidayDate:dd-MM-yyyy}").FontSize(7).AlignCenter();
|
|
table.Cell().Border(0.25f).Padding(2).Text(holiday.HolidayName).FontSize(7).AlignLeft();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
table.Cell().ColumnSpan(2).Border(0.25f).Padding(2)
|
|
.Text($"No public holidays found for {userSetting?.State?.StateName ?? "this state"} in {displayMonth:MMMM yyyy}.") // Changed format
|
|
.FontSize(7).Italic().AlignCenter();
|
|
}
|
|
});
|
|
}));
|
|
}));
|
|
}));
|
|
});
|
|
});
|
|
// Add the automatically generated document text only if signatures are present
|
|
if (approvedSignatures != null && approvedSignatures.Any())
|
|
{
|
|
column.Item().PaddingTop(20).AlignCenter().Text("This is an automatically generated document. No signature is required for validation.").FontSize(8).Italic().FontColor(Colors.Grey.Darken2);
|
|
}
|
|
});
|
|
});
|
|
}).GeneratePdf(stream);
|
|
|
|
stream.Position = 0;
|
|
return stream;
|
|
}
|
|
|
|
private void ComposeTable(IContainer container, List<OtRegisterModel> records, UserModel user, decimal userRate, bool showStationColumn, List<DateTime> allDatesInMonth, bool isHoU, List<DateTime> publicHolidays)
|
|
{
|
|
var recordsGroupedByDate = records
|
|
.GroupBy(r => r.OtDate.Date)
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
var userSetting = _centralDbContext.Hrusersetting
|
|
.Include(us => us.State)
|
|
.FirstOrDefault(us => us.UserId == user.Id);
|
|
|
|
container.Table(table =>
|
|
{
|
|
table.ColumnsDefinition(columns =>
|
|
{
|
|
if (!isHoU)
|
|
{
|
|
columns.RelativeColumn(0.25f); // Basic Salary
|
|
columns.RelativeColumn(0.2f); // ORP
|
|
columns.RelativeColumn(0.2f); // HRP
|
|
}
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.4f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.25f);
|
|
columns.RelativeColumn(0.25f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.25f);
|
|
columns.RelativeColumn(0.3f);
|
|
columns.RelativeColumn(0.2f);
|
|
columns.RelativeColumn(0.2f);
|
|
if (!isHoU)
|
|
{
|
|
columns.RelativeColumn(0.25f);
|
|
}
|
|
|
|
if (showStationColumn)
|
|
{
|
|
columns.RelativeColumn(0.3f);
|
|
}
|
|
|
|
columns.RelativeColumn(0.7f);
|
|
});
|
|
|
|
table.Header(header =>
|
|
{
|
|
if (!isHoU)
|
|
{
|
|
header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3).Text("Basic Salary\n(RM)").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3).Text("ORP").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3).Text("HRP").FontSize(6).Bold().AlignCenter();
|
|
}
|
|
header.Cell().RowSpan(2).Background("#e0f7da").Border(0.25f).Padding(3).Text("Day").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#d0ead2").Border(0.25f).Padding(3).Text("Date").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().ColumnSpan(3).Background("#dceefb").Border(0.25f).Padding(3).Text("Office Hours").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().ColumnSpan(3).Background("#edf2f7").Border(0.25f).Padding(3).Text("After Office Hours").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(3).Text("OT Hrs\n(Office Hour)").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(3).Text("OT Hrs\n(After Office Hour)").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().ColumnSpan(1).Background("#deebf7").Border(0.25f).Padding(3).Text("Normal Day").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().ColumnSpan(3).Background("#deebf7").Border(0.25f).Padding(3).Text("Off Day").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().ColumnSpan(3).Background("#deebf7").Border(0.25f).Padding(3).Text("Rest Day").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().ColumnSpan(2).Background("#deebf7").Border(0.25f).Padding(3).Text("Public Holiday").FontSize(5).Bold().AlignCenter();
|
|
|
|
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(3).Text("Total OT").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(3).Text("Total\nND & OD").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(3).Text("Total\nRD").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(3).Text("Total\nPH").FontSize(6).Bold().AlignCenter();
|
|
|
|
if (!isHoU)
|
|
{
|
|
header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3).Text("OT Amt\n(RM)").FontSize(6).Bold().AlignCenter();
|
|
}
|
|
|
|
if (showStationColumn)
|
|
{
|
|
header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(3).Text("Station").FontSize(6).Bold().AlignCenter();
|
|
}
|
|
|
|
header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(3).Text("Description").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().Background("#dceefb").Border(0.25f).Padding(3).Text("From").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#dceefb").Border(0.25f).Padding(3).Text("To").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#dceefb").Border(0.25f).Padding(3).Text("Break\n(min)").FontSize(5).Bold().AlignCenter();
|
|
|
|
header.Cell().Background("#edf2f7").Border(0.25f).Padding(3).Text("From").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#edf2f7").Border(0.25f).Padding(3).Text("To").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#edf2f7").Border(0.25f).Padding(3).Text("Break\n(min)").FontSize(5).Bold().AlignCenter();
|
|
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("ND OT").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("OD < 4").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("OD 4-8").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("OD After").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("RD < 4").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("RD 4-8").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("RD After").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("PH < 8").FontSize(5).Bold().AlignCenter();
|
|
header.Cell().Background("#deebf7").Border(0.25f).Padding(3).Text("PH After").FontSize(5).Bold().AlignCenter();
|
|
});
|
|
|
|
|
|
decimal totalOfficeBreak = 0;
|
|
decimal totalAfterBreak = 0;
|
|
decimal totalOtHrsOffice = 0;
|
|
decimal totalOtHrsAfterOffice = 0;
|
|
decimal totalNdOt = 0;
|
|
decimal totalOdUnder4 = 0;
|
|
decimal totalOd4to8 = 0;
|
|
decimal totalOdAfter = 0;
|
|
decimal totalRdUnder4 = 0;
|
|
decimal totalRd4to8 = 0;
|
|
decimal totalRdAfter = 0;
|
|
decimal totalPhUnder8 = 0;
|
|
decimal totalPhAfter = 0;
|
|
decimal grandTotalOt = 0;
|
|
decimal grandTotalNdOd = 0;
|
|
decimal grandTotalRd = 0;
|
|
decimal grandTotalPh = 0;
|
|
decimal grandTotalOtAmount = 0;
|
|
|
|
|
|
bool alternate = false;
|
|
|
|
// Changed calculations as per your request
|
|
var basicSalary = userRate; // userRate is now treated as basic salary
|
|
var orp = basicSalary / 26m; // Calculate ORP from basicSalary
|
|
var hrp = orp / 8m; // Calculate HRP from ORP
|
|
|
|
|
|
bool hasPrintedSalaryDetails = false;
|
|
DateTime? previousDate = null;
|
|
|
|
foreach (var date in allDatesInMonth)
|
|
{
|
|
recordsGroupedByDate.TryGetValue(date, out var dateRecords);
|
|
var recordsToShow = dateRecords ?? new List<OtRegisterModel> { new OtRegisterModel { OtDate = date } };
|
|
recordsToShow = recordsToShow.OrderBy(r => r.OfficeFrom ?? r.AfterFrom).ToList();
|
|
|
|
foreach (var record in recordsToShow)
|
|
{
|
|
var dayType = GetDayType(date, userSetting?.State?.WeekendId, publicHolidays);
|
|
var backgroundColor = GetDayCellBackgroundColor(date, userSetting?.State?.WeekendId, publicHolidays);
|
|
|
|
var classified = ClassifyOt(record, hrp, publicHolidays, userSetting?.State?.WeekendId);
|
|
var totalOt = CalculateTotalOtHrs(record, hrp, publicHolidays, userSetting?.State?.WeekendId);
|
|
var totalNdOd = CalculateNdOdTotal(record, hrp, publicHolidays, userSetting?.State?.WeekendId);
|
|
var totalRd = CalculateRdTotal(record, hrp, publicHolidays, userSetting?.State?.WeekendId);
|
|
var totalPh = CalculatePhTotal(record, hrp, publicHolidays, userSetting?.State?.WeekendId);
|
|
var otAmt = CalculateOtAmount(record, hrp, publicHolidays, userSetting?.State?.WeekendId);
|
|
|
|
totalOfficeBreak += (decimal)record.OfficeBreak.GetValueOrDefault(0);
|
|
totalAfterBreak += (decimal)record.AfterBreak.GetValueOrDefault(0);
|
|
totalOtHrsOffice += ConvertTimeToDecimal(CalculateOfficeOtHours(record));
|
|
totalOtHrsAfterOffice += ConvertTimeToDecimal(CalculateAfterOfficeOtHours(record));
|
|
|
|
totalNdOt += decimal.TryParse(classified["ndAfter"], out decimal ndOtVal) ? ndOtVal : 0;
|
|
totalOdUnder4 += decimal.TryParse(classified["odUnder4"], out decimal odUnder4Val) ? odUnder4Val : 0;
|
|
totalOd4to8 += decimal.TryParse(classified["odBetween4And8"], out decimal od4to8Val) ? od4to8Val : 0;
|
|
totalOdAfter += decimal.TryParse(classified["odAfter"], out decimal odAfterVal) ? odAfterVal : 0;
|
|
totalRdUnder4 += decimal.TryParse(classified["rdUnder4"], out decimal rdUnder4Val) ? rdUnder4Val : 0;
|
|
totalRd4to8 += decimal.TryParse(classified["rdBetween4And8"], out decimal rd4to8Val) ? rd4to8Val : 0;
|
|
totalRdAfter += decimal.TryParse(classified["rdAfter"], out decimal rdAfterVal) ? rdAfterVal : 0;
|
|
totalPhUnder8 += decimal.TryParse(classified["phUnder8"], out decimal phUnder8Val) ? phUnder8Val : 0;
|
|
totalPhAfter += decimal.TryParse(classified["phAfter"], out decimal phAfterVal) ? phAfterVal : 0;
|
|
|
|
grandTotalOt += decimal.TryParse(totalOt, out decimal totalOtVal) ? totalOtVal : 0;
|
|
grandTotalNdOd += decimal.TryParse(totalNdOd, out decimal totalNdOdVal) ? totalNdOdVal : 0;
|
|
grandTotalRd += decimal.TryParse(totalRd, out decimal totalRdVal) ? totalRdVal : 0;
|
|
grandTotalPh += decimal.TryParse(totalPh, out decimal totalPhVal) ? totalPhVal : 0;
|
|
grandTotalOtAmount += decimal.TryParse(otAmt, out decimal otAmtVal) ? otAmtVal : 0;
|
|
|
|
|
|
string rowBg = alternate ? "#f9f9f9" : "#ffffff";
|
|
if (dateRecords == null || !dateRecords.Any())
|
|
rowBg = alternate ? Colors.Grey.Lighten5 : Colors.White;
|
|
|
|
|
|
void AddCell(string value, bool center = true, string? bg = null)
|
|
{
|
|
var cell = table.Cell().Background(bg ?? rowBg).Border(0.25f).Padding(2);
|
|
var text = cell.Text(value).FontSize(6);
|
|
if (center) text.AlignCenter();
|
|
}
|
|
|
|
if (!isHoU)
|
|
{
|
|
// Display the calculated values here
|
|
AddCell(!hasPrintedSalaryDetails ? $"{basicSalary:F2}" : "");
|
|
AddCell(!hasPrintedSalaryDetails ? $"{orp:F2}" : "");
|
|
AddCell(!hasPrintedSalaryDetails ? $"{hrp:F2}" : "");
|
|
}
|
|
hasPrintedSalaryDetails = true; // Ensure these values are printed only once
|
|
|
|
if (date.Date != previousDate?.Date)
|
|
{
|
|
AddCell(date.ToString("ddd"), true, backgroundColor);
|
|
AddCell(date.ToString("dd-MM-yyyy"), true, backgroundColor);
|
|
}
|
|
else
|
|
{
|
|
AddCell("", true, backgroundColor);
|
|
AddCell("", true, backgroundColor);
|
|
}
|
|
previousDate = date;
|
|
|
|
AddCell(record.OfficeFrom?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(record.OfficeTo?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(FormatAsHourMinute(record.OfficeBreak, isMinutes: true));
|
|
AddCell(record.AfterFrom?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(record.AfterTo?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(FormatAsHourMinute(record.AfterBreak, isMinutes: true));
|
|
AddCell(CalculateOfficeOtHours(record));
|
|
AddCell(CalculateAfterOfficeOtHours(record));
|
|
AddCell(classified["ndAfter"]);
|
|
AddCell(classified["odUnder4"]);
|
|
AddCell(classified["odBetween4And8"]);
|
|
AddCell(classified["odAfter"]);
|
|
AddCell(classified["rdUnder4"]);
|
|
AddCell(classified["rdBetween4And8"]);
|
|
AddCell(classified["rdAfter"]);
|
|
AddCell(classified["phUnder8"]);
|
|
AddCell(classified["phAfter"]);
|
|
AddCell(totalOt);
|
|
AddCell(totalNdOd);
|
|
AddCell(totalRd);
|
|
AddCell(totalPh);
|
|
if (!isHoU)
|
|
{
|
|
AddCell(otAmt == "0.00" ? "" : otAmt);
|
|
}
|
|
|
|
|
|
if (showStationColumn)
|
|
{
|
|
AddCell(record.Stations?.StationName ?? "");
|
|
}
|
|
|
|
table.Cell().Background(rowBg).Border(0.25f).Padding(2)
|
|
.Text(record.OtDescription ?? "").FontSize(6).AlignLeft();
|
|
|
|
alternate = !alternate;
|
|
}
|
|
}
|
|
|
|
if (!isHoU)
|
|
{
|
|
table.Cell().ColumnSpan(5).Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text("TOTAL").Bold().FontSize(6).AlignCenter();
|
|
}
|
|
else
|
|
{
|
|
table.Cell().ColumnSpan(2).Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text("TOTAL").Bold().FontSize(6).AlignCenter();
|
|
}
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text(totalOfficeBreak == 0 ? "0.00" : FormatAsHourMinute(totalOfficeBreak, isMinutes: true))
|
|
.Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text(totalAfterBreak == 0 ? "0.00" : FormatAsHourMinute(totalAfterBreak, isMinutes: true))
|
|
.Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalNdOt:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalOdUnder4:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalOd4to8:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalOdAfter:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalRdUnder4:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalRd4to8:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalRdAfter:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalPhUnder8:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{totalPhAfter:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{grandTotalOt:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{grandTotalNdOd:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{grandTotalRd:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{grandTotalPh:N2}").Bold().FontSize(6).AlignCenter();
|
|
|
|
if (!isHoU)
|
|
{
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text($"{grandTotalOtAmount:N2}").Bold().FontSize(6).AlignCenter();
|
|
}
|
|
|
|
if (showStationColumn)
|
|
{
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
}
|
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
});
|
|
}
|
|
|
|
public MemoryStream GenerateSimpleOvertimeTablePdf(
|
|
List<OtRegisterModel> records,
|
|
UserModel user, // User parameter added here
|
|
decimal userRate,
|
|
DateTime? selectedMonth = null,
|
|
byte[]? logoImage = null,
|
|
string? flexiHour = null)
|
|
{
|
|
bool isAdminUser = IsAdmin(user.Id);
|
|
bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser;
|
|
|
|
DateTime displayMonth = selectedMonth ??
|
|
(records.Any() ? records.First().OtDate : DateTime.Now);
|
|
|
|
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
|
|
|
|
// Fetch user setting here once
|
|
var userSetting = _centralDbContext.Hrusersetting
|
|
.Include(us => us.State)
|
|
.FirstOrDefault(us => us.UserId == user.Id);
|
|
|
|
// Fetch actual public holiday objects including their names for the current month/year and user's state
|
|
var publicHolidaysForUser = _centralDbContext.Holidays
|
|
.Where(h => userSetting != null && h.StateId == userSetting.State.StateId && h.HolidayDate.Month == displayMonth.Month && h.HolidayDate.Year == displayMonth.Year)
|
|
.OrderBy(h => h.HolidayDate) // Order by date for display
|
|
.ToList();
|
|
|
|
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 =>
|
|
{
|
|
// Top Section: Logo, User Info, Generated Date
|
|
column.Item().Row(row =>
|
|
{
|
|
row.RelativeItem(2).Column(col =>
|
|
{
|
|
if (logoImage != null)
|
|
{
|
|
col.Item().PaddingBottom(10).Container().Height(25).Image(logoImage, ImageScaling.FitArea);
|
|
}
|
|
|
|
// Option 1: Inline with Clear Labels for user details
|
|
col.Item().PaddingBottom(2).Text(text =>
|
|
{
|
|
text.Span("Name: ").SemiBold().FontSize(9);
|
|
text.Span($"{user.FullName} |").FontSize(9);
|
|
|
|
text.Span(" Department: ").SemiBold().FontSize(9);
|
|
text.Span($"{user.Department?.DepartmentName ?? "N/A"} |").FontSize(9);
|
|
|
|
if (!string.IsNullOrEmpty(flexiHour))
|
|
{
|
|
text.Span(" Flexi Hour: ").SemiBold().FontSize(9);
|
|
text.Span($"{flexiHour}").FontSize(9);
|
|
}
|
|
});
|
|
});
|
|
|
|
row.RelativeItem(1).AlignRight().Text($"Generated: {DateTime.Now:dd MMM yyyy HH:mm}")
|
|
.FontSize(9).FontColor(Colors.Grey.Medium);
|
|
});
|
|
|
|
// Overtime Record title remains separate for clarity
|
|
column.Item().PaddingTop(3).Row(row =>
|
|
{
|
|
row.RelativeItem(2).Text($"Overtime Record: {displayMonth:MMMM yyyy}").SemiBold().FontSize(9).Italic();
|
|
|
|
// Legend (remains on the same line as Overtime Record)
|
|
row.RelativeItem(1).AlignRight().Column(legendCol =>
|
|
{
|
|
legendCol.Item().Element(e => e.ShowEntire().Row(subRow =>
|
|
{
|
|
subRow.AutoItem().Text(text =>
|
|
{
|
|
text.Span(" ").BackgroundColor("#add8e6").FontSize(9);
|
|
text.Span(" Weekend").FontSize(8).FontColor(Colors.Black);
|
|
});
|
|
}));
|
|
legendCol.Item().Element(e => e.ShowEntire().Row(subRow =>
|
|
{
|
|
subRow.AutoItem().Text(text =>
|
|
{
|
|
text.Span(" ").BackgroundColor("#ffc0cb").FontSize(9);
|
|
text.Span(" Public Holiday").FontSize(8).FontColor(Colors.Black);
|
|
});
|
|
}));
|
|
});
|
|
});
|
|
|
|
column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2);
|
|
|
|
// Pass the fetched publicHolidays to ComposeSimpleTable
|
|
column.Item().Element(container => ComposeSimpleTable(container, records, user, showStationColumn, allDatesInMonth, publicHolidaysForUser.Select(h => h.HolidayDate.Date).ToList()));
|
|
|
|
// Public Holidays List (this section remains as is for now, below the table)
|
|
column.Item().PaddingTop(20).Element(container =>
|
|
{
|
|
container.ShowEntire().Row(row =>
|
|
{
|
|
row.RelativeItem().Column(legendCol =>
|
|
{
|
|
// Removed the legend remarks from here as they are now above the table
|
|
});
|
|
|
|
// Public Holidays List (on the right)
|
|
row.RelativeItem(2).AlignRight().Column(holidayListCol =>
|
|
{
|
|
holidayListCol.Item().Element(e => e.ShowEntire().Text("Public Holidays:").FontSize(9).SemiBold());
|
|
|
|
holidayListCol.Item().Table(table =>
|
|
{
|
|
table.ColumnsDefinition(columns =>
|
|
{
|
|
columns.RelativeColumn(1); // For Date
|
|
columns.RelativeColumn(2); // For Holiday Name
|
|
});
|
|
|
|
table.Header(header =>
|
|
{
|
|
header.Cell().Border(0.25f).Padding(2).Text("Date").FontSize(7).Bold().AlignCenter();
|
|
header.Cell().Border(0.25f).Padding(2).Text("Holiday Name").FontSize(7).Bold().AlignCenter();
|
|
});
|
|
|
|
if (publicHolidaysForUser.Any())
|
|
{
|
|
foreach (var holiday in publicHolidaysForUser)
|
|
{
|
|
table.Cell().Border(0.25f).Padding(2).Text($"{holiday.HolidayDate:dd-MM-yyyy}").FontSize(7).AlignCenter();
|
|
table.Cell().Border(0.25f).Padding(2).Text(holiday.HolidayName).FontSize(7).AlignLeft();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
table.Cell().ColumnSpan(2).Border(0.25f).Padding(2)
|
|
.Text($"No public holidays found for {userSetting?.State?.StateName ?? "this state"} in {displayMonth:MMMM yyyy}.") // Changed format
|
|
.FontSize(7).Italic().AlignCenter();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}).GeneratePdf(stream);
|
|
|
|
stream.Position = 0;
|
|
return stream;
|
|
}
|
|
|
|
// IMPORTANT: The ComposeSimpleTable method signature now accepts List<DateTime> for public holidays.
|
|
private void ComposeSimpleTable(IContainer container, List<OtRegisterModel> records, UserModel user, bool showStationColumn, List<DateTime> allDatesInMonth, List<DateTime> publicHolidays)
|
|
{
|
|
var recordsGroupedByDate = records
|
|
.GroupBy(r => r.OtDate.Date)
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
// Fetch user settings and public holidays within ComposeSimpleTable
|
|
var userSetting = _centralDbContext.Hrusersetting
|
|
.Include(us => us.State)
|
|
.FirstOrDefault(us => us.UserId == user.Id);
|
|
|
|
container.Table(table =>
|
|
{
|
|
// Define columns
|
|
table.ColumnsDefinition(columns =>
|
|
{
|
|
columns.RelativeColumn(0.8f); // Day (ddd)
|
|
columns.RelativeColumn(1); // Date
|
|
|
|
// Office Hour group
|
|
columns.RelativeColumn(1); // From
|
|
columns.RelativeColumn(1); // To
|
|
columns.RelativeColumn(0.8f); // Break
|
|
|
|
// After Office Hour group
|
|
columns.RelativeColumn(1); // From
|
|
columns.RelativeColumn(1); // To
|
|
columns.RelativeColumn(0.8f); // Break
|
|
|
|
columns.RelativeColumn(1); // Total OT Hours
|
|
columns.RelativeColumn(0.8f); // Break (min)
|
|
|
|
if (showStationColumn)
|
|
{
|
|
columns.RelativeColumn(1.5f); // Station
|
|
}
|
|
|
|
columns.RelativeColumn(2); // Description
|
|
});
|
|
|
|
// Header
|
|
table.Header(header =>
|
|
{
|
|
// First row of the header
|
|
header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3)
|
|
.Text("Day").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3)
|
|
.Text("Date").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().ColumnSpan(3).Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("Office Hour").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().ColumnSpan(3).Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("After Office Hour").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3)
|
|
.Text("Total OT Hours").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3)
|
|
.Text("Break (min)").FontSize(6).Bold().AlignCenter();
|
|
|
|
if (showStationColumn)
|
|
{
|
|
header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(3)
|
|
.Text("Station").FontSize(6).Bold().AlignCenter();
|
|
}
|
|
|
|
header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(3)
|
|
.Text("Description").FontSize(6).Bold().AlignCenter();
|
|
|
|
// Second row of the header (sub-headers)
|
|
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("From").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("To").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("Break").FontSize(6).Bold().AlignCenter();
|
|
|
|
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("From").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("To").FontSize(6).Bold().AlignCenter();
|
|
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
|
.Text("Break").FontSize(6).Bold().AlignCenter();
|
|
});
|
|
|
|
decimal totalHours = 0;
|
|
decimal totalBreak = 0;
|
|
bool alternate = false;
|
|
DateTime? previousDate = null; // To avoid repeating Day/Date for multiple records on the same day
|
|
|
|
foreach (var date in allDatesInMonth)
|
|
{
|
|
recordsGroupedByDate.TryGetValue(date, out var dateRecords);
|
|
var recordsToShow = dateRecords ?? new List<OtRegisterModel> { new OtRegisterModel { OtDate = date } };
|
|
recordsToShow = recordsToShow.OrderBy(r => r.OfficeFrom ?? r.AfterFrom).ToList();
|
|
|
|
foreach (var record in recordsToShow)
|
|
{
|
|
// Get background color for the current date based on user's state settings
|
|
var backgroundColor = GetDayCellBackgroundColor(date, userSetting?.State?.WeekendId, publicHolidays);
|
|
|
|
string rowBg = alternate ? "#f9f9f9" : "#ffffff";
|
|
if (dateRecords == null || !dateRecords.Any())
|
|
rowBg = alternate ? Colors.Grey.Lighten5 : Colors.White;
|
|
|
|
void AddCell(string value, bool center = true, string? bg = null)
|
|
{
|
|
var cell = table.Cell().Background(bg ?? rowBg).Border(0.25f).Padding(2);
|
|
var text = cell.Text(value).FontSize(6);
|
|
if (center) text.AlignCenter();
|
|
}
|
|
|
|
// Apply background color to Day and Date cells
|
|
if (date.Date != previousDate?.Date)
|
|
{
|
|
AddCell(date.ToString("ddd"), true, backgroundColor);
|
|
AddCell(date.ToString("dd-MM-yyyy"), true, backgroundColor);
|
|
}
|
|
else
|
|
{
|
|
// If multiple OT entries for the same day, don't repeat Day and Date, but still apply background
|
|
AddCell("", true, backgroundColor);
|
|
AddCell("", true, backgroundColor);
|
|
}
|
|
previousDate = date; // Update previousDate for the next iteration
|
|
|
|
|
|
// Calculate values
|
|
var officeHours = CalculateOfficeOtHours(record);
|
|
var afterHours = CalculateAfterOfficeOtHours(record);
|
|
var totalTime = ConvertTimeToDecimal(officeHours) + ConvertTimeToDecimal(afterHours);
|
|
var breakTime = (record.OfficeBreak ?? 0) + (record.AfterBreak ?? 0);
|
|
|
|
totalHours += totalTime;
|
|
totalBreak += breakTime;
|
|
|
|
// Office Hour
|
|
AddCell(record.OfficeFrom?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(record.OfficeTo?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(FormatAsHourMinute(record.OfficeBreak, isMinutes: true));
|
|
|
|
// After Office Hour
|
|
AddCell(record.AfterFrom?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(record.AfterTo?.ToString(@"hh\:mm") ?? "");
|
|
AddCell(FormatAsHourMinute(record.AfterBreak, isMinutes: true));
|
|
|
|
// Totals
|
|
AddCell(FormatAsHourMinute(totalTime, isMinutes: false));
|
|
AddCell(FormatAsHourMinute(breakTime, isMinutes: true));
|
|
|
|
// Station (if applicable)
|
|
if (showStationColumn)
|
|
{
|
|
AddCell(record.Stations?.StationName ?? "");
|
|
}
|
|
|
|
// Description
|
|
table.Cell().Background(rowBg).Border(0.25f).Padding(2)
|
|
.Text(record.OtDescription ?? "").FontSize(6).AlignLeft();
|
|
|
|
alternate = !alternate;
|
|
}
|
|
}
|
|
|
|
// Footer with totals
|
|
table.Footer(footer =>
|
|
{
|
|
// "TOTAL" will span only the first two columns (Day and Date)
|
|
footer.Cell().ColumnSpan(2).Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text("TOTAL").Bold().FontSize(6).AlignCenter();
|
|
|
|
// Create an empty cell that spans the remaining columns before "Total OT Hours"
|
|
// Remaining columns: Office Hour (3) + After Office Hour (3) = 6 columns
|
|
footer.Cell().ColumnSpan(6).Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
|
|
|
|
// These remain aligned with their respective columns
|
|
footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text(FormatAsHourMinute(totalHours, isMinutes: false)).Bold().FontSize(6).AlignCenter();
|
|
|
|
footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
|
.Text(FormatAsHourMinute(totalBreak, isMinutes: true)).Bold().FontSize(6).AlignCenter();
|
|
|
|
if (showStationColumn)
|
|
{
|
|
footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
}
|
|
|
|
footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter();
|
|
});
|
|
});
|
|
}
|
|
private bool IsAdmin(int userId)
|
|
{
|
|
var userRoles = _centralDbContext.UserRoles
|
|
.Where(ur => ur.UserId == userId)
|
|
.Join(_centralDbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name)
|
|
.ToList();
|
|
|
|
return userRoles.Any(role => role.Contains("SuperAdmin") || role.Contains("SystemAdmin"));
|
|
}
|
|
|
|
private string GetMonthYearString(List<OtRegisterModel> records)
|
|
{
|
|
if (records == null || !records.Any())
|
|
return "No Records";
|
|
|
|
var firstDate = records.First().OtDate;
|
|
var lastDate = records.Last().OtDate;
|
|
|
|
if (firstDate.Month == lastDate.Month && firstDate.Year == lastDate.Year)
|
|
return firstDate.ToString("MMMM yyyy"); // Fixed formatting to yyyy
|
|
|
|
return $"{firstDate:MMM yyyy} - {lastDate:MMM yyyy}"; // Fixed formatting to yyyy
|
|
}
|
|
|
|
private static IContainer CellStyle(IContainer container)
|
|
{
|
|
return container
|
|
.Padding(5)
|
|
.Border(1)
|
|
.BorderColor(QuestPDF.Helpers.Colors.Grey.Lighten2)
|
|
.AlignMiddle()
|
|
.AlignLeft();
|
|
}
|
|
|
|
private string FormatAsHourMinute(decimal? hoursOrMinutes, bool isMinutes = false)
|
|
{
|
|
if (hoursOrMinutes == null || hoursOrMinutes == 0)
|
|
return "";
|
|
|
|
TimeSpan ts = isMinutes
|
|
? TimeSpan.FromMinutes((double)hoursOrMinutes.Value)
|
|
: TimeSpan.FromHours((double)hoursOrMinutes.Value);
|
|
|
|
int totalHours = (int)(ts.TotalHours);
|
|
int minutes = (int)(ts.Minutes);
|
|
|
|
return $"{totalHours}:{minutes:D2}";
|
|
}
|
|
|
|
private string GetDayType(DateTime date, int? weekendId, List<DateTime> publicHolidays)
|
|
{
|
|
if (publicHolidays.Contains(date.Date))
|
|
{
|
|
return "Public Holiday";
|
|
}
|
|
|
|
DayOfWeek dayOfWeek = date.DayOfWeek;
|
|
|
|
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Saturday) || // Assuming weekendId 1 means Saturday is Rest Day
|
|
(weekendId == 2 && dayOfWeek == DayOfWeek.Sunday)) // Assuming weekendId 2 means Sunday is Rest Day
|
|
{
|
|
return "Rest Day";
|
|
}
|
|
|
|
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Friday) || // Assuming weekendId 1 means Friday is Off Day
|
|
(weekendId == 2 && dayOfWeek == DayOfWeek.Saturday)) // Assuming weekendId 2 means Saturday is Off Day
|
|
{
|
|
return "Off Day";
|
|
}
|
|
|
|
return "Normal Day";
|
|
}
|
|
|
|
// Updated these methods to reflect the new calculation logic and accept the correct input.
|
|
private decimal CalculateOrp(decimal basicSalary)
|
|
{
|
|
return basicSalary / 26m;
|
|
}
|
|
|
|
private decimal CalculateHrp(decimal orp)
|
|
{
|
|
return orp / 8m;
|
|
}
|
|
|
|
private decimal ConvertTimeToDecimal(string time)
|
|
{
|
|
if (string.IsNullOrEmpty(time)) return 0;
|
|
|
|
var parts = time.Split(':');
|
|
if (parts.Length != 2) return 0;
|
|
|
|
if (int.TryParse(parts[0], out int hours) && int.TryParse(parts[1], out int minutes))
|
|
{
|
|
return hours + (minutes / 60m);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private string CalculateOfficeOtHours(OtRegisterModel record)
|
|
{
|
|
if (!record.OfficeFrom.HasValue || !record.OfficeTo.HasValue) return "";
|
|
|
|
// Convert TimeSpan to minutes from start of the day
|
|
double fromMinutes = record.OfficeFrom.Value.TotalMinutes;
|
|
double toMinutes = record.OfficeTo.Value.TotalMinutes;
|
|
|
|
double totalMinutes = toMinutes - fromMinutes;
|
|
|
|
// Handle overnight shifts (e.g., 21:00 to 02:00)
|
|
if (totalMinutes < 0)
|
|
{
|
|
totalMinutes += 24 * 60; // Add 24 hours in minutes
|
|
}
|
|
|
|
totalMinutes -= (record.OfficeBreak ?? 0); // Subtract break
|
|
totalMinutes = Math.Max(0, totalMinutes); // Ensure total hours are not negative
|
|
|
|
int hours = (int)(totalMinutes / 60);
|
|
int minutes = (int)(totalMinutes % 60);
|
|
|
|
return $"{hours}:{minutes:D2}";
|
|
}
|
|
|
|
private string CalculateAfterOfficeOtHours(OtRegisterModel record)
|
|
{
|
|
if (!record.AfterFrom.HasValue || !record.AfterTo.HasValue) return "";
|
|
|
|
// Convert TimeSpan to minutes from start of the day
|
|
double fromMinutes = record.AfterFrom.Value.TotalMinutes;
|
|
double toMinutes = record.AfterTo.Value.TotalMinutes;
|
|
|
|
double totalMinutes = toMinutes - fromMinutes;
|
|
|
|
// Handle overnight shifts (e.g., 21:00 to 02:00)
|
|
if (totalMinutes < 0)
|
|
{
|
|
totalMinutes += 24 * 60; // Add 24 hours in minutes
|
|
}
|
|
|
|
totalMinutes -= (record.AfterBreak ?? 0); // Subtract break
|
|
totalMinutes = Math.Max(0, totalMinutes); // Ensure total hours are not negative
|
|
|
|
int hours = (int)(totalMinutes / 60);
|
|
int minutes = (int)(totalMinutes % 60);
|
|
|
|
return $"{hours}:{minutes:D2}";
|
|
}
|
|
|
|
private Dictionary<string, string> ClassifyOt(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
|
{
|
|
var officeRaw = CalculateOfficeOtHours(record);
|
|
var afterRaw = CalculateAfterOfficeOtHours(record);
|
|
|
|
var officeHrs = ConvertTimeToDecimal(officeRaw);
|
|
var afterHrs = ConvertTimeToDecimal(afterRaw);
|
|
|
|
// Helper to format numbers. Will return "" if num is 0 or less.
|
|
var toFixedOrEmpty = (decimal num) => num > 0 ? num.ToString("N2") : "";
|
|
|
|
var dayType = GetDayType(record.OtDate, weekendId, publicHolidays);
|
|
|
|
var result = new Dictionary<string, string>
|
|
{
|
|
["ndAfter"] = "",
|
|
["odUnder4"] = "",
|
|
["odBetween4And8"] = "",
|
|
["odAfter"] = "",
|
|
["rdUnder4"] = "",
|
|
["rdBetween4And8"] = "",
|
|
["rdAfter"] = "",
|
|
["phUnder8"] = "",
|
|
["phAfter"] = ""
|
|
};
|
|
|
|
switch (dayType)
|
|
{
|
|
case "Normal Day":
|
|
result["ndAfter"] = toFixedOrEmpty(afterHrs); // Only after-office OT on normal days
|
|
break;
|
|
|
|
case "Off Day":
|
|
if (officeHrs > 0) // Only process if there are office hours
|
|
{
|
|
if (officeHrs <= 4) // If total office hours are 4 or less
|
|
{
|
|
result["odUnder4"] = toFixedOrEmpty(officeHrs); // Assign all to 'under 4'
|
|
}
|
|
else if (officeHrs <= 8) // If total office hours are more than 4, but 8 or less
|
|
{
|
|
result["odBetween4And8"] = toFixedOrEmpty(officeHrs); // Assign all to '4-8'
|
|
}
|
|
else // If total office hours are more than 8
|
|
{
|
|
result["odAfter"] = toFixedOrEmpty(officeHrs); // Assign all to 'OD After' (for office hours beyond 8)
|
|
}
|
|
}
|
|
// Add 'afterHrs' to 'odAfter' as it's separate "After Office" time
|
|
// If 'odAfter' only represents office hours > 8, you would need a new key like 'odSeparateAfterOffice'.
|
|
result["odAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["odAfter"]) + afterHrs);
|
|
break;
|
|
|
|
case "Rest Day":
|
|
if (officeHrs > 0) // Only process if there are office hours
|
|
{
|
|
if (officeHrs <= 4) // If total office hours are 4 or less
|
|
{
|
|
result["rdUnder4"] = toFixedOrEmpty(officeHrs); // Assign all to 'under 4'
|
|
}
|
|
else if (officeHrs <= 8) // If total office hours are more than 4, but 8 or less
|
|
{
|
|
result["rdBetween4And8"] = toFixedOrEmpty(officeHrs); // Assign all to '4-8'
|
|
}
|
|
else // If total office hours are more than 8
|
|
{
|
|
result["rdAfter"] = toFixedOrEmpty(officeHrs); // Assign all to 'RD After' (for office hours beyond 8)
|
|
}
|
|
}
|
|
// Add 'afterHrs' to 'rdAfter'
|
|
result["rdAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["rdAfter"]) + afterHrs);
|
|
break;
|
|
|
|
case "Public Holiday":
|
|
if (officeHrs > 0) // Only process if there are office hours
|
|
{
|
|
if (officeHrs <= 8) // If total office hours are 8 or less
|
|
{
|
|
result["phUnder8"] = toFixedOrEmpty(officeHrs); // Assign all to 'under 8'
|
|
}
|
|
else // If total office hours are more than 8
|
|
{
|
|
result["phAfter"] = toFixedOrEmpty(officeHrs); // Assign all to 'PH After' (for office hours beyond 8)
|
|
}
|
|
}
|
|
// Add 'afterHrs' to 'phAfter'
|
|
result["phAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["phAfter"]) + afterHrs);
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private string CalculateTotalOtHrs(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
|
{
|
|
var classified = ClassifyOt(record, hrp, publicHolidays, weekendId);
|
|
decimal total = 0;
|
|
|
|
foreach (var value in classified.Values)
|
|
{
|
|
if (decimal.TryParse(value, out decimal num))
|
|
{
|
|
total += num;
|
|
}
|
|
}
|
|
|
|
return total > 0 ? total.ToString("N2") : "";
|
|
}
|
|
|
|
private string CalculateNdOdTotal(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
|
{
|
|
var classified = ClassifyOt(record, hrp, publicHolidays, weekendId);
|
|
|
|
var nd = decimal.TryParse(classified["ndAfter"], out decimal ndVal) ? ndVal : 0;
|
|
var od1 = decimal.TryParse(classified["odUnder4"], out decimal od1Val) ? od1Val : 0;
|
|
var od2 = decimal.TryParse(classified["odBetween4And8"], out decimal od2Val) ? od2Val : 0;
|
|
var od3 = decimal.TryParse(classified["odAfter"], out decimal od3Val) ? od3Val : 0;
|
|
|
|
var total = nd + od1 + od2 + od3;
|
|
return total > 0 ? total.ToString("N2") : "";
|
|
}
|
|
|
|
private string CalculateRdTotal(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
|
{
|
|
var classified = ClassifyOt(record, hrp, publicHolidays, weekendId);
|
|
var total =
|
|
(decimal.TryParse(classified["rdUnder4"], out decimal rd1) ? rd1 : 0) +
|
|
(decimal.TryParse(classified["rdBetween4And8"], out decimal rd2) ? rd2 : 0) +
|
|
(decimal.TryParse(classified["rdAfter"], out decimal rd3) ? rd3 : 0);
|
|
|
|
return total > 0 ? total.ToString("N2") : "";
|
|
}
|
|
|
|
private string CalculatePhTotal(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
|
{
|
|
var classified = ClassifyOt(record, hrp, publicHolidays, weekendId);
|
|
var total =
|
|
(decimal.TryParse(classified["phUnder8"], out decimal ph1) ? ph1 : 0) +
|
|
(decimal.TryParse(classified["phAfter"], out decimal ph2) ? ph2 : 0);
|
|
|
|
return total > 0 ? total.ToString("N2") : "";
|
|
}
|
|
|
|
private string CalculateOtAmount(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
|
{
|
|
// Now, orp and hrp are already correctly calculated based on the incoming userRate (which is now Basic Salary)
|
|
// in the ComposeTable method, so we pass hrp directly.
|
|
// However, the existing logic in this method relies on 'orp' and 'hrp' being derived
|
|
// in a specific way for the calculations.
|
|
// If the incoming 'hrp' to this method is indeed the HRP value, we need to calculate ORP from it.
|
|
// Re-calculating ORP and HRP here based on the 'hrp' received by this method to align with existing calculation logic.
|
|
decimal orpFromHrp = hrp * 8m; // This is the ORP used in calculations within this method
|
|
decimal basicSalaryFromOrp = orpFromHrp * 26m; // This is the Basic Salary derived from orpFromHrp
|
|
|
|
var orp = orpFromHrp; // Use the ORP derived from the HRP passed to this function
|
|
var dayType = GetDayType(record.OtDate, weekendId, publicHolidays);
|
|
|
|
var officeRaw = CalculateOfficeOtHours(record);
|
|
var afterRaw = CalculateAfterOfficeOtHours(record);
|
|
|
|
var officeHours = ConvertTimeToDecimal(officeRaw);
|
|
var afterOfficeHours = ConvertTimeToDecimal(afterRaw);
|
|
|
|
decimal amountOffice = 0;
|
|
decimal amountAfter = 0;
|
|
|
|
if (officeHours > 0)
|
|
{
|
|
if (dayType == "Off Day" || dayType == "Rest Day")
|
|
{
|
|
if (officeHours <= 4)
|
|
{
|
|
amountOffice = 0.5m * orp;
|
|
}
|
|
else if (officeHours > 4 && officeHours <= 8)
|
|
{
|
|
amountOffice = 1 * orp;
|
|
}
|
|
}
|
|
else if (dayType == "Public Holiday")
|
|
{
|
|
amountOffice = 2 * orp;
|
|
}
|
|
}
|
|
|
|
if (afterOfficeHours > 0)
|
|
{
|
|
switch (dayType)
|
|
{
|
|
case "Normal Day":
|
|
case "Off Day":
|
|
amountAfter = 1.5m * hrp * afterOfficeHours;
|
|
break;
|
|
case "Rest Day":
|
|
amountAfter = 2 * hrp * afterOfficeHours;
|
|
break;
|
|
case "Public Holiday":
|
|
amountAfter = 3 * hrp * afterOfficeHours;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var totalAmount = amountOffice + amountAfter;
|
|
return totalAmount.ToString("N2");
|
|
}
|
|
private List<DateTime> GetAllDatesInMonth(DateTime month)
|
|
{
|
|
return Enumerable.Range(1, DateTime.DaysInMonth(month.Year, month.Month))
|
|
.Select(day => new DateTime(month.Year, month.Month, day))
|
|
.ToList();
|
|
}
|
|
|
|
private string GetDayCellBackgroundColor(DateTime date, int? weekendId, List<DateTime> publicHolidays)
|
|
{
|
|
if (publicHolidays.Contains(date.Date))
|
|
return "#ffc0cb"; // Light red for Public Holiday
|
|
|
|
var dayOfWeek = date.DayOfWeek;
|
|
// Assuming weekendId 1 for Friday/Saturday weekend, 2 for Saturday/Sunday
|
|
if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) ||
|
|
(weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday)))
|
|
return "#add8e6"; // Light blue for Weekend
|
|
|
|
return "#ffffff"; // White for Normal Day
|
|
}
|
|
}
|
|
} |