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 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 { 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 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 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 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 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 ClassifyOt(OtRegisterModel record, decimal hrp, List 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 { ["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 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 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 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 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 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 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"; } } } }