PSTW_CentralizeSystem/Areas/OTcalculate/Services/OvertimeExcel.cs
2025-06-09 14:41:52 +08:00

2195 lines
106 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using ClosedXML.Excel;
using ClosedXML.Excel.Drawings;
using Microsoft.AspNetCore.Hosting; // Added for IWebHostEnvironment
using PSTW_CentralSystem.Areas.OTcalculate.Models;
using PSTW_CentralSystem.Models;
using PSTW_CentralSystem.DBContext;
namespace PSTW_CentralSystem.Areas.OTcalculate.Services
{
public class OvertimeExcel
{
private readonly CentralSystemContext _centralDbContext;
private readonly IWebHostEnvironment _env; // Added for IWebHostEnvironment
public OvertimeExcel(CentralSystemContext centralDbContext, IWebHostEnvironment env) // Modified constructor
{
_centralDbContext = centralDbContext;
_env = env; // Initialize IWebHostEnvironment
}
public MemoryStream GenerateOvertimeExcel(
List<OtRegisterModel> records,
UserModel user,
decimal userRate,
DateTime? selectedMonth = null,
bool isHoU = false,
string? flexiHour = null,
byte[]? logoImage = null,
bool isSimplifiedExport = false,
OtStatusModel? otStatus = null)
{
bool isAdminUser = IsAdmin(user.Id);
bool showStationColumn = user.Department?.DepartmentId == 3 || user.Department?.DepartmentId == 2 || isAdminUser;
var stream = new MemoryStream();
using (var workbook = new XLWorkbook())
{
var worksheet = workbook.Worksheets.Add("Overtime Report");
worksheet.ShowGridLines = true;
int currentRow = 1;
int logoBottomRow = 0;
if (logoImage != null)
{
using (var ms = new MemoryStream(logoImage))
{
var picture = worksheet.AddPicture(ms)
.MoveTo(worksheet.Cell(currentRow, 1))
.WithPlacement(XLPicturePlacement.FreeFloating);
picture.Name = "Company Logo";
picture.Scale(0.3);
logoBottomRow = currentRow + 2;
}
}
currentRow = logoBottomRow + 1;
DateTime displayMonth = selectedMonth ??
(records.Any() ? records.First().OtDate : DateTime.Now);
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
records = records.OrderBy(r => r.OtDate).ToList();
// Add user information
worksheet.Cell(currentRow, 1).Value = $"Name: {user.FullName}";
worksheet.Cell(currentRow, 1).Style.Font.SetBold();
currentRow++;
worksheet.Cell(currentRow, 1).Value = $"Department: {user.Department?.DepartmentName ?? "N/A"}";
worksheet.Cell(currentRow, 1).Style.Font.SetBold();
currentRow++;
if (!string.IsNullOrEmpty(flexiHour))
{
worksheet.Cell(currentRow, 1).Value = $"Flexi Hour: {flexiHour}";
worksheet.Cell(currentRow, 1).Style.Font.SetBold();
currentRow++;
}
worksheet.Cell(currentRow, 1).Value = $"Overtime Report: {displayMonth:MMMM yyyy}";
worksheet.Cell(currentRow, 1).Style.Font.SetBold();
currentRow++;
currentRow++; // Add an empty row for spacing
// --- Conditional Header Generation ---
if (isSimplifiedExport)
{
AddSimplifiedHeaders(worksheet, ref currentRow, showStationColumn);
}
else
{
// Existing logic for full headers (for approvers or other reports)
if (isHoU)
{
if (showStationColumn)
{
AddHoUPSTWAirHeaders(worksheet, ref currentRow);
}
else
{
AddHoUNonPSTWAirHeaders(worksheet, ref currentRow);
}
}
else // !isHoU path
{
if (showStationColumn)
{
AddNonHoUPSTWAirHeaders(worksheet, ref currentRow);
}
else
{
AddNonHoUNonPSTWAirHeaders(worksheet, ref currentRow);
}
}
}
// --- End Conditional Header Generation ---
var userSetting = _centralDbContext.Hrusersetting
.Include(us => us.State)
.FirstOrDefault(us => us.UserId == user.Id);
var publicHolidays = _centralDbContext.Holidays
.Where(h => userSetting != null && h.StateId == userSetting.State.StateId)
.ToList();
var publicHolidayDates = publicHolidays.Select(h => h.HolidayDate.Date).ToList();
var recordsGroupedByDate = records
.GroupBy(r => r.OtDate.Date)
.ToDictionary(g => g.Key, g => g.ToList());
decimal totalAllBreaksMinutes = 0;
decimal totalAllOtMinutes = 0;
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;
// UserRate is now interpreted as Basic Salary
var basicSalary = userRate;
var orp = CalculateOrp(basicSalary); // ORP = Basic Salary / 26
var hrp = CalculateHrp(orp); // HRP = ORP / 8
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)
{
int col = 1;
if (isSimplifiedExport)
{
var dayCell = worksheet.Cell(currentRow, col);
var dateCell = worksheet.Cell(currentRow, col + 1);
if (record.OtDate.Date != previousDate?.Date)
{
dayCell.Value = record.OtDate.ToString("ddd");
dateCell.Value = record.OtDate.ToString("dd-MM-yyyy");
}
else
{
dayCell.Value = "";
dateCell.Value = "";
}
ApplyDayTypeStyle(dayCell, record.OtDate, userSetting?.State?.WeekendId, publicHolidayDates);
ApplyDayTypeStyle(dateCell, record.OtDate, userSetting?.State?.WeekendId, publicHolidayDates);
col += 2;
worksheet.Cell(currentRow, col++).Value = record.OfficeFrom?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = record.OfficeTo?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = FormatAsHourMinute(record.OfficeBreak, isMinutes: true);
worksheet.Cell(currentRow, col++).Value = record.AfterFrom?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = record.AfterTo?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = FormatAsHourMinute(record.AfterBreak, isMinutes: true);
var currentRecordTotalOtMinutes = (CalculateTimeDifferenceInMinutes(record.OfficeFrom, record.OfficeTo) - record.OfficeBreak.GetValueOrDefault(0)) +
(CalculateTimeDifferenceInMinutes(record.AfterFrom, record.AfterTo) - record.AfterBreak.GetValueOrDefault(0));
currentRecordTotalOtMinutes = Math.Max(0, currentRecordTotalOtMinutes);
worksheet.Cell(currentRow, col++).Value = FormatAsHourMinute(currentRecordTotalOtMinutes, isMinutes: true);
totalAllOtMinutes += currentRecordTotalOtMinutes;
var currentRecordTotalBreakMinutes = record.OfficeBreak.GetValueOrDefault(0) + record.AfterBreak.GetValueOrDefault(0);
worksheet.Cell(currentRow, col++).Value = FormatAsHourMinute(currentRecordTotalBreakMinutes, isMinutes: true);
totalAllBreaksMinutes += currentRecordTotalBreakMinutes;
if (showStationColumn)
{
worksheet.Cell(currentRow, col++).Value = record.Stations?.StationName ?? "";
}
var descriptionCell = worksheet.Cell(currentRow, col);
descriptionCell.Value = record.OtDescription ?? "";
descriptionCell.Style.Alignment.WrapText = true;
descriptionCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
descriptionCell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Top;
col++;
var rowRange = worksheet.Range(currentRow, 1, currentRow, col - 1);
rowRange.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
rowRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
rowRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
descriptionCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
}
else
{
var dayType = GetDayType(date, userSetting?.State?.WeekendId, publicHolidayDates);
var classified = ClassifyOt(record, hrp, publicHolidayDates, userSetting?.State?.WeekendId);
decimal currentNdOt = decimal.TryParse(classified["ndAfter"], out decimal ndOtVal) ? ndOtVal : 0;
decimal currentOdUnder4 = decimal.TryParse(classified["odUnder4"], out decimal odUnder4Val) ? odUnder4Val : 0;
decimal currentOd4to8 = decimal.TryParse(classified["odBetween4And8"], out decimal od4to8Val) ? od4to8Val : 0;
decimal currentOdAfter = decimal.TryParse(classified["odAfter"], out decimal odAfterVal) ? odAfterVal : 0;
decimal currentRdUnder4 = decimal.TryParse(classified["rdUnder4"], out decimal rdUnder4Val) ? rdUnder4Val : 0;
decimal currentRd4to8 = decimal.TryParse(classified["rdBetween4And8"], out decimal rd4to8Val) ? rd4to8Val : 0;
decimal currentRdAfter = decimal.TryParse(classified["rdAfter"], out decimal rdAfterVal) ? rdAfterVal : 0;
decimal currentPhUnder8 = decimal.TryParse(classified["phUnder8"], out decimal phUnder8Val) ? phUnder8Val : 0;
decimal currentPhAfter = decimal.TryParse(classified["phAfter"], out decimal phAfterVal) ? phAfterVal : 0;
decimal currentRowTotalOt = currentNdOt + currentOdUnder4 + currentOd4to8 + currentOdAfter +
currentRdUnder4 + currentRd4to8 + currentRdAfter +
currentPhUnder8 + currentPhAfter;
decimal currentRowTotalNdOd = currentNdOt + currentOdUnder4 + currentOd4to8 + currentOdAfter;
decimal currentRowTotalRd = currentRdUnder4 + currentRd4to8 + currentRdAfter;
decimal currentRowTotalPh = currentPhUnder8 + currentPhAfter;
var otAmt = CalculateOtAmount(record, hrp, publicHolidayDates, userSetting?.State?.WeekendId);
totalOfficeBreak += (decimal)record.OfficeBreak.GetValueOrDefault(0);
totalAfterBreak += (decimal)record.AfterBreak.GetValueOrDefault(0);
totalOtHrsOffice += ConvertTimeToDecimal(CalculateOfficeOtHours(record));
totalOtHrsAfterOffice += ConvertTimeToDecimal(CalculateAfterOfficeOtHours(record));
totalNdOt += currentNdOt;
totalOdUnder4 += currentOdUnder4;
totalOd4to8 += currentOd4to8;
totalOdAfter += currentOdAfter;
totalRdUnder4 += currentRdUnder4;
totalRd4to8 += currentRd4to8;
totalRdAfter += currentRdAfter;
totalPhUnder8 += currentPhUnder8;
totalPhAfter += currentPhAfter;
grandTotalOt += currentRowTotalOt;
grandTotalNdOd += currentRowTotalNdOd;
grandTotalRd += currentRowTotalRd;
grandTotalPh += currentRowTotalPh;
grandTotalOtAmount += decimal.TryParse(otAmt, out decimal otAmtVal) ? otAmtVal : 0;
if (!isHoU)
{
// Basic Salary
worksheet.Cell(currentRow, col).Value = !hasPrintedSalaryDetails ? basicSalary.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
// ORP
worksheet.Cell(currentRow, col).Value = !hasPrintedSalaryDetails ? orp.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
// HRP
worksheet.Cell(currentRow, col).Value = !hasPrintedSalaryDetails ? hrp.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
}
hasPrintedSalaryDetails = true;
var dayCell = worksheet.Cell(currentRow, col);
var dateCell = worksheet.Cell(currentRow, col + 1);
if (record.OtDate.Date != previousDate?.Date)
{
dayCell.Value = record.OtDate.ToString("ddd");
dateCell.Value = record.OtDate.ToString("dd-MM-yyyy");
}
else
{
dayCell.Value = "";
dateCell.Value = "";
}
ApplyDayTypeStyle(dayCell, record.OtDate, userSetting?.State?.WeekendId, publicHolidayDates);
ApplyDayTypeStyle(dateCell, record.OtDate, userSetting?.State?.WeekendId, publicHolidayDates);
col += 2;
worksheet.Cell(currentRow, col++).Value = record.OfficeFrom?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = record.OfficeTo?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = FormatAsHourMinute(record.OfficeBreak, isMinutes: true);
worksheet.Cell(currentRow, col++).Value = record.AfterFrom?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = record.AfterTo?.ToString(@"hh\:mm") ?? "";
worksheet.Cell(currentRow, col++).Value = FormatAsHourMinute(record.AfterBreak, isMinutes: true);
worksheet.Cell(currentRow, col++).Value = CalculateOfficeOtHours(record);
worksheet.Cell(currentRow, col++).Value = CalculateAfterOfficeOtHours(record);
worksheet.Cell(currentRow, col++).Value = currentNdOt > 0 ? currentNdOt.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentOdUnder4 > 0 ? currentOdUnder4.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentOd4to8 > 0 ? currentOd4to8.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentOdAfter > 0 ? currentOdAfter.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRdUnder4 > 0 ? currentRdUnder4.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRd4to8 > 0 ? currentRd4to8.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRdAfter > 0 ? currentRdAfter.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentPhUnder8 > 0 ? currentPhUnder8.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentPhAfter > 0 ? currentPhAfter.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRowTotalOt > 0 ? currentRowTotalOt.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRowTotalNdOd > 0 ? currentRowTotalNdOd.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRowTotalRd > 0 ? currentRowTotalRd.ToString("N2") : "";
worksheet.Cell(currentRow, col++).Value = currentRowTotalPh > 0 ? currentRowTotalPh.ToString("N2") : "";
if (!isHoU)
{
worksheet.Cell(currentRow, col++).Value = otAmt == "0.00" ? "" : otAmt;
}
if (showStationColumn)
{
worksheet.Cell(currentRow, col++).Value = record.Stations?.StationName ?? "";
}
var descriptionCell = worksheet.Cell(currentRow, col);
descriptionCell.Value = record.OtDescription ?? "";
descriptionCell.Style.Alignment.WrapText = true;
descriptionCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
descriptionCell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Top;
col++;
var rowRange = worksheet.Range(currentRow, 1, currentRow, col - 1);
rowRange.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
rowRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
rowRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
descriptionCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
}
currentRow++;
previousDate = record.OtDate.Date;
}
}
var totalsRowRange = worksheet.Range(currentRow, 1, currentRow, worksheet.LastColumnUsed().ColumnNumber());
totalsRowRange.Style.Font.SetBold();
foreach (var cell in totalsRowRange.Cells())
{
ApplyHeaderStyle(cell, 5);
}
totalsRowRange.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
totalsRowRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
if (isSimplifiedExport)
{
int totalLabelStartColumnIndex = 1;
int totalLabelEndColumnIndex = 2;
worksheet.Range(currentRow, totalLabelStartColumnIndex, currentRow, totalLabelEndColumnIndex).Merge();
worksheet.Cell(currentRow, totalLabelStartColumnIndex).Value = "TOTAL";
worksheet.Cell(currentRow, totalLabelStartColumnIndex).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
int currentTotalCol = totalLabelEndColumnIndex + 1;
currentTotalCol += 6;
worksheet.Cell(currentRow, currentTotalCol++).Value = FormatAsHourMinute(totalAllOtMinutes, isMinutes: true);
worksheet.Cell(currentRow, currentTotalCol++).Value = FormatAsHourMinute(totalAllBreaksMinutes, isMinutes: true);
if (showStationColumn)
{
currentTotalCol++;
}
}
else
{
int totalLabelStartColumnIndex = 1;
int totalLabelEndColumnIndex;
if (!isHoU)
{
totalLabelEndColumnIndex = 3; // Basic Salary, ORP, HRP are new first three columns
}
else
{
totalLabelEndColumnIndex = 2;
}
worksheet.Range(currentRow, totalLabelStartColumnIndex, currentRow, totalLabelEndColumnIndex).Merge();
worksheet.Cell(currentRow, totalLabelStartColumnIndex).Value = "TOTAL";
worksheet.Cell(currentRow, totalLabelStartColumnIndex).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
int colOfficeBreakIndex = 0;
int colAfterBreakIndex = 0;
int colNdOtIndex = 0;
int colOdUnder4Index = 0;
int colOd4to8Index = 0;
int colOdAfterIndex = 0;
int colRdUnder4Index = 0;
int colRd4to8Index = 0;
int colRdAfterIndex = 0;
int colPhUnder8Index = 0;
int colPhAfterIndex = 0;
int colTotalOtIndex = 0;
int colTotalNdOdIndex = 0;
int colTotalRdIndex = 0;
int colTotalPhIndex = 0;
int colOtAmtIndex = 0;
if (!isHoU)
{
// Adjusted column indices due to Basic Salary, ORP, HRP being added at the beginning
colOfficeBreakIndex = 8;
colAfterBreakIndex = 11;
colNdOtIndex = 14;
colOdUnder4Index = 15;
colOd4to8Index = 16;
colOdAfterIndex = 17;
colRdUnder4Index = 18;
colRd4to8Index = 19;
colRdAfterIndex = 20;
colPhUnder8Index = 21;
colPhAfterIndex = 22;
colTotalOtIndex = 23;
colTotalNdOdIndex = 24;
colTotalRdIndex = 25;
colTotalPhIndex = 26;
colOtAmtIndex = 27;
}
else
{
colOfficeBreakIndex = 5;
colAfterBreakIndex = 8;
colNdOtIndex = 11;
colOdUnder4Index = 12;
colOd4to8Index = 13;
colOdAfterIndex = 14;
colRdUnder4Index = 15;
colRd4to8Index = 16;
colRdAfterIndex = 17;
colPhUnder8Index = 18;
colPhAfterIndex = 19;
colTotalOtIndex = 20;
colTotalNdOdIndex = 21;
colTotalRdIndex = 22;
colTotalPhIndex = 23;
}
worksheet.Cell(currentRow, colOfficeBreakIndex).Value = string.IsNullOrEmpty(FormatAsHourMinute(totalOfficeBreak, isMinutes: true)) ? "0:00" : FormatAsHourMinute(totalOfficeBreak, isMinutes: true);
worksheet.Cell(currentRow, colAfterBreakIndex).Value = string.IsNullOrEmpty(FormatAsHourMinute(totalAfterBreak, isMinutes: true)) ? "0:00" : FormatAsHourMinute(totalAfterBreak, isMinutes: true);
worksheet.Cell(currentRow, colNdOtIndex).Value = totalNdOt.ToString("N2");
worksheet.Cell(currentRow, colOdUnder4Index).Value = totalOdUnder4.ToString("N2");
worksheet.Cell(currentRow, colOd4to8Index).Value = totalOd4to8.ToString("N2");
worksheet.Cell(currentRow, colOdAfterIndex).Value = totalOdAfter.ToString("N2");
worksheet.Cell(currentRow, colRdUnder4Index).Value = totalRdUnder4.ToString("N2");
worksheet.Cell(currentRow, colRd4to8Index).Value = totalRd4to8.ToString("N2");
worksheet.Cell(currentRow, colRdAfterIndex).Value = totalRdAfter.ToString("N2");
worksheet.Cell(currentRow, colPhUnder8Index).Value = totalPhUnder8.ToString("N2");
worksheet.Cell(currentRow, colPhAfterIndex).Value = totalPhAfter.ToString("N2");
worksheet.Cell(currentRow, colTotalOtIndex).Value = grandTotalOt.ToString("N2");
worksheet.Cell(currentRow, colTotalNdOdIndex).Value = grandTotalNdOd.ToString("N2");
worksheet.Cell(currentRow, colTotalRdIndex).Value = grandTotalRd.ToString("N2");
worksheet.Cell(currentRow, colTotalPhIndex).Value = grandTotalPh.ToString("N2");
if (!isHoU)
{
worksheet.Cell(currentRow, colOtAmtIndex).Value = grandTotalOtAmount.ToString("N2");
}
}
currentRow++;
// --- Approval Signatures and Remarks Section (Combined) ---
if (!isSimplifiedExport && otStatus != null)
{
currentRow++; // Add a blank line before approval section
// Signature section header (left side)
worksheet.Cell(currentRow, 1).Value = "Approval Summary:";
worksheet.Cell(currentRow, 1).Style.Font.SetBold();
currentRow++;
int signatureStartCol = 1;
int signatureColWidth = 5;
// Calculate remarks start column (same as before)
int remarksStartColumn = worksheet.LastColumnUsed().ColumnNumber() - 2;
void AddApproverSignature(int column, string role, string status, DateTime? submitDate, int? approverUserId)
{
if (status == "Approved")
{
var approverUser = _centralDbContext.Users.FirstOrDefault(u => u.Id == approverUserId);
if (approverUser != null)
{
var approvedByCell = worksheet.Cell(currentRow, column);
approvedByCell.Value = $"Approved by:";
approvedByCell.Style.Font.SetBold();
approvedByCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
worksheet.Cell(currentRow + 1, column).Value = approverUser.FullName;
worksheet.Cell(currentRow + 1, column).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
worksheet.Cell(currentRow + 1, column).Style.Font.SetFontName("Brush Script MT");
worksheet.Cell(currentRow + 1, column).Style.Font.SetFontSize(18);
worksheet.Cell(currentRow + 2, column).Value = approverUser.FullName;
worksheet.Cell(currentRow + 2, column).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
worksheet.Cell(currentRow + 3, column).Value = submitDate?.ToString("dd MMMM yyyy") ?? "N/A";
worksheet.Cell(currentRow + 3, column).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
}
}
}
// Loop to add all approved signatures
var approvalFlow = _centralDbContext.Hrusersetting
.Include(us => us.Approvalflow)
.ThenInclude(af => af.HeadOfUnit)
.Include(us => us.Approvalflow)
.ThenInclude(af => af.HeadOfDepartment)
.Include(us => us.Approvalflow)
.ThenInclude(af => af.ManagerUser)
.Include(us => us.Approvalflow)
.ThenInclude(af => af.HRUser)
.Where(us => us.UserId == user.Id)
.Select(us => us.Approvalflow)
.FirstOrDefault();
if (approvalFlow != null)
{
int currentSignatureCol = signatureStartCol;
bool hasAnyApprovedSignature = false;
if (otStatus.HouStatus == "Approved")
{
AddApproverSignature(currentSignatureCol, "Head of Unit", otStatus.HouStatus, otStatus.HouSubmitDate, approvalFlow.HoU);
currentSignatureCol += signatureColWidth;
hasAnyApprovedSignature = true;
}
if (otStatus.HodStatus == "Approved")
{
AddApproverSignature(currentSignatureCol, "Head of Department", otStatus.HodStatus, otStatus.HodSubmitDate, approvalFlow.HoD);
currentSignatureCol += signatureColWidth;
hasAnyApprovedSignature = true;
}
if (otStatus.ManagerStatus == "Approved")
{
AddApproverSignature(currentSignatureCol, "Manager", otStatus.ManagerStatus, otStatus.ManagerSubmitDate, approvalFlow.Manager);
currentSignatureCol += signatureColWidth;
hasAnyApprovedSignature = true;
}
if (otStatus.HrStatus == "Approved")
{
AddApproverSignature(currentSignatureCol, "HR", otStatus.HrStatus, otStatus.HrSubmitDate, approvalFlow.HR);
currentSignatureCol += signatureColWidth;
hasAnyApprovedSignature = true;
}
// Add public holidays list below the color indicators
int remarksColorStartRow = currentRow - 1; // Adjust as needed for spacing from signatures
var phColorCell = worksheet.Cell(remarksColorStartRow, remarksStartColumn);
phColorCell.Value = " "; // Small cell for color swatch
phColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); // Pink color
phColorCell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
worksheet.Cell(remarksColorStartRow, remarksStartColumn + 1).Value = " Public Holiday";
worksheet.Cell(remarksColorStartRow, remarksStartColumn + 1).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
// Weekend/Off Day Color (right side) - on next row
var weekendColorCell = worksheet.Cell(remarksColorStartRow + 1, remarksStartColumn);
weekendColorCell.Value = " "; // Small cell for color swatch
weekendColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("ADD8E6"); // Light Blue color
weekendColorCell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
worksheet.Cell(remarksColorStartRow + 1, remarksStartColumn + 1).Value = " Weekend";
worksheet.Cell(remarksColorStartRow + 1, remarksStartColumn + 1).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
// Add public holidays list below the color indicators
int holidayListRow = remarksColorStartRow + 3; // Start below the color indicators
worksheet.Cell(holidayListRow, remarksStartColumn).Value = $"Public Holidays in {displayMonth:MMMM yyyy}:";
worksheet.Cell(holidayListRow, remarksStartColumn).Style.Font.SetBold();
holidayListRow++;
if (publicHolidays != null && publicHolidays.Any())
{
var monthHolidays = publicHolidays
.Where(h => h.HolidayDate.Year == displayMonth.Year && h.HolidayDate.Month == displayMonth.Month)
.OrderBy(h => h.HolidayDate.Day)
.ToList();
if (monthHolidays.Any())
{
foreach (var holiday in monthHolidays)
{
worksheet.Cell(holidayListRow, remarksStartColumn).Value = $"- {holiday.HolidayDate:dd MMMM}: {holiday.HolidayName}";
worksheet.Cell(holidayListRow, remarksStartColumn).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
holidayListRow++;
}
}
else
{
worksheet.Cell(holidayListRow, remarksStartColumn).Value = "No public holidays recorded for this month.";
worksheet.Cell(holidayListRow, remarksStartColumn).Style.Font.SetItalic();
holidayListRow++;
}
}
else
{
worksheet.Cell(holidayListRow, remarksStartColumn).Value = "No public holiday data found.";
worksheet.Cell(holidayListRow, remarksStartColumn).Style.Font.SetItalic();
holidayListRow++;
}
// --- START ADDITION ---
// Display the message only if at least one signature was approved and displayed.
if (hasAnyApprovedSignature)
{
int messageRow = holidayListRow + 2; // 2 rows below the last public holiday detail for spacing
// Merge cells for the message to span across the relevant width
int lastContentColumn = worksheet.LastColumnUsed().ColumnNumber();
var messageCellRange = worksheet.Range(messageRow, 1, messageRow, lastContentColumn);
messageCellRange.Merge();
messageCellRange.Value = "This is an automatically generated document. No signature is required for validation.";
messageCellRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
messageCellRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
messageCellRange.Style.Font.SetItalic();
messageCellRange.Style.Font.SetFontSize(10);
messageCellRange.Style.Font.FontColor = XLColor.Gray;
// Update currentRow to be after the message for subsequent content (if any)
currentRow = messageRow + 1;
}
// --- END ADDITION ---
// Update currentRow to the bottom of the entire remarks section
// This line is now outside the hasAnyApprovedSignature block, and uses the updated currentRow
currentRow = Math.Max(currentRow, holidayListRow);
}
}
else if (isSimplifiedExport) // This is the new section for simplified export remarks
{
// Call the AddRemarksSection here for simplified export
AddRemarksSection(worksheet, ref currentRow, displayMonth, userSetting?.State?.WeekendId, publicHolidays.Select(h => new CalendarModel { HolidayDate = h.HolidayDate, HolidayName = h.HolidayName }).ToList());
}
// --- End Combined Approval Signatures and Remarks Section ---
// --- Column Sizing Logic (Conditional) ---
worksheet.Columns().AdjustToContents();
if (isSimplifiedExport)
{
worksheet.Column(1).Width = 10;
worksheet.Column(2).Width = 12;
worksheet.Column(3).Width = 9;
worksheet.Column(4).Width = 9;
worksheet.Column(5).Width = 9;
worksheet.Column(6).Width = 9;
worksheet.Column(7).Width = 9;
worksheet.Column(8).Width = 9;
worksheet.Column(9).Width = 15;
worksheet.Column(10).Width = 10;
int descCol = 11;
if (showStationColumn)
{
worksheet.Column(11).Width = 15;
descCol = 12;
}
worksheet.Column(descCol).Width = 60;
}
else
{
if (!isHoU)
{
worksheet.Column(1).Width = 15; // Basic Salary
worksheet.Column(2).Width = 10; // ORP
worksheet.Column(3).Width = 10; // HRP
worksheet.Column(4).Width = 10; // Day
worksheet.Column(5).Width = 12; // Date
worksheet.Column(6).Width = 9; // Office From
worksheet.Column(7).Width = 9; // Office To
worksheet.Column(8).Width = 9; // Office Break
worksheet.Column(9).Width = 9; // After From
worksheet.Column(10).Width = 9; // After To
worksheet.Column(11).Width = 9; // After Break
worksheet.Column(12).Width = 15; // OT Hrs (Office Hour)
worksheet.Column(13).Width = 18; // OT Hrs (After Office Hour)
for (int i = 14; i <= 27; i++)
{
worksheet.Column(i).Width = 12;
}
int descColIndex = showStationColumn ? 29 : 28;
worksheet.Column(descColIndex).Width = 60;
}
else
{
worksheet.Column(1).Width = 10;
worksheet.Column(2).Width = 12;
worksheet.Column(3).Width = 9;
worksheet.Column(4).Width = 9;
worksheet.Column(5).Width = 9;
worksheet.Column(6).Width = 9;
worksheet.Column(7).Width = 9;
worksheet.Column(8).Width = 9;
worksheet.Column(9).Width = 15;
worksheet.Column(10).Width = 18;
for (int i = 11; i <= 23; i++)
{
worksheet.Column(i).Width = 12;
}
int descColIndex = showStationColumn ? 25 : 24;
worksheet.Column(descColIndex).Width = 60;
}
}
// --- End Column Sizing Logic ---
workbook.SaveAs(stream);
}
stream.Position = 0;
return stream;
}
// Add the new simplified header method (remains the same as previous response)
private void AddSimplifiedHeaders(IXLWorksheet worksheet, ref int rowIndex, bool showStationColumn)
{
int startRowIndex = rowIndex;
int currentCol = 1;
// Row 1 Headers
var cellDay = worksheet.Cell(rowIndex, currentCol);
cellDay.Value = "Day";
ApplyHeaderStyle(cellDay, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge();
currentCol++;
var cellDate = worksheet.Cell(rowIndex, currentCol);
cellDate.Value = "Date";
ApplyHeaderStyle(cellDate, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge();
currentCol++;
var cellOfficeHour = worksheet.Cell(rowIndex, currentCol);
cellOfficeHour.Value = "Office Hour";
ApplyHeaderStyle(cellOfficeHour, 2);
var mergedRangeOfficeHour = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge();
mergedRangeOfficeHour.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeOfficeHour.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellAfterOfficeHour = worksheet.Cell(rowIndex, currentCol);
cellAfterOfficeHour.Value = "After Office Hour";
ApplyHeaderStyle(cellAfterOfficeHour, 2);
var mergedRangeAfterOfficeHour = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge();
mergedRangeAfterOfficeHour.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeAfterOfficeHour.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellTotalOTHrs = worksheet.Cell(rowIndex, currentCol);
cellTotalOTHrs.Value = "Total OT Hours";
ApplyHeaderStyle(cellTotalOTHrs, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge();
currentCol++;
var cellBreakMin = worksheet.Cell(rowIndex, currentCol);
cellBreakMin.Value = "Break (min)";
ApplyHeaderStyle(cellBreakMin, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge();
currentCol++;
if (showStationColumn)
{
var cellStation = worksheet.Cell(rowIndex, currentCol);
cellStation.Value = "Station";
ApplyHeaderStyle(cellStation, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge();
currentCol++;
}
var cellDescription = worksheet.Cell(rowIndex, currentCol);
cellDescription.Value = "Description";
ApplyHeaderStyle(cellDescription, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge();
currentCol++;
// Apply borders to the full header range for Row 1
var headerRow1Range = worksheet.Range(startRowIndex, 1, startRowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow1Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow1Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for sub-headers
// Row 2 Sub-Headers
currentCol = 1;
currentCol += 2; // Skip Day and Date (vertically merged)
var cellOfficeFrom = worksheet.Cell(rowIndex, currentCol++);
cellOfficeFrom.Value = "From";
ApplyHeaderStyle(cellOfficeFrom, 3);
var cellOfficeTo = worksheet.Cell(rowIndex, currentCol++);
cellOfficeTo.Value = "To";
ApplyHeaderStyle(cellOfficeTo, 3);
var cellOfficeBreak = worksheet.Cell(rowIndex, currentCol++);
cellOfficeBreak.Value = "Break";
ApplyHeaderStyle(cellOfficeBreak, 3);
var cellAfterFrom = worksheet.Cell(rowIndex, currentCol++);
cellAfterFrom.Value = "From";
ApplyHeaderStyle(cellAfterFrom, 3);
var cellAfterTo = worksheet.Cell(rowIndex, currentCol++);
cellAfterTo.Value = "To";
ApplyHeaderStyle(cellAfterTo, 3);
var cellAfterBreak = worksheet.Cell(rowIndex, currentCol++);
cellAfterBreak.Value = "Break";
ApplyHeaderStyle(cellAfterBreak, 3);
// Skip Total OT Hours and Break (min) (vertically merged)
currentCol += 2;
if (showStationColumn)
{
currentCol++; // Skip Station (vertically merged)
}
// Skip Description (vertically merged)
currentCol++;
// Apply borders to the full header range for Row 2
var headerRow2Range = worksheet.Range(rowIndex, 1, rowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow2Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow2Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for data
}
// --- Helper methods (AddNonHoUPSTWAirHeaders, AddNonHoUNonPSTWAirHeaders, AddHoUPSTWAirHeaders, AddHoUNonPSTWAirHeaders,
// ApplyHeaderStyle, ApplyDayTypeStyle, GetDayCellColorStyleIndex, IsAdmin, GetAllDatesInMonth, GetDayType, CalculateOrp,
// FormatAsHourMinute, ConvertTimeToDecimal, CalculateTimeDifferenceInMinutes, CalculateOfficeOtHours, CalculateAfterOfficeOtHours,
// ClassifyOt, CalculateTotalOtHrs, CalculateNdOdTotal, CalculateRdTotal, CalculatePhTotal, CalculateOtAmount)
// defined only once below this line.
private void ApplyHeaderStyle(IXLCell cell, int styleType)
{
cell.Style.Font.SetBold();
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
cell.Style.Alignment.WrapText = true;
cell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
cell.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
switch (styleType)
{
case 1: // Primary Headers (e.g., "Day", "OT Hrs", "Total OT") - Darker Blue
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#78aafa"); // A standard blue
cell.Style.Font.FontColor = XLColor.Black; // Changed to Black
break;
case 2: // Grouping Headers (e.g., "Office Hour", "After Office Hour", "Off Day", "Rest Day", "Public Holiday") - Light Blue
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#DDEBF7"); // Lighter blue
cell.Style.Font.FontColor = XLColor.Black;
break;
case 3: // Sub-Headers (e.g., "From", "To", "Break", "ND OT", "OD < 4") - Light Green
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#C6E0B4"); // Light green
cell.Style.Font.FontColor = XLColor.Black;
break;
case 4: // Specific headers that need a different color (like "Description" if it needs unique) - Not used in this update, but kept as an option.
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#F8CBAD"); // Light orange
cell.Style.Font.FontColor = XLColor.Black;
break;
case 5: // Totals row background - Purple
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("D8D1F5"); // Original purple for totals
cell.Style.Font.FontColor = XLColor.Black;
break;
case 6: // Pink for Basic Salary, ORP, HRP, OT Amt
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#FF69B4"); // Hot Pink
cell.Style.Font.FontColor = XLColor.Black; // Changed to Black
break;
default: // Fallback
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#A9D08E");
cell.Style.Font.FontColor = XLColor.Black;
break;
}
}
private void ApplyDayTypeStyle(IXLCell cell, DateTime date, int? weekendId, List<DateTime> publicHolidays)
{
uint styleIndex = GetDayCellColorStyleIndex(date, weekendId, publicHolidays);
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
cell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
cell.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
switch (styleIndex)
{
case 18: // Pink for public holidays (FFC0CB)
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB");
break;
case 19: // Light Blue for weekends/off days (ADD8E6)
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("ADD8E6");
break;
default: // Default (no specific background fill)
cell.Style.Fill.BackgroundColor = XLColor.NoColor;
break;
}
}
// Method to add headers for Non-HoU, PSTWAIR (includes Basic Salary, ORP, HRP, Station)
private void AddNonHoUPSTWAirHeaders(IXLWorksheet worksheet, ref int rowIndex)
{
int startRowIndex = rowIndex;
int currentCol = 1;
// Row 1 Headers
currentCol = 1;
var cellA = worksheet.Cell(rowIndex, currentCol);
cellA.Value = "Basic Salary (RM)";
ApplyHeaderStyle(cellA, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellB = worksheet.Cell(rowIndex, currentCol);
cellB.Value = "ORP";
ApplyHeaderStyle(cellB, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellC = worksheet.Cell(rowIndex, currentCol);
cellC.Value = "HRP";
ApplyHeaderStyle(cellC, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellD = worksheet.Cell(rowIndex, currentCol);
cellD.Value = "Day";
ApplyHeaderStyle(cellD, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellE = worksheet.Cell(rowIndex, currentCol);
cellE.Value = "Date";
ApplyHeaderStyle(cellE, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellF = worksheet.Cell(rowIndex, currentCol);
cellF.Value = "Office Hour";
ApplyHeaderStyle(cellF, 2);
var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge F,G,H for Office Hour
mergedRangeF.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeF.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellI = worksheet.Cell(rowIndex, currentCol);
cellI.Value = "After Office Hour";
ApplyHeaderStyle(cellI, 2);
var mergedRangeI = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge I,J,K for After Office Hour
mergedRangeI.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeI.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellL = worksheet.Cell(rowIndex, currentCol);
cellL.Value = "OT Hrs (Office Hour)";
ApplyHeaderStyle(cellL, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellM = worksheet.Cell(rowIndex, currentCol);
cellM.Value = "OT Hrs (After Office Hour)";
ApplyHeaderStyle(cellM, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellN = worksheet.Cell(rowIndex, currentCol);
cellN.Value = "Normal Day (After Office)";
ApplyHeaderStyle(cellN, 2); // Changed to 2 to match 'Off Day' color
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellO = worksheet.Cell(rowIndex, currentCol);
cellO.Value = "Off Day";
ApplyHeaderStyle(cellO, 2); // Already using 2
var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge O,P,Q for Off Day
mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellR = worksheet.Cell(rowIndex, currentCol);
cellR.Value = "Rest Day";
ApplyHeaderStyle(cellR, 2);
var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge R,S,T for Rest Day
mergedRangeR.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeR.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellU = worksheet.Cell(rowIndex, currentCol);
cellU.Value = "Public Holiday";
ApplyHeaderStyle(cellU, 2);
var mergedRangeU = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); // Merge U,V for Public Holiday
mergedRangeU.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeU.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 2;
var cellW = worksheet.Cell(rowIndex, currentCol);
cellW.Value = "Total OT";
ApplyHeaderStyle(cellW, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellX = worksheet.Cell(rowIndex, currentCol);
cellX.Value = "Total ND & OD";
ApplyHeaderStyle(cellX, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellY = worksheet.Cell(rowIndex, currentCol);
cellY.Value = "Total RD";
ApplyHeaderStyle(cellY, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellZ = worksheet.Cell(rowIndex, currentCol);
cellZ.Value = "Total PH";
ApplyHeaderStyle(cellZ, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellAA = worksheet.Cell(rowIndex, currentCol);
cellAA.Value = "OT Amt (RM)";
ApplyHeaderStyle(cellAA, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellAB = worksheet.Cell(rowIndex, currentCol);
cellAB.Value = "Station";
ApplyHeaderStyle(cellAB, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellAC = worksheet.Cell(rowIndex, currentCol);
cellAC.Value = "Description";
ApplyHeaderStyle(cellAC, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var headerRow1Range = worksheet.Range(startRowIndex, 1, startRowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow1Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow1Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for sub-headers
// Row 2 Sub-Headers
currentCol = 1;
// These cells are part of vertical merges from Row 1, so they are conceptually empty,
// but we need to ensure borders are drawn for the entire merged block.
// We just advance the column counter past the merged cells from Row 1.
currentCol += 5; // Basic Salary, ORP, HRP, Day, Date
var cellF_sub = worksheet.Cell(rowIndex, currentCol++);
cellF_sub.Value = "From";
ApplyHeaderStyle(cellF_sub, 3);
var cellG_sub = worksheet.Cell(rowIndex, currentCol++);
cellG_sub.Value = "To";
ApplyHeaderStyle(cellG_sub, 3);
var cellH_sub = worksheet.Cell(rowIndex, currentCol++);
cellH_sub.Value = "Break";
ApplyHeaderStyle(cellH_sub, 3);
var cellI_sub = worksheet.Cell(rowIndex, currentCol++);
cellI_sub.Value = "From";
ApplyHeaderStyle(cellI_sub, 3);
var cellJ_sub = worksheet.Cell(rowIndex, currentCol++);
cellJ_sub.Value = "To";
ApplyHeaderStyle(cellJ_sub, 3);
var cellK_sub = worksheet.Cell(rowIndex, currentCol++);
cellK_sub.Value = "Break";
ApplyHeaderStyle(cellK_sub, 3);
currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged)
var cellN_sub = worksheet.Cell(rowIndex, currentCol++);
cellN_sub.Value = "ND OT";
ApplyHeaderStyle(cellN_sub, 3);
var cellO_sub = worksheet.Cell(rowIndex, currentCol++);
cellO_sub.Value = "OD < 4";
ApplyHeaderStyle(cellO_sub, 3);
var cellP_sub = worksheet.Cell(rowIndex, currentCol++);
cellP_sub.Value = "OD 4-8";
ApplyHeaderStyle(cellP_sub, 3);
var cellQ_sub = worksheet.Cell(rowIndex, currentCol++);
cellQ_sub.Value = "OD After";
ApplyHeaderStyle(cellQ_sub, 3);
var cellR_sub = worksheet.Cell(rowIndex, currentCol++);
cellR_sub.Value = "RD < 4";
ApplyHeaderStyle(cellR_sub, 3);
var cellS_sub = worksheet.Cell(rowIndex, currentCol++);
cellS_sub.Value = "RD 4-8";
ApplyHeaderStyle(cellS_sub, 3);
var cellT_sub = worksheet.Cell(rowIndex, currentCol++);
cellT_sub.Value = "RD After";
ApplyHeaderStyle(cellT_sub, 3);
var cellU_sub = worksheet.Cell(rowIndex, currentCol++);
cellU_sub.Value = "PH < 8";
ApplyHeaderStyle(cellU_sub, 3);
var cellV_sub = worksheet.Cell(rowIndex, currentCol++);
cellV_sub.Value = "PH After";
ApplyHeaderStyle(cellV_sub, 3);
// Skip Total OT, Total ND & OD, Total RD, Total PH, OT Amt (RM), Station, Description (vertically merged)
currentCol += 7;
// Apply borders to the full header range for Row 2
var headerRow2Range = worksheet.Range(rowIndex, 1, rowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow2Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow2Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for data
}
// Method to add headers for Non-HoU, Non-PSTWAIR (includes Basic Salary, ORP, HRP, NO Station)
private void AddNonHoUNonPSTWAirHeaders(IXLWorksheet worksheet, ref int rowIndex)
{
int startRowIndex = rowIndex;
int currentCol;
// Row 1 Headers
currentCol = 1;
var cellA = worksheet.Cell(rowIndex, currentCol);
cellA.Value = "Basic Salary (RM)";
ApplyHeaderStyle(cellA, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellB = worksheet.Cell(rowIndex, currentCol);
cellB.Value = "ORP";
ApplyHeaderStyle(cellB, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellC = worksheet.Cell(rowIndex, currentCol);
cellC.Value = "HRP";
ApplyHeaderStyle(cellC, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellD = worksheet.Cell(rowIndex, currentCol);
cellD.Value = "Day";
ApplyHeaderStyle(cellD, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellE = worksheet.Cell(rowIndex, currentCol);
cellE.Value = "Date";
ApplyHeaderStyle(cellE, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellF = worksheet.Cell(rowIndex, currentCol);
cellF.Value = "Office Hour";
ApplyHeaderStyle(cellF, 2);
var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge F,G,H
mergedRangeF.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeF.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellI = worksheet.Cell(rowIndex, currentCol);
cellI.Value = "After Office Hour";
ApplyHeaderStyle(cellI, 2);
var mergedRangeI = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge I,J,K
mergedRangeI.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeI.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellL = worksheet.Cell(rowIndex, currentCol);
cellL.Value = "OT Hrs (Office Hour)";
ApplyHeaderStyle(cellL, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellM = worksheet.Cell(rowIndex, currentCol);
cellM.Value = "OT Hrs (After Office Hour)";
ApplyHeaderStyle(cellM, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellN = worksheet.Cell(rowIndex, currentCol);
cellN.Value = "Normal Day (After Office)";
ApplyHeaderStyle(cellN, 2); // Changed to 2 to match 'Off Day' color
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellO = worksheet.Cell(rowIndex, currentCol);
cellO.Value = "Off Day";
ApplyHeaderStyle(cellO, 2); // Already using 2
var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge O,P,Q
mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellR = worksheet.Cell(rowIndex, currentCol);
cellR.Value = "Rest Day";
ApplyHeaderStyle(cellR, 2);
var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge R,S,T
mergedRangeR.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeR.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellU = worksheet.Cell(rowIndex, currentCol);
cellU.Value = "Public Holiday";
ApplyHeaderStyle(cellU, 2);
var mergedRangeU = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); // Merge U,V
mergedRangeU.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeU.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 2;
var cellW = worksheet.Cell(rowIndex, currentCol);
cellW.Value = "Total OT";
ApplyHeaderStyle(cellW, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellX = worksheet.Cell(rowIndex, currentCol);
cellX.Value = "Total ND & OD";
ApplyHeaderStyle(cellX, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellY = worksheet.Cell(rowIndex, currentCol);
cellY.Value = "Total RD";
ApplyHeaderStyle(cellY, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellZ = worksheet.Cell(rowIndex, currentCol);
cellZ.Value = "Total PH";
ApplyHeaderStyle(cellZ, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellAA = worksheet.Cell(rowIndex, currentCol);
cellAA.Value = "OT Amt (RM)";
ApplyHeaderStyle(cellAA, 6); // Set to pink
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellAB = worksheet.Cell(rowIndex, currentCol);
cellAB.Value = "Description"; // AB (No Station Column)
ApplyHeaderStyle(cellAB, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var headerRow1Range = worksheet.Range(startRowIndex, 1, startRowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow1Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow1Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for sub-headers
// Row 2 Sub-Headers
currentCol = 1;
currentCol += 5; // Basic Salary, ORP, HRP, Day, Date
var cellF_sub = worksheet.Cell(rowIndex, currentCol++);
cellF_sub.Value = "From";
ApplyHeaderStyle(cellF_sub, 3);
var cellG_sub = worksheet.Cell(rowIndex, currentCol++);
cellG_sub.Value = "To";
ApplyHeaderStyle(cellG_sub, 3);
var cellH_sub = worksheet.Cell(rowIndex, currentCol++);
cellH_sub.Value = "Break";
ApplyHeaderStyle(cellH_sub, 3);
var cellI_sub = worksheet.Cell(rowIndex, currentCol++);
cellI_sub.Value = "From";
ApplyHeaderStyle(cellI_sub, 3);
var cellJ_sub = worksheet.Cell(rowIndex, currentCol++);
cellJ_sub.Value = "To";
ApplyHeaderStyle(cellJ_sub, 3);
var cellK_sub = worksheet.Cell(rowIndex, currentCol++);
cellK_sub.Value = "Break";
ApplyHeaderStyle(cellK_sub, 3);
currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged)
var cellN_sub = worksheet.Cell(rowIndex, currentCol++);
cellN_sub.Value = "ND OT";
ApplyHeaderStyle(cellN_sub, 3);
var cellO_sub = worksheet.Cell(rowIndex, currentCol++);
cellO_sub.Value = "OD < 4";
ApplyHeaderStyle(cellO_sub, 3);
var cellP_sub = worksheet.Cell(rowIndex, currentCol++);
cellP_sub.Value = "OD 4-8";
ApplyHeaderStyle(cellP_sub, 3);
var cellQ_sub = worksheet.Cell(rowIndex, currentCol++);
cellQ_sub.Value = "OD After";
ApplyHeaderStyle(cellQ_sub, 3);
var cellR_sub = worksheet.Cell(rowIndex, currentCol++);
cellR_sub.Value = "RD < 4";
ApplyHeaderStyle(cellR_sub, 3);
var cellS_sub = worksheet.Cell(rowIndex, currentCol++);
cellS_sub.Value = "RD 4-8";
ApplyHeaderStyle(cellS_sub, 3);
var cellT_sub = worksheet.Cell(rowIndex, currentCol++);
cellT_sub.Value = "RD After";
ApplyHeaderStyle(cellT_sub, 3);
var cellU_sub = worksheet.Cell(rowIndex, currentCol++);
cellU_sub.Value = "PH < 8";
ApplyHeaderStyle(cellU_sub, 3);
var cellV_sub = worksheet.Cell(rowIndex, currentCol++);
cellV_sub.Value = "PH After";
ApplyHeaderStyle(cellV_sub, 3);
currentCol += 6; // Skip Total OT, Total ND & OD, Total RD, Total PH, OT Amt (RM), Description (vertically merged)
// Apply borders to the full header range for Row 2
var headerRow2Range = worksheet.Range(rowIndex, 1, rowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow2Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow2Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for data
}
// Method to add headers for HoU, PSTWAIR (NO Basic Salary, ORP, HRP, includes Station)
private void AddHoUPSTWAirHeaders(IXLWorksheet worksheet, ref int rowIndex)
{
int startRowIndex = rowIndex;
int currentCol = 1;
// Row 1 Headers
currentCol = 1;
var cellA = worksheet.Cell(rowIndex, currentCol);
cellA.Value = "Day";
ApplyHeaderStyle(cellA, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellB = worksheet.Cell(rowIndex, currentCol);
cellB.Value = "Date";
ApplyHeaderStyle(cellB, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellC = worksheet.Cell(rowIndex, currentCol);
cellC.Value = "Office Hour";
ApplyHeaderStyle(cellC, 2);
var mergedRangeC = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge C,D,E
mergedRangeC.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeC.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellF = worksheet.Cell(rowIndex, currentCol);
cellF.Value = "After Office Hour";
ApplyHeaderStyle(cellF, 2);
var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge F,G,H
mergedRangeF.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeF.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellI = worksheet.Cell(rowIndex, currentCol);
cellI.Value = "OT Hrs (Office Hour)";
ApplyHeaderStyle(cellI, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellJ = worksheet.Cell(rowIndex, currentCol);
cellJ.Value = "OT Hrs (After Office Hour)";
ApplyHeaderStyle(cellJ, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellK = worksheet.Cell(rowIndex, currentCol);
cellK.Value = "Normal Day (After Office)";
ApplyHeaderStyle(cellK, 2); // Changed to 2 to match 'Off Day' color
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellL = worksheet.Cell(rowIndex, currentCol);
cellL.Value = "Off Day";
ApplyHeaderStyle(cellL, 2); // Already using 2
var mergedRangeL = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge L,M,N
mergedRangeL.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeL.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellO = worksheet.Cell(rowIndex, currentCol);
cellO.Value = "Rest Day";
ApplyHeaderStyle(cellO, 2);
var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge O,P,Q
mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellR = worksheet.Cell(rowIndex, currentCol);
cellR.Value = "Public Holiday";
ApplyHeaderStyle(cellR, 2);
var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); // Merge R,S
mergedRangeR.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeR.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 2;
var cellT = worksheet.Cell(rowIndex, currentCol);
cellT.Value = "Total OT";
ApplyHeaderStyle(cellT, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellU = worksheet.Cell(rowIndex, currentCol);
cellU.Value = "Total ND & OD";
ApplyHeaderStyle(cellU, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellV = worksheet.Cell(rowIndex, currentCol);
cellV.Value = "Total RD";
ApplyHeaderStyle(cellV, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellW = worksheet.Cell(rowIndex, currentCol);
cellW.Value = "Total PH";
ApplyHeaderStyle(cellW, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellX = worksheet.Cell(rowIndex, currentCol);
cellX.Value = "Station";
ApplyHeaderStyle(cellX, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellY = worksheet.Cell(rowIndex, currentCol);
cellY.Value = "Description";
ApplyHeaderStyle(cellY, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var headerRow1Range = worksheet.Range(startRowIndex, 1, startRowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow1Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow1Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for sub-headers
// Row 2 Sub-Headers
currentCol = 1;
currentCol += 2; // Day, Date
var cellC_sub = worksheet.Cell(rowIndex, currentCol++);
cellC_sub.Value = "From";
ApplyHeaderStyle(cellC_sub, 3);
var cellD_sub = worksheet.Cell(rowIndex, currentCol++);
cellD_sub.Value = "To";
ApplyHeaderStyle(cellD_sub, 3);
var cellE_sub = worksheet.Cell(rowIndex, currentCol++);
cellE_sub.Value = "Break";
ApplyHeaderStyle(cellE_sub, 3);
var cellF_sub = worksheet.Cell(rowIndex, currentCol++);
cellF_sub.Value = "From";
ApplyHeaderStyle(cellF_sub, 3);
var cellG_sub = worksheet.Cell(rowIndex, currentCol++);
cellG_sub.Value = "To";
ApplyHeaderStyle(cellG_sub, 3);
var cellH_sub = worksheet.Cell(rowIndex, currentCol++);
cellH_sub.Value = "Break";
ApplyHeaderStyle(cellH_sub, 3);
currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged)
var cellK_sub = worksheet.Cell(rowIndex, currentCol++);
cellK_sub.Value = "ND OT";
ApplyHeaderStyle(cellK_sub, 3);
var cellL_sub = worksheet.Cell(rowIndex, currentCol++);
cellL_sub.Value = "OD < 4";
ApplyHeaderStyle(cellL_sub, 3);
var cellM_sub = worksheet.Cell(rowIndex, currentCol++);
cellM_sub.Value = "OD 4-8";
ApplyHeaderStyle(cellM_sub, 3);
var cellN_sub = worksheet.Cell(rowIndex, currentCol++);
cellN_sub.Value = "OD After";
ApplyHeaderStyle(cellN_sub, 3);
var cellO_sub = worksheet.Cell(rowIndex, currentCol++);
cellO_sub.Value = "RD < 4";
ApplyHeaderStyle(cellO_sub, 3);
var cellP_sub = worksheet.Cell(rowIndex, currentCol++);
cellP_sub.Value = "RD 4-8";
ApplyHeaderStyle(cellP_sub, 3);
var cellQ_sub = worksheet.Cell(rowIndex, currentCol++);
cellQ_sub.Value = "RD After";
ApplyHeaderStyle(cellQ_sub, 3);
var cellR_sub = worksheet.Cell(rowIndex, currentCol++);
cellR_sub.Value = "PH < 8";
ApplyHeaderStyle(cellR_sub, 3);
var cellS_sub = worksheet.Cell(rowIndex, currentCol++);
cellS_sub.Value = "PH After";
ApplyHeaderStyle(cellS_sub, 3);
currentCol += 5; // Skip Total OT, Total ND & OD, Total RD, Total PH, Station, Description (vertically merged)
// Apply borders to the full header range for Row 2
var headerRow2Range = worksheet.Range(rowIndex, 1, rowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow2Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow2Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for data
}
// Method to add headers for HoU, Non-PSTWAIR (NO Basic Salary, ORP, HRP, NO Station)
private void AddHoUNonPSTWAirHeaders(IXLWorksheet worksheet, ref int rowIndex)
{
int startRowIndex = rowIndex;
int currentCol;
// Row 1 Headers
currentCol = 1;
var cellA = worksheet.Cell(rowIndex, currentCol);
cellA.Value = "Day";
ApplyHeaderStyle(cellA, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellB = worksheet.Cell(rowIndex, currentCol);
cellB.Value = "Date";
ApplyHeaderStyle(cellB, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellC = worksheet.Cell(rowIndex, currentCol);
cellC.Value = "Office Hour";
ApplyHeaderStyle(cellC, 2);
var mergedRangeC = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge C,D,E
mergedRangeC.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeC.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellF = worksheet.Cell(rowIndex, currentCol);
cellF.Value = "After Office Hour";
ApplyHeaderStyle(cellF, 2);
var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge F,G,H
mergedRangeF.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeF.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellI = worksheet.Cell(rowIndex, currentCol);
cellI.Value = "OT Hrs (Office Hour)";
ApplyHeaderStyle(cellI, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellJ = worksheet.Cell(rowIndex, currentCol);
cellJ.Value = "OT Hrs (After Office Hour)";
ApplyHeaderStyle(cellJ, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellK = worksheet.Cell(rowIndex, currentCol);
cellK.Value = "Normal Day (After Office)";
ApplyHeaderStyle(cellK, 2); // Changed to 2 to match 'Off Day' color
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellL = worksheet.Cell(rowIndex, currentCol);
cellL.Value = "Off Day";
ApplyHeaderStyle(cellL, 2); // Already using 2
var mergedRangeL = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge L,M,N
mergedRangeL.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeL.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellO = worksheet.Cell(rowIndex, currentCol);
cellO.Value = "Rest Day";
ApplyHeaderStyle(cellO, 2);
var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); // Merge O,P,Q
mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 3;
var cellR = worksheet.Cell(rowIndex, currentCol);
cellR.Value = "Public Holiday";
ApplyHeaderStyle(cellR, 2);
var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); // Merge R,S
mergedRangeR.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
mergedRangeR.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
currentCol += 2;
var cellT = worksheet.Cell(rowIndex, currentCol);
cellT.Value = "Total OT";
ApplyHeaderStyle(cellT, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellU = worksheet.Cell(rowIndex, currentCol);
cellU.Value = "Total ND & OD";
ApplyHeaderStyle(cellU, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellV = worksheet.Cell(rowIndex, currentCol);
cellV.Value = "Total RD";
ApplyHeaderStyle(cellV, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellW = worksheet.Cell(rowIndex, currentCol);
cellW.Value = "Total PH";
ApplyHeaderStyle(cellW, 1); // Changed to style 1 (Darker blue)
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var cellX = worksheet.Cell(rowIndex, currentCol);
cellX.Value = "Description"; // X (No Station Column)
ApplyHeaderStyle(cellX, 1);
worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically
currentCol++;
var headerRow1Range = worksheet.Range(startRowIndex, 1, startRowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow1Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow1Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for sub-headers
// Row 2 Sub-Headers
currentCol = 1;
currentCol += 2; // Day, Date
var cellC_sub = worksheet.Cell(rowIndex, currentCol++);
cellC_sub.Value = "From";
ApplyHeaderStyle(cellC_sub, 3);
var cellD_sub = worksheet.Cell(rowIndex, currentCol++);
cellD_sub.Value = "To";
ApplyHeaderStyle(cellD_sub, 3);
var cellE_sub = worksheet.Cell(rowIndex, currentCol++);
cellE_sub.Value = "Break";
ApplyHeaderStyle(cellE_sub, 3);
var cellF_sub = worksheet.Cell(rowIndex, currentCol++);
cellF_sub.Value = "From";
ApplyHeaderStyle(cellF_sub, 3);
var cellG_sub = worksheet.Cell(rowIndex, currentCol++);
cellG_sub.Value = "To";
ApplyHeaderStyle(cellG_sub, 3);
var cellH_sub = worksheet.Cell(rowIndex, currentCol++);
cellH_sub.Value = "Break";
ApplyHeaderStyle(cellH_sub, 3);
currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged)
var cellK_sub = worksheet.Cell(rowIndex, currentCol++);
cellK_sub.Value = "ND OT";
ApplyHeaderStyle(cellK_sub, 3);
var cellL_sub = worksheet.Cell(rowIndex, currentCol++);
cellL_sub.Value = "OD < 4";
ApplyHeaderStyle(cellL_sub, 3);
var cellM_sub = worksheet.Cell(rowIndex, currentCol++);
cellM_sub.Value = "OD 4-8";
ApplyHeaderStyle(cellM_sub, 3);
var cellN_sub = worksheet.Cell(rowIndex, currentCol++);
cellN_sub.Value = "OD After";
ApplyHeaderStyle(cellN_sub, 3);
var cellO_sub = worksheet.Cell(rowIndex, currentCol++);
cellO_sub.Value = "RD < 4";
ApplyHeaderStyle(cellO_sub, 3);
var cellP_sub = worksheet.Cell(rowIndex, currentCol++);
cellP_sub.Value = "RD 4-8";
ApplyHeaderStyle(cellP_sub, 3);
var cellQ_sub = worksheet.Cell(rowIndex, currentCol++);
cellQ_sub.Value = "RD After";
ApplyHeaderStyle(cellQ_sub, 3);
var cellR_sub = worksheet.Cell(rowIndex, currentCol++);
cellR_sub.Value = "PH < 8";
ApplyHeaderStyle(cellR_sub, 3);
var cellS_sub = worksheet.Cell(rowIndex, currentCol++);
cellS_sub.Value = "PH After";
ApplyHeaderStyle(cellS_sub, 3);
currentCol += 4; // Skip Total OT, Total ND & OD, Total RD, Total PH, Description (vertically merged)
// Apply borders to the full header range for Row 2
var headerRow2Range = worksheet.Range(rowIndex, 1, rowIndex, worksheet.LastColumnUsed().ColumnNumber());
headerRow2Range.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
headerRow2Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
rowIndex++; // Move to the next row for data
}
private uint GetDayCellColorStyleIndex(DateTime date, int? weekendId, List<DateTime> publicHolidays)
{
if (publicHolidays.Contains(date.Date))
return 18; // Style 18: Pink for public holidays
DayOfWeek dayOfWeek = date.DayOfWeek;
if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) ||
(weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday)))
return 19; // Style 19: Light Blue for weekends/off days (Fri/Sat or Sat/Sun)
return 11; // Style 11: Default normal text center aligned
}
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 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 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.Friday || dayOfWeek == DayOfWeek.Saturday)) ||
(weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday)))
{
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Friday) ||
(weekendId == 2 && dayOfWeek == DayOfWeek.Saturday))
{
return "Off Day";
}
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Saturday) ||
(weekendId == 2 && dayOfWeek == DayOfWeek.Sunday))
{
return "Rest Day";
}
}
return "Normal Day";
}
// Modified: ORP = Basic Salary / 26
private decimal CalculateOrp(decimal basicSalary)
{
return basicSalary / 26m;
}
// New: HRP = ORP / 8
private decimal CalculateHrp(decimal orp)
{
return orp / 8m;
}
// Corrected FormatAsHourMinute method
private string FormatAsHourMinute(decimal? totalMinutes, bool isMinutes = false)
{
if (totalMinutes == null || totalMinutes == 0)
return "";
TimeSpan ts;
if (isMinutes)
{
// If isMinutes is true, input is total minutes
ts = TimeSpan.FromMinutes((double)totalMinutes.Value);
}
else
{
// If isMinutes is false, input is decimal hours (e.g., 1.5 for 1 hour 30 mins)
// Convert decimal hours to minutes for TimeSpan.FromMinutes
ts = TimeSpan.FromMinutes((double)(totalMinutes.Value * 60));
}
// Handle negative times if durations can ever be negative, though usually they are clamped at 0
if (ts.TotalMinutes < 0)
{
return $"-{Math.Abs((int)ts.TotalHours)}:{Math.Abs(ts.Minutes):D2}";
}
int hours = (int)ts.TotalHours;
int minutes = ts.Minutes; // TimeSpan.Minutes property gives the minute component (0-59)
return $"{hours}:{minutes:D2}";
}
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;
}
// --- Missing Helper Method: CalculateTimeDifferenceInMinutes ---
private decimal CalculateTimeDifferenceInMinutes(TimeSpan? from, TimeSpan? to)
{
if (!from.HasValue || !to.HasValue) return 0;
double fromTotalMinutes = from.Value.TotalMinutes;
double toTotalMinutes = to.Value.TotalMinutes;
double totalMinutes = toTotalMinutes - fromTotalMinutes;
// If the 'to' time is earlier than the 'from' time, it means the period crosses midnight.
// Add 24 hours (1440 minutes) to account for the next day.
if (totalMinutes < 0)
{
totalMinutes += 24 * 60; // Add 24 hours in minutes
}
return (decimal)Math.Max(0, totalMinutes); // Ensure the result is non-negative
}
// --- End Missing Helper Method ---
private string CalculateOfficeOtHours(OtRegisterModel record)
{
if (!record.OfficeFrom.HasValue || !record.OfficeTo.HasValue) return "";
var duration = record.OfficeTo.Value - record.OfficeFrom.Value;
var totalMinutes = duration.TotalMinutes - (record.OfficeBreak ?? 0);
totalMinutes = Math.Max(0, totalMinutes);
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 "";
var duration = record.AfterTo.Value - record.AfterFrom.Value;
var totalMinutes = duration.TotalMinutes - (record.AfterBreak ?? 0);
totalMinutes = Math.Max(0, totalMinutes);
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);
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);
break;
case "Off Day":
if (officeHrs > 0)
{
if (officeHrs <= 4) result["odUnder4"] = toFixedOrEmpty(officeHrs);
else if (officeHrs > 4 && officeHrs <= 8) result["odBetween4And8"] = toFixedOrEmpty(officeHrs);
}
result["odAfter"] = toFixedOrEmpty(afterHrs);
break;
case "Rest Day":
if (officeHrs > 0)
{
if (officeHrs <= 4) result["rdUnder4"] = toFixedOrEmpty(officeHrs);
else if (officeHrs > 4 && officeHrs <= 8) result["rdBetween4And8"] = toFixedOrEmpty(officeHrs);
}
result["rdAfter"] = toFixedOrEmpty(afterHrs);
break;
case "Public Holiday":
if (officeHrs > 0)
{
if (officeHrs <= 8) result["phUnder8"] = toFixedOrEmpty(officeHrs);
}
result["phAfter"] = toFixedOrEmpty(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)
{
var orp = CalculateOrp(hrp * 208); // Recalculate ORP based on an assumed 208 hours in a month to get back to basic salary, then divide by 26
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");
}
// Modified AddRemarksSection to start at column 1
private void AddRemarksSection(IXLWorksheet worksheet, ref int currentRow, DateTime displayMonth, int? weekendId, List<CalendarModel> publicHolidays)
{
// Start remarks at column 1 for left alignment
int remarksStartColumn = 1;
int remarksRow = currentRow + 1; // Start remarks below the total table
// Public Holiday Color
var phColorCell = worksheet.Cell(remarksRow, remarksStartColumn);
phColorCell.Value = " "; // Small cell for color swatch
phColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); // Pink color
phColorCell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
worksheet.Cell(remarksRow, remarksStartColumn + 1).Value = " Public Holiday";
worksheet.Cell(remarksRow, remarksStartColumn + 1).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
remarksRow++;
// Weekend/Off Day Color
var weekendColorCell = worksheet.Cell(remarksRow, remarksStartColumn);
weekendColorCell.Value = " "; // Small cell for color swatch
weekendColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("ADD8E6"); // Light Blue color
weekendColorCell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
worksheet.Cell(remarksRow, remarksStartColumn + 1).Value = " Weekend";
worksheet.Cell(remarksRow, remarksStartColumn + 1).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
remarksRow++;
remarksRow++; // Blank line for spacing
// --- Public Holidays List for the Month ---
worksheet.Cell(remarksRow, remarksStartColumn).Value = $"Public Holidays in {displayMonth:MMMM yyyy}:";
worksheet.Cell(remarksRow, remarksStartColumn).Style.Font.SetBold();
remarksRow++;
if (publicHolidays != null && publicHolidays.Any())
{
var monthHolidays = publicHolidays
.Where(h => h.HolidayDate.Year == displayMonth.Year && h.HolidayDate.Month == displayMonth.Month)
.OrderBy(h => h.HolidayDate.Day)
.ToList();
if (monthHolidays.Any())
{
foreach (var holiday in monthHolidays)
{
worksheet.Cell(remarksRow, remarksStartColumn).Value = $"- {holiday.HolidayDate:dd MMMM}: {holiday.HolidayName}";
worksheet.Cell(remarksRow, remarksStartColumn).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
remarksRow++;
}
}
else
{
worksheet.Cell(remarksRow, remarksStartColumn).Value = "No public holidays recorded for this month.";
worksheet.Cell(remarksRow, remarksStartColumn).Style.Font.SetItalic();
remarksRow++;
}
}
else
{
worksheet.Cell(remarksRow, remarksStartColumn).Value = "No public holiday data found.";
worksheet.Cell(remarksRow, remarksStartColumn).Style.Font.SetItalic();
remarksRow++;
}
// Update currentRow to the bottom of the remarks section
currentRow = remarksRow;
}
private string GetWeekendDefinition(int? weekendId)
{
if (!weekendId.HasValue) return "N/A";
switch (weekendId)
{
case 1: return "Friday & Saturday";
case 2: return "Saturday & Sunday";
default: return "Unknown";
}
}
}
}