From efd69601ec5f72df9e119f456b84508f1e7bda5f Mon Sep 17 00:00:00 2001 From: Naz <2022755409@student.uitm.edu.my> Date: Wed, 11 Jun 2025 10:30:45 +0800 Subject: [PATCH] Latest --- .../ApprovalDashboardController.cs | 2 +- Areas/OTcalculate/Models/OtRegisterModel.cs | 18 +- Areas/OTcalculate/Models/StaffSignModel.cs | 2 +- Areas/OTcalculate/Services/OvertimeExcel.cs | 444 ++++++++---------- Areas/OTcalculate/Services/OvertimePDF.cs | 196 +++----- .../Views/ApprovalDashboard/Approval.cshtml | 40 +- .../Views/ApprovalDashboard/OtReview.cshtml | 186 +++----- .../Views/HrDashboard/Calendar.cshtml | 2 +- .../Views/HrDashboard/HrUserSetting.cshtml | 32 +- .../OTcalculate/Views/HrDashboard/Rate.cshtml | 2 +- .../Views/HrDashboard/Settings.cshtml | 3 +- .../Views/Overtime/EditOvertime.cshtml | 43 +- .../Views/Overtime/OtRecords.cshtml | 20 +- .../Views/Overtime/OtRegister.cshtml | 50 +- .../Views/Overtime/OtStatus.cshtml | 14 +- Controllers/API/OvertimeAPI.cs | 202 +++----- Program.cs | 2 + 17 files changed, 469 insertions(+), 789 deletions(-) diff --git a/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs b/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs index e4ebfa6..58f3ea7 100644 --- a/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs +++ b/Areas/OTcalculate/Controllers/ApprovalDashboardController.cs @@ -20,7 +20,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers } public IActionResult OtReview(int statusId) { - ViewBag.StatusId = statusId; // If needed in the view + ViewBag.StatusId = statusId; return View(); } diff --git a/Areas/OTcalculate/Models/OtRegisterModel.cs b/Areas/OTcalculate/Models/OtRegisterModel.cs index 8d3e950..2184a82 100644 --- a/Areas/OTcalculate/Models/OtRegisterModel.cs +++ b/Areas/OTcalculate/Models/OtRegisterModel.cs @@ -47,36 +47,34 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Models return TimeSpan.TryParse(time, out TimeSpan result) ? result : null; } - // OtRegisterEditDto.cs public class OtRegisterEditDto { public int OvertimeId { get; set; } public DateTime OtDate { get; set; } - public string? OfficeFrom { get; set; } // Use string to match input type (e.g., "09:00") - public string? OfficeTo { get; set; } // Use string to match input type + public string? OfficeFrom { get; set; } + public string? OfficeTo { get; set; } public int? OfficeBreak { get; set; } - public string? AfterFrom { get; set; } // Use string to match input type - public string? AfterTo { get; set; } // Use string to match input type + public string? AfterFrom { get; set; } + public string? AfterTo { get; set; } public int? AfterBreak { get; set; } public int? StationId { get; set; } public string? OtDescription { get; set; } - // You might also need to send the StatusId if it's relevant for the update logic + public int StatusId { get; set; } public string? OtDays { get; set; } } public class OtUpdateLog { - public string ApproverRole { get; set; } // e.g., "HoU", "HoD", "Manager", "HR" + public string ApproverRole { get; set; } public int ApproverUserId { get; set; } public DateTime UpdateTimestamp { get; set; } - public string ChangeType { get; set; } // New: "Edit" or "Delete" + public string ChangeType { get; set; } + - // For "Edit" type public OtRegisterModel? BeforeEdit { get; set; } public OtRegisterEditDto? AfterEdit { get; set; } - // For "Delete" type public OtRegisterModel? DeletedRecord { get; set; } } diff --git a/Areas/OTcalculate/Models/StaffSignModel.cs b/Areas/OTcalculate/Models/StaffSignModel.cs index 999cf9b..6390c9c 100644 --- a/Areas/OTcalculate/Models/StaffSignModel.cs +++ b/Areas/OTcalculate/Models/StaffSignModel.cs @@ -23,6 +23,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Models { public string ApproverName { get; set; } public byte[]? SignatureImage { get; set; } - public DateTime? ApprovedDate { get; set; } // New property for the approval date + public DateTime? ApprovedDate { get; set; } } } diff --git a/Areas/OTcalculate/Services/OvertimeExcel.cs b/Areas/OTcalculate/Services/OvertimeExcel.cs index 26a5054..da9a93e 100644 --- a/Areas/OTcalculate/Services/OvertimeExcel.cs +++ b/Areas/OTcalculate/Services/OvertimeExcel.cs @@ -5,7 +5,7 @@ using System.Linq; using Microsoft.EntityFrameworkCore; using ClosedXML.Excel; using ClosedXML.Excel.Drawings; -using Microsoft.AspNetCore.Hosting; // Added for IWebHostEnvironment +using Microsoft.AspNetCore.Hosting; using PSTW_CentralSystem.Areas.OTcalculate.Models; using PSTW_CentralSystem.Models; @@ -17,12 +17,12 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services public class OvertimeExcel { private readonly CentralSystemContext _centralDbContext; - private readonly IWebHostEnvironment _env; // Added for IWebHostEnvironment + private readonly IWebHostEnvironment _env; - public OvertimeExcel(CentralSystemContext centralDbContext, IWebHostEnvironment env) // Modified constructor + public OvertimeExcel(CentralSystemContext centralDbContext, IWebHostEnvironment env) { _centralDbContext = centralDbContext; - _env = env; // Initialize IWebHostEnvironment + _env = env; } public MemoryStream GenerateOvertimeExcel( @@ -71,7 +71,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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++; @@ -91,17 +90,15 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services worksheet.Cell(currentRow, 1).Style.Font.SetBold(); currentRow++; - currentRow++; // Add an empty row for spacing + currentRow++; - - // --- 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) @@ -113,7 +110,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services AddHoUNonPSTWAirHeaders(worksheet, ref currentRow); } } - else // !isHoU path + else { if (showStationColumn) { @@ -125,7 +122,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services } } } - // --- End Conditional Header Generation --- var userSetting = _centralDbContext.Hrusersetting @@ -412,7 +408,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (!isHoU) { - totalLabelEndColumnIndex = 3; // Basic Salary, ORP, HRP are new first three columns + totalLabelEndColumnIndex = 3; } else { @@ -442,7 +438,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (!isHoU) { - // Adjusted column indices due to Basic Salary, ORP, HRP being added at the beginning colOfficeBreakIndex = 8; colAfterBreakIndex = 11; colNdOtIndex = 14; @@ -498,18 +493,16 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (!isHoU) { - worksheet.Cell(currentRow, colOtAmtIndex).Value = grandTotalOtAmount.ToString("N2"); + worksheet.Cell(currentRow, colOtAmtIndex).Value = Math.Round(grandTotalOtAmount, MidpointRounding.AwayFromZero).ToString("F2"); } } currentRow++; - // --- Approval Signatures and Remarks Section (Combined) --- if (!isSimplifiedExport && otStatus != null) { - currentRow++; // Add a blank line before approval section + currentRow++; - // Signature section header (left side) worksheet.Cell(currentRow, 1).Value = "Approval Summary:"; worksheet.Cell(currentRow, 1).Style.Font.SetBold(); currentRow++; @@ -517,7 +510,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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) @@ -546,7 +538,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services } } - // Loop to add all approved signatures var approvalFlow = _centralDbContext.Hrusersetting .Include(us => us.Approvalflow) .ThenInclude(af => af.HeadOfUnit) @@ -593,28 +584,26 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services hasAnyApprovedSignature = true; } - // Add public holidays list below the color indicators - int remarksColorStartRow = currentRow - 1; // Adjust as needed for spacing from signatures + int remarksColorStartRow = currentRow - 1; var phColorCell = worksheet.Cell(remarksColorStartRow, remarksStartColumn); - phColorCell.Value = " "; // Small cell for color swatch - phColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); // Pink color + phColorCell.Value = " "; + phColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); 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.Value = " "; + weekendColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("ADD8E6"); 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 + + int holidayListRow = remarksColorStartRow + 3; worksheet.Cell(holidayListRow, remarksStartColumn).Value = $"Public Holidays in {displayMonth:MMMM yyyy}:"; worksheet.Cell(holidayListRow, remarksStartColumn).Style.Font.SetBold(); holidayListRow++; @@ -649,13 +638,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + int messageRow = holidayListRow + 2; - // 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(); @@ -666,25 +652,17 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + else if (isSimplifiedExport) { - // 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) @@ -760,7 +738,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services worksheet.Column(descColIndex).Width = 60; } } - // --- End Column Sizing Logic --- workbook.SaveAs(stream); } @@ -769,13 +746,11 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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); @@ -831,16 +806,15 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + rowIndex++; + - // Row 2 Sub-Headers currentCol = 1; - currentCol += 2; // Skip Day and Date (vertically merged) + currentCol += 2; var cellOfficeFrom = worksheet.Cell(rowIndex, currentCol++); cellOfficeFrom.Value = "From"; @@ -866,33 +840,22 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services cellAfterBreak.Value = "Break"; ApplyHeaderStyle(cellAfterBreak, 3); - // Skip Total OT Hours and Break (min) (vertically merged) currentCol += 2; if (showStationColumn) { - currentCol++; // Skip Station (vertically merged) + currentCol++; } - // 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 + rowIndex++; } - - // --- 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(); @@ -904,31 +867,31 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + case 1: + cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#78aafa"); + cell.Style.Font.FontColor = XLColor.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 + case 2: + cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#DDEBF7"); 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 + case 3: + cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#C6E0B4"); 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 + case 4: + cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#F8CBAD"); cell.Style.Font.FontColor = XLColor.Black; break; - case 5: // Totals row background - Purple - cell.Style.Fill.BackgroundColor = XLColor.FromHtml("D8D1F5"); // Original purple for totals + case 5: + cell.Style.Fill.BackgroundColor = XLColor.FromHtml("D8D1F5"); 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 + case 6: + cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#FF69B4"); + cell.Style.Font.FontColor = XLColor.Black; break; - default: // Fallback + default: cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#A9D08E"); cell.Style.Font.FontColor = XLColor.Black; break; @@ -945,60 +908,58 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services switch (styleIndex) { - case 18: // Pink for public holidays (FFC0CB) + case 18: cell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); break; - case 19: // Light Blue for weekends/off days (ADD8E6) + case 19: cell.Style.Fill.BackgroundColor = XLColor.FromHtml("ADD8E6"); break; - default: // Default (no specific background fill) + default: 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 + ApplyHeaderStyle(cellA, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellB, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellC, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellD = worksheet.Cell(rowIndex, currentCol); cellD.Value = "Day"; ApplyHeaderStyle(cellD, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellE = worksheet.Cell(rowIndex, currentCol); cellE.Value = "Date"; ApplyHeaderStyle(cellE, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeF.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeF.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1006,7 +967,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeI = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeI.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeI.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1014,25 +975,25 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellN, 2); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellO, 2); + var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1040,7 +1001,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeR.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeR.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1048,7 +1009,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeU = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); mergedRangeU.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeU.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 2; @@ -1056,43 +1017,43 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services var cellW = worksheet.Cell(rowIndex, currentCol); cellW.Value = "Total OT"; ApplyHeaderStyle(cellW, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellY = worksheet.Cell(rowIndex, currentCol); cellY.Value = "Total RD"; ApplyHeaderStyle(cellY, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellZ = worksheet.Cell(rowIndex, currentCol); cellZ.Value = "Total PH"; ApplyHeaderStyle(cellZ, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellAA, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellAB = worksheet.Cell(rowIndex, currentCol); cellAB.Value = "Station"; ApplyHeaderStyle(cellAB, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellAC = worksheet.Cell(rowIndex, currentCol); cellAC.Value = "Description"; ApplyHeaderStyle(cellAC, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var headerRow1Range = worksheet.Range(startRowIndex, 1, startRowIndex, worksheet.LastColumnUsed().ColumnNumber()); @@ -1100,14 +1061,11 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services headerRow1Range.Style.Border.InsideBorder = XLBorderStyleValues.Thin; - rowIndex++; // Move to the next row for sub-headers + rowIndex++; - // 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 + + currentCol += 5; var cellF_sub = worksheet.Cell(rowIndex, currentCol++); cellF_sub.Value = "From"; @@ -1133,7 +1091,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services cellK_sub.Value = "Break"; ApplyHeaderStyle(cellK_sub, 3); - currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged) + currentCol += 2; var cellN_sub = worksheet.Cell(rowIndex, currentCol++); cellN_sub.Value = "ND OT"; @@ -1171,59 +1129,55 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + rowIndex++; } - // 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 + ApplyHeaderStyle(cellA, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellB, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellC, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellD = worksheet.Cell(rowIndex, currentCol); cellD.Value = "Day"; ApplyHeaderStyle(cellD, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellE = worksheet.Cell(rowIndex, currentCol); cellE.Value = "Date"; ApplyHeaderStyle(cellE, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeF.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeF.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1231,7 +1185,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeI = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeI.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeI.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1239,25 +1193,25 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellN, 2); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellO, 2); + var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1265,7 +1219,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeR.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeR.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1273,7 +1227,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeU = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); mergedRangeU.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeU.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 2; @@ -1281,48 +1235,47 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services var cellW = worksheet.Cell(rowIndex, currentCol); cellW.Value = "Total OT"; ApplyHeaderStyle(cellW, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellY = worksheet.Cell(rowIndex, currentCol); cellY.Value = "Total RD"; ApplyHeaderStyle(cellY, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellZ = worksheet.Cell(rowIndex, currentCol); cellZ.Value = "Total PH"; ApplyHeaderStyle(cellZ, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellAA, 6); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellAB = worksheet.Cell(rowIndex, currentCol); - cellAB.Value = "Description"; // AB (No Station Column) + cellAB.Value = "Description"; ApplyHeaderStyle(cellAB, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + rowIndex++; - // Row 2 Sub-Headers currentCol = 1; - currentCol += 5; // Basic Salary, ORP, HRP, Day, Date + currentCol += 5; var cellF_sub = worksheet.Cell(rowIndex, currentCol++); cellF_sub.Value = "From"; @@ -1348,7 +1301,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services cellK_sub.Value = "Break"; ApplyHeaderStyle(cellK_sub, 3); - currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged) + currentCol += 2; var cellN_sub = worksheet.Cell(rowIndex, currentCol++); cellN_sub.Value = "ND OT"; @@ -1386,40 +1339,37 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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) + currentCol += 6; - // 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 + rowIndex++; } - // 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 + ApplyHeaderStyle(cellA, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellB, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + var mergedRangeC = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeC.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeC.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1427,33 +1377,33 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); 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 + ApplyHeaderStyle(cellI, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellJ, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellK, 2); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellL, 2); + var mergedRangeL = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeL.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeL.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1461,7 +1411,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1469,56 +1419,55 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); 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 + ApplyHeaderStyle(cellT, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellU, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellV, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellW, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellX, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellY, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + rowIndex++; - // Row 2 Sub-Headers currentCol = 1; - currentCol += 2; // Day, Date + currentCol += 2; var cellC_sub = worksheet.Cell(rowIndex, currentCol++); cellC_sub.Value = "From"; @@ -1544,7 +1493,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services cellH_sub.Value = "Break"; ApplyHeaderStyle(cellH_sub, 3); - currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged) + currentCol += 2; var cellK_sub = worksheet.Cell(rowIndex, currentCol++); cellK_sub.Value = "ND OT"; @@ -1582,40 +1531,37 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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) + currentCol += 5; - // 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 + rowIndex++; } - // 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 + ApplyHeaderStyle(cellA, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellB, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + var mergedRangeC = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeC.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeC.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1623,33 +1569,33 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeF = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); 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 + ApplyHeaderStyle(cellI, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellJ, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellK, 2); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellL, 2); + var mergedRangeL = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeL.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeL.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1657,7 +1603,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeO = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 2).Merge(); mergedRangeO.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); mergedRangeO.Style.Border.InsideBorder = XLBorderStyleValues.Thin; currentCol += 3; @@ -1665,50 +1611,49 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + var mergedRangeR = worksheet.Range(rowIndex, currentCol, rowIndex, currentCol + 1).Merge(); 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 + ApplyHeaderStyle(cellT, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellU, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellV, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + ApplyHeaderStyle(cellW, 1); + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); currentCol++; var cellX = worksheet.Cell(rowIndex, currentCol); - cellX.Value = "Description"; // X (No Station Column) + cellX.Value = "Description"; ApplyHeaderStyle(cellX, 1); - worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); // Merge vertically + worksheet.Range(rowIndex, currentCol, rowIndex + 1, currentCol).Merge(); 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 + rowIndex++; - // Row 2 Sub-Headers currentCol = 1; - currentCol += 2; // Day, Date + currentCol += 2; var cellC_sub = worksheet.Cell(rowIndex, currentCol++); cellC_sub.Value = "From"; @@ -1734,7 +1679,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services cellH_sub.Value = "Break"; ApplyHeaderStyle(cellH_sub, 3); - currentCol += 2; // Skip OT Hrs (Office Hour) and OT Hrs (After Office Hour) (vertically merged) + currentCol += 2; var cellK_sub = worksheet.Cell(rowIndex, currentCol++); cellK_sub.Value = "ND OT"; @@ -1772,28 +1717,27 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services cellS_sub.Value = "PH After"; ApplyHeaderStyle(cellS_sub, 3); - currentCol += 4; // Skip Total OT, Total ND & OD, Total RD, Total PH, Description (vertically merged) + currentCol += 4; - // 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 + rowIndex++; } private uint GetDayCellColorStyleIndex(DateTime date, int? weekendId, List publicHolidays) { if (publicHolidays.Contains(date.Date)) - return 18; // Style 18: Pink for public holidays + return 18; 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 19; - return 11; // Style 11: Default normal text center aligned + return 11; } private bool IsAdmin(int userId) @@ -1840,20 +1784,18 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services return "Normal Day"; } - // Modified: ORP = Basic Salary / 26 + // ORP = Basic Salary / 26 private decimal CalculateOrp(decimal basicSalary) { return basicSalary / 26m; } - // New: HRP = ORP / 8 + // 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) @@ -1862,24 +1804,20 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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) + int minutes = ts.Minutes; return $"{hours}:{minutes:D2}"; } @@ -1900,7 +1838,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services return 0; } - // --- Missing Helper Method: CalculateTimeDifferenceInMinutes --- private decimal CalculateTimeDifferenceInMinutes(TimeSpan? from, TimeSpan? to) { if (!from.HasValue || !to.HasValue) return 0; @@ -1910,17 +1847,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + totalMinutes += 24 * 60; } - return (decimal)Math.Max(0, totalMinutes); // Ensure the result is non-negative + return (decimal)Math.Max(0, totalMinutes); } - // --- End Missing Helper Method --- - private string CalculateOfficeOtHours(OtRegisterModel record) { @@ -2060,7 +1994,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 orp = CalculateOrp(hrp * 208); var dayType = GetDayType(record.OtDate, weekendId, publicHolidays); var officeRaw = CalculateOfficeOtHours(record); @@ -2111,37 +2045,32 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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 + int remarksStartColumn = 1; + int remarksRow = currentRow + 1; + var phColorCell = worksheet.Cell(remarksRow, remarksStartColumn); - phColorCell.Value = " "; // Small cell for color swatch - phColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); // Pink color + phColorCell.Value = " "; + phColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("FFC0CB"); 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.Value = " "; + weekendColorCell.Style.Fill.BackgroundColor = XLColor.FromHtml("ADD8E6"); 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 + remarksRow++; - // --- 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++; @@ -2176,7 +2105,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services remarksRow++; } - // Update currentRow to the bottom of the remarks section currentRow = remarksRow; } diff --git a/Areas/OTcalculate/Services/OvertimePDF.cs b/Areas/OTcalculate/Services/OvertimePDF.cs index d864634..f32e9fa 100644 --- a/Areas/OTcalculate/Services/OvertimePDF.cs +++ b/Areas/OTcalculate/Services/OvertimePDF.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using PSTW_CentralSystem.Areas.OTcalculate.Models; -using PSTW_CentralSystem.Models; // Ensure this is included for CalendarModel, StateModel etc. +using PSTW_CentralSystem.Models; using Microsoft.EntityFrameworkCore; using PSTW_CentralSystem.DBContext; @@ -22,14 +22,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services } public MemoryStream GenerateOvertimeTablePdf( - List records, - UserModel user, - decimal userRate, - DateTime? selectedMonth = null, - byte[]? logoImage = null, - bool isHoU = false, - string? flexiHour = null, - List? approvedSignatures = null) + List records, + UserModel user, + decimal userRate, + DateTime? selectedMonth = null, + byte[]? logoImage = null, + bool isHoU = false, + string? flexiHour = null, + List? approvedSignatures = null) { bool isAdminUser = IsAdmin(user.Id); bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser; @@ -39,15 +39,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services var allDatesInMonth = GetAllDatesInMonth(displayMonth); - // Fetch user setting here once var userSetting = _centralDbContext.Hrusersetting .Include(us => us.State) .FirstOrDefault(us => us.UserId == user.Id); - // Fetch actual public holiday objects including their names for the current month/year and user's state var publicHolidaysForUser = _centralDbContext.Holidays .Where(h => userSetting != null && h.StateId == userSetting.State.StateId && h.HolidayDate.Month == displayMonth.Month && h.HolidayDate.Year == displayMonth.Year) - .OrderBy(h => h.HolidayDate) // Order by date for display + .OrderBy(h => h.HolidayDate) .ToList(); records = records.OrderBy(r => r.OtDate).ToList(); @@ -62,7 +60,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services page.Content().Column(column => { - // Top Section: Logo, User Info, Generated Date column.Item().Row(row => { row.RelativeItem(2).Column(col => @@ -72,7 +69,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services col.Item().PaddingBottom(10).Container().Height(25).Image(logoImage, ImageScaling.FitArea); } - // Option 1: Inline with Clear Labels for user details col.Item().PaddingBottom(2).Text(text => { text.Span("Name: ").SemiBold().FontSize(9); @@ -93,12 +89,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services .FontSize(9).FontColor(Colors.Grey.Medium); }); - // Overtime Record title remains separate for clarity column.Item().PaddingTop(3).Row(row => { row.RelativeItem(2).Text($"Overtime Record: {displayMonth:MMMM yyyy}").FontSize(9).Italic(); - // Legend (remains on the same line as Overtime Record) row.RelativeItem(1).AlignRight().Column(legendCol => { legendCol.Item().Element(e => e.ShowEntire().Row(subRow => @@ -123,15 +117,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2); - // Pass the fetched publicHolidays to ComposeTable column.Item().Element(container => ComposeTable(container, records, user, userRate, showStationColumn, allDatesInMonth, isHoU, publicHolidaysForUser.Select(h => h.HolidayDate.Date).ToList())); - // Approval Signatures and Remarks section column.Item().PaddingTop(20).Element(container => { container.ShowEntire().Row(row => { - // Left side - Approval Signatures + row.RelativeItem().Element(e => e.ShowEntire().Column(approvalCol => { if (approvedSignatures != null && approvedSignatures.Any()) @@ -161,8 +153,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (approval.ApprovedDate.HasValue) { individualApprovalColumn.Item().PaddingTop(2) - .Text(approval.ApprovedDate.Value.ToString("dd MMMM yyyy")) // Changed format to avoid special characters - .FontSize(8).AlignCenter(); + .Text(approval.ApprovedDate.Value.ToString("dd MMMM yyyy")) + .FontSize(8).AlignCenter(); } })); } @@ -170,12 +162,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services } })); - // Right side - Remarks and Public Holidays row.RelativeItem().Element(e => e.ShowEntire().Column(remarksCol => { remarksCol.Item().Element(e => e.ShowEntire().Row(remarksRow => { - // Public Holidays List as a mini-table remarksRow.RelativeItem(2).Element(e => e.ShowEntire().AlignRight().Column(holidayListCol => { holidayListCol.Item().Text("Public Holidays:").FontSize(9).SemiBold(); @@ -183,8 +173,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { table.ColumnsDefinition(columns => { - columns.RelativeColumn(1); // For Date - columns.RelativeColumn(2); // For Holiday Name + columns.RelativeColumn(1); + columns.RelativeColumn(2); }); table.Header(header => @@ -204,8 +194,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services else { table.Cell().ColumnSpan(2).Border(0.25f).Padding(2) - .Text($"No public holidays found for {userSetting?.State?.StateName ?? "this state"} in {displayMonth:MMMM yyyy}.") // Changed format - .FontSize(7).Italic().AlignCenter(); + .Text($"No public holidays found for {userSetting?.State?.StateName ?? "this state"} in {displayMonth:MMMM yyyy}.") + .FontSize(7).Italic().AlignCenter(); } }); })); @@ -213,7 +203,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services })); }); }); - // Add the automatically generated document text only if signatures are present + if (approvedSignatures != null && approvedSignatures.Any()) { column.Item().PaddingTop(20).AlignCenter().Text("This is an automatically generated document. No signature is required for validation.").FontSize(8).Italic().FontColor(Colors.Grey.Darken2); @@ -363,7 +353,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services bool alternate = false; - // Changed calculations as per your request var basicSalary = userRate; // userRate is now treated as basic salary var orp = basicSalary / 26m; // Calculate ORP from basicSalary var hrp = orp / 8m; // Calculate HRP from ORP @@ -431,7 +420,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services AddCell(!hasPrintedSalaryDetails ? $"{orp:F2}" : ""); AddCell(!hasPrintedSalaryDetails ? $"{hrp:F2}" : ""); } - hasPrintedSalaryDetails = true; // Ensure these values are printed only once + hasPrintedSalaryDetails = true; if (date.Date != previousDate?.Date) { @@ -554,7 +543,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (!isHoU) { table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3) - .Text($"{grandTotalOtAmount:N2}").Bold().FontSize(6).AlignCenter(); + .Text($"{Math.Round(grandTotalOtAmount, MidpointRounding.AwayFromZero):F2}").Bold().FontSize(6).AlignCenter(); } if (showStationColumn) @@ -566,12 +555,12 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services } public MemoryStream GenerateSimpleOvertimeTablePdf( - List records, - UserModel user, // User parameter added here - decimal userRate, - DateTime? selectedMonth = null, - byte[]? logoImage = null, - string? flexiHour = null) + List records, + UserModel user, + decimal userRate, + DateTime? selectedMonth = null, + byte[]? logoImage = null, + string? flexiHour = null) { bool isAdminUser = IsAdmin(user.Id); bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser; @@ -581,15 +570,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services var allDatesInMonth = GetAllDatesInMonth(displayMonth); - // Fetch user setting here once var userSetting = _centralDbContext.Hrusersetting .Include(us => us.State) .FirstOrDefault(us => us.UserId == user.Id); - // Fetch actual public holiday objects including their names for the current month/year and user's state var publicHolidaysForUser = _centralDbContext.Holidays .Where(h => userSetting != null && h.StateId == userSetting.State.StateId && h.HolidayDate.Month == displayMonth.Month && h.HolidayDate.Year == displayMonth.Year) - .OrderBy(h => h.HolidayDate) // Order by date for display + .OrderBy(h => h.HolidayDate) .ToList(); records = records.OrderBy(r => r.OtDate).ToList(); @@ -604,7 +591,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services page.Content().Column(column => { - // Top Section: Logo, User Info, Generated Date column.Item().Row(row => { row.RelativeItem(2).Column(col => @@ -614,7 +600,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services col.Item().PaddingBottom(10).Container().Height(25).Image(logoImage, ImageScaling.FitArea); } - // Option 1: Inline with Clear Labels for user details col.Item().PaddingBottom(2).Text(text => { text.Span("Name: ").SemiBold().FontSize(9); @@ -635,12 +620,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services .FontSize(9).FontColor(Colors.Grey.Medium); }); - // Overtime Record title remains separate for clarity column.Item().PaddingTop(3).Row(row => { row.RelativeItem(2).Text($"Overtime Record: {displayMonth:MMMM yyyy}").SemiBold().FontSize(9).Italic(); - // Legend (remains on the same line as Overtime Record) row.RelativeItem(1).AlignRight().Column(legendCol => { legendCol.Item().Element(e => e.ShowEntire().Row(subRow => @@ -664,20 +647,17 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2); - // Pass the fetched publicHolidays to ComposeSimpleTable column.Item().Element(container => ComposeSimpleTable(container, records, user, showStationColumn, allDatesInMonth, publicHolidaysForUser.Select(h => h.HolidayDate.Date).ToList())); - // Public Holidays List (this section remains as is for now, below the table) column.Item().PaddingTop(20).Element(container => { container.ShowEntire().Row(row => { row.RelativeItem().Column(legendCol => { - // Removed the legend remarks from here as they are now above the table + }); - // Public Holidays List (on the right) row.RelativeItem(2).AlignRight().Column(holidayListCol => { holidayListCol.Item().Element(e => e.ShowEntire().Text("Public Holidays:").FontSize(9).SemiBold()); @@ -686,8 +666,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { table.ColumnsDefinition(columns => { - columns.RelativeColumn(1); // For Date - columns.RelativeColumn(2); // For Holiday Name + columns.RelativeColumn(1); + columns.RelativeColumn(2); }); table.Header(header => @@ -708,7 +688,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { table.Cell().ColumnSpan(2).Border(0.25f).Padding(2) .Text($"No public holidays found for {userSetting?.State?.StateName ?? "this state"} in {displayMonth:MMMM yyyy}.") // Changed format - .FontSize(7).Italic().AlignCenter(); + .FontSize(7).Italic().AlignCenter(); } }); }); @@ -722,21 +702,18 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services return stream; } - // IMPORTANT: The ComposeSimpleTable method signature now accepts List for public holidays. private void ComposeSimpleTable(IContainer container, List records, UserModel user, bool showStationColumn, List allDatesInMonth, List publicHolidays) { var recordsGroupedByDate = records .GroupBy(r => r.OtDate.Date) .ToDictionary(g => g.Key, g => g.ToList()); - // Fetch user settings and public holidays within ComposeSimpleTable var userSetting = _centralDbContext.Hrusersetting .Include(us => us.State) .FirstOrDefault(us => us.UserId == user.Id); container.Table(table => { - // Define columns table.ColumnsDefinition(columns => { columns.RelativeColumn(0.8f); // Day (ddd) @@ -766,53 +743,52 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services // Header table.Header(header => { - // First row of the header header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3) .Text("Day").FontSize(6).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3) - .Text("Date").FontSize(6).Bold().AlignCenter(); + .Text("Date").FontSize(6).Bold().AlignCenter(); header.Cell().ColumnSpan(3).Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("Office Hour").FontSize(6).Bold().AlignCenter(); + .Text("Office Hour").FontSize(6).Bold().AlignCenter(); header.Cell().ColumnSpan(3).Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("After Office Hour").FontSize(6).Bold().AlignCenter(); + .Text("After Office Hour").FontSize(6).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3) - .Text("Total OT Hours").FontSize(6).Bold().AlignCenter(); + .Text("Total OT Hours").FontSize(6).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#fce5cd").Border(0.25f).Padding(3) - .Text("Break (min)").FontSize(6).Bold().AlignCenter(); + .Text("Break (min)").FontSize(6).Bold().AlignCenter(); if (showStationColumn) { header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(3) - .Text("Station").FontSize(6).Bold().AlignCenter(); + .Text("Station").FontSize(6).Bold().AlignCenter(); } header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(3) - .Text("Description").FontSize(6).Bold().AlignCenter(); + .Text("Description").FontSize(6).Bold().AlignCenter(); // Second row of the header (sub-headers) header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3) .Text("From").FontSize(6).Bold().AlignCenter(); header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("To").FontSize(6).Bold().AlignCenter(); + .Text("To").FontSize(6).Bold().AlignCenter(); header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("Break").FontSize(6).Bold().AlignCenter(); + .Text("Break").FontSize(6).Bold().AlignCenter(); header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("From").FontSize(6).Bold().AlignCenter(); + .Text("From").FontSize(6).Bold().AlignCenter(); header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("To").FontSize(6).Bold().AlignCenter(); + .Text("To").FontSize(6).Bold().AlignCenter(); header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3) - .Text("Break").FontSize(6).Bold().AlignCenter(); + .Text("Break").FontSize(6).Bold().AlignCenter(); }); decimal totalHours = 0; decimal totalBreak = 0; bool alternate = false; - DateTime? previousDate = null; // To avoid repeating Day/Date for multiple records on the same day + DateTime? previousDate = null; foreach (var date in allDatesInMonth) { @@ -822,7 +798,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services foreach (var record in recordsToShow) { - // Get background color for the current date based on user's state settings var backgroundColor = GetDayCellBackgroundColor(date, userSetting?.State?.WeekendId, publicHolidays); string rowBg = alternate ? "#f9f9f9" : "#ffffff"; @@ -836,7 +811,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (center) text.AlignCenter(); } - // Apply background color to Day and Date cells if (date.Date != previousDate?.Date) { AddCell(date.ToString("ddd"), true, backgroundColor); @@ -844,14 +818,11 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services } else { - // If multiple OT entries for the same day, don't repeat Day and Date, but still apply background AddCell("", true, backgroundColor); AddCell("", true, backgroundColor); } - previousDate = date; // Update previousDate for the next iteration + previousDate = date; - - // Calculate values var officeHours = CalculateOfficeOtHours(record); var afterHours = CalculateAfterOfficeOtHours(record); var totalTime = ConvertTimeToDecimal(officeHours) + ConvertTimeToDecimal(afterHours); @@ -874,7 +845,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services AddCell(FormatAsHourMinute(totalTime, isMinutes: false)); AddCell(FormatAsHourMinute(breakTime, isMinutes: true)); - // Station (if applicable) + // Station if (showStationColumn) { AddCell(record.Stations?.StationName ?? ""); @@ -882,7 +853,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services // Description table.Cell().Background(rowBg).Border(0.25f).Padding(2) - .Text(record.OtDescription ?? "").FontSize(6).AlignLeft(); + .Text(record.OtDescription ?? "").FontSize(6).AlignLeft(); alternate = !alternate; } @@ -891,21 +862,17 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services // Footer with totals table.Footer(footer => { - // "TOTAL" will span only the first two columns (Day and Date) + // TOTAL footer.Cell().ColumnSpan(2).Background("#d8d1f5").Border(0.80f).Padding(3) .Text("TOTAL").Bold().FontSize(6).AlignCenter(); - // Create an empty cell that spans the remaining columns before "Total OT Hours" - // Remaining columns: Office Hour (3) + After Office Hour (3) = 6 columns footer.Cell().ColumnSpan(6).Background("#d8d1f5").Border(0.80f).Padding(3).Text("").FontSize(6).AlignCenter(); - - // These remain aligned with their respective columns footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3) .Text(FormatAsHourMinute(totalHours, isMinutes: false)).Bold().FontSize(6).AlignCenter(); footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3) - .Text(FormatAsHourMinute(totalBreak, isMinutes: true)).Bold().FontSize(6).AlignCenter(); + .Text(FormatAsHourMinute(totalBreak, isMinutes: true)).Bold().FontSize(6).AlignCenter(); if (showStationColumn) { @@ -935,9 +902,9 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services var lastDate = records.Last().OtDate; if (firstDate.Month == lastDate.Month && firstDate.Year == lastDate.Year) - return firstDate.ToString("MMMM yyyy"); // Fixed formatting to yyyy + return firstDate.ToString("MMMM yyyy"); - return $"{firstDate:MMM yyyy} - {lastDate:MMM yyyy}"; // Fixed formatting to yyyy + return $"{firstDate:MMM yyyy} - {lastDate:MMM yyyy}"; } private static IContainer CellStyle(IContainer container) @@ -956,8 +923,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services return ""; TimeSpan ts = isMinutes - ? TimeSpan.FromMinutes((double)hoursOrMinutes.Value) - : TimeSpan.FromHours((double)hoursOrMinutes.Value); + ? TimeSpan.FromMinutes((double)hoursOrMinutes.Value) + : TimeSpan.FromHours((double)hoursOrMinutes.Value); int totalHours = (int)(ts.TotalHours); int minutes = (int)(ts.Minutes); @@ -974,14 +941,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services DayOfWeek dayOfWeek = date.DayOfWeek; - if ((weekendId == 1 && dayOfWeek == DayOfWeek.Saturday) || // Assuming weekendId 1 means Saturday is Rest Day - (weekendId == 2 && dayOfWeek == DayOfWeek.Sunday)) // Assuming weekendId 2 means Sunday is Rest Day + if ((weekendId == 1 && dayOfWeek == DayOfWeek.Saturday) || + (weekendId == 2 && dayOfWeek == DayOfWeek.Sunday)) { return "Rest Day"; } - if ((weekendId == 1 && dayOfWeek == DayOfWeek.Friday) || // Assuming weekendId 1 means Friday is Off Day - (weekendId == 2 && dayOfWeek == DayOfWeek.Saturday)) // Assuming weekendId 2 means Saturday is Off Day + if ((weekendId == 1 && dayOfWeek == DayOfWeek.Friday) || + (weekendId == 2 && dayOfWeek == DayOfWeek.Saturday)) { return "Off Day"; } @@ -989,7 +956,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services return "Normal Day"; } - // Updated these methods to reflect the new calculation logic and accept the correct input. private decimal CalculateOrp(decimal basicSalary) { return basicSalary / 26m; @@ -1019,20 +985,18 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { if (!record.OfficeFrom.HasValue || !record.OfficeTo.HasValue) return ""; - // Convert TimeSpan to minutes from start of the day double fromMinutes = record.OfficeFrom.Value.TotalMinutes; double toMinutes = record.OfficeTo.Value.TotalMinutes; double totalMinutes = toMinutes - fromMinutes; - // Handle overnight shifts (e.g., 21:00 to 02:00) if (totalMinutes < 0) { - totalMinutes += 24 * 60; // Add 24 hours in minutes + totalMinutes += 24 * 60; } - totalMinutes -= (record.OfficeBreak ?? 0); // Subtract break - totalMinutes = Math.Max(0, totalMinutes); // Ensure total hours are not negative + totalMinutes -= (record.OfficeBreak ?? 0); + totalMinutes = Math.Max(0, totalMinutes); int hours = (int)(totalMinutes / 60); int minutes = (int)(totalMinutes % 60); @@ -1044,20 +1008,18 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { if (!record.AfterFrom.HasValue || !record.AfterTo.HasValue) return ""; - // Convert TimeSpan to minutes from start of the day double fromMinutes = record.AfterFrom.Value.TotalMinutes; double toMinutes = record.AfterTo.Value.TotalMinutes; double totalMinutes = toMinutes - fromMinutes; - // Handle overnight shifts (e.g., 21:00 to 02:00) if (totalMinutes < 0) { - totalMinutes += 24 * 60; // Add 24 hours in minutes + totalMinutes += 24 * 60; } - totalMinutes -= (record.AfterBreak ?? 0); // Subtract break - totalMinutes = Math.Max(0, totalMinutes); // Ensure total hours are not negative + totalMinutes -= (record.AfterBreak ?? 0); + totalMinutes = Math.Max(0, totalMinutes); int hours = (int)(totalMinutes / 60); int minutes = (int)(totalMinutes % 60); @@ -1073,7 +1035,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services var officeHrs = ConvertTimeToDecimal(officeRaw); var afterHrs = ConvertTimeToDecimal(afterRaw); - // Helper to format numbers. Will return "" if num is 0 or less. var toFixedOrEmpty = (decimal num) => num > 0 ? num.ToString("N2") : ""; var dayType = GetDayType(record.OtDate, weekendId, publicHolidays); @@ -1094,7 +1055,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services switch (dayType) { case "Normal Day": - result["ndAfter"] = toFixedOrEmpty(afterHrs); // Only after-office OT on normal days + result["ndAfter"] = toFixedOrEmpty(afterHrs); break; case "Off Day": @@ -1113,8 +1074,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services result["odAfter"] = toFixedOrEmpty(officeHrs); // Assign all to 'OD After' (for office hours beyond 8) } } - // Add 'afterHrs' to 'odAfter' as it's separate "After Office" time - // If 'odAfter' only represents office hours > 8, you would need a new key like 'odSeparateAfterOffice'. + result["odAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["odAfter"]) + afterHrs); break; @@ -1134,7 +1094,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services result["rdAfter"] = toFixedOrEmpty(officeHrs); // Assign all to 'RD After' (for office hours beyond 8) } } - // Add 'afterHrs' to 'rdAfter' + result["rdAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["rdAfter"]) + afterHrs); break; @@ -1150,7 +1110,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services result["phAfter"] = toFixedOrEmpty(officeHrs); // Assign all to 'PH After' (for office hours beyond 8) } } - // Add 'afterHrs' to 'phAfter' + result["phAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["phAfter"]) + afterHrs); break; } @@ -1210,16 +1170,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services private string CalculateOtAmount(OtRegisterModel record, decimal hrp, List publicHolidays, int? weekendId) { - // Now, orp and hrp are already correctly calculated based on the incoming userRate (which is now Basic Salary) - // in the ComposeTable method, so we pass hrp directly. - // However, the existing logic in this method relies on 'orp' and 'hrp' being derived - // in a specific way for the calculations. - // If the incoming 'hrp' to this method is indeed the HRP value, we need to calculate ORP from it. - // Re-calculating ORP and HRP here based on the 'hrp' received by this method to align with existing calculation logic. - decimal orpFromHrp = hrp * 8m; // This is the ORP used in calculations within this method - decimal basicSalaryFromOrp = orpFromHrp * 26m; // This is the Basic Salary derived from orpFromHrp + decimal orp = hrp * 8m; - var orp = orpFromHrp; // Use the ORP derived from the HRP passed to this function var dayType = GetDayType(record.OtDate, weekendId, publicHolidays); var officeRaw = CalculateOfficeOtHours(record); @@ -1273,22 +1225,22 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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(); + .Select(day => new DateTime(month.Year, month.Month, day)) + .ToList(); } private string GetDayCellBackgroundColor(DateTime date, int? weekendId, List publicHolidays) { if (publicHolidays.Contains(date.Date)) - return "#ffc0cb"; // Light red for Public Holiday + return "#ffc0cb"; var dayOfWeek = date.DayOfWeek; - // Assuming weekendId 1 for Friday/Saturday weekend, 2 for Saturday/Sunday + if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) || (weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday))) - return "#add8e6"; // Light blue for Weekend + return "#add8e6"; - return "#ffffff"; // White for Normal Day + return "#ffffff"; } } } \ No newline at end of file diff --git a/Areas/OTcalculate/Views/ApprovalDashboard/Approval.cshtml b/Areas/OTcalculate/Views/ApprovalDashboard/Approval.cshtml index aa26987..c0f3a04 100644 --- a/Areas/OTcalculate/Views/ApprovalDashboard/Approval.cshtml +++ b/Areas/OTcalculate/Views/ApprovalDashboard/Approval.cshtml @@ -4,7 +4,6 @@ } @@ -284,7 +278,7 @@ searchQuery: '', currentPage: 1, itemsPerPage: 10, - overallPendingMonths: [] // To store the list of "MM/YYYY" strings + overallPendingMonths: [] }; }, watch: { diff --git a/Areas/OTcalculate/Views/ApprovalDashboard/OtReview.cshtml b/Areas/OTcalculate/Views/ApprovalDashboard/OtReview.cshtml index c6d54f0..aaa4162 100644 --- a/Areas/OTcalculate/Views/ApprovalDashboard/OtReview.cshtml +++ b/Areas/OTcalculate/Views/ApprovalDashboard/OtReview.cshtml @@ -4,7 +4,6 @@ } @@ -26,7 +26,7 @@

-
Rate
+
Salary
@@ -383,7 +383,7 @@ if (!this.stateUserList.length) { this.fetchUsersState().then(() => { this.initiateStateTable(); - this.fetchApprovalFlows(); // Ensure approval flows are fetched on tab change + this.fetchApprovalFlows(); }); } else { this.initiateStateTable(); @@ -408,8 +408,8 @@ if (response.ok) { alert(successMessage); - clearCallback(); // Clears form selections - await fetchCallback(); // Fetches the updated data + clearCallback(); + await fetchCallback(); } else { const errorData = await response.json(); alert(errorData.message || "Failed to update. Please try again."); @@ -439,7 +439,6 @@ this.userList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin"); console.log("Fetched User data", this.userList); - // Reinitialize DataTable with updated data this.initiateTable(); } catch (error) { @@ -464,7 +463,6 @@ this.stateUserList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin"); console.log("Fetched state users", this.stateUserList); - // Reinitialize the state table to reflect updated data this.initiateStateTable(); } catch (error) { @@ -569,8 +567,8 @@ this.selectedUsersState, this.selectedStateAll, "State updated successfully!", - () => {}, // Don't clear yet - () => {}, // Don't fetch yet + () => {}, + () => {}, "StateId" ); } @@ -602,7 +600,6 @@ } } - // After updates, refresh and clear await this.fetchUsersState(); this.initiateStateTable(); this.clearAllSelectionsStateFlow(); @@ -643,8 +640,8 @@ try { const response = await fetch('/OvertimeAPI/GetApprovalFlowList'); const data = await response.json(); - this.approvalFlowList = data; // Update the data correctly - console.log('Fetched approval flows:', this.approvalFlowList); // Verify data + this.approvalFlowList = data; + console.log('Fetched approval flows:', this.approvalFlowList); } catch (error) { console.error('Error fetching approval flows:', error); } @@ -690,7 +687,6 @@ async deleteApprovalFlow(approvalId) { if (!approvalId) { - // This handles the "undefined" ID case more gracefully console.error("No approval ID provided for deletion."); alert("An error occurred: No approval flow selected for deletion."); return; @@ -707,22 +703,17 @@ alert("Approval flow deleted successfully."); await this.fetchApprovalFlows(); } else { - // Parse the error response from the server const errorData = await response.json(); const errorMessage = errorData.message || "Failed to delete approval flow."; - // Display the alert, but DON'T re-throw or console.error if it's a specific bad request - // Only log to console for unexpected server errors (e.g., 500 status codes) - if (response.status === 400) { // Check for Bad Request specifically + if (response.status === 400) { alert(`Error: ${errorMessage}`); } else { - // For other errors (e.g., 500 Internal Server Error), still log to console console.error("Error deleting flow:", errorMessage); alert(`Error: ${errorMessage}`); } } } catch (error) { - // This catch block handles network errors or errors that prevent a valid response console.error("An unexpected error occurred during deletion:", error); alert(`An unexpected error occurred: ${error.message || "Please try again."}`); } @@ -749,13 +740,10 @@ } }, openEditModal(flow) { - // Set the editFlow to the selected flow this.editFlow = { ...flow }; - // Optionally, log the data to ensure it's correct console.log(this.editFlow); - // Show the modal this.fetchAllUsers().then(() => { const modal = new bootstrap.Modal(document.getElementById('editApprovalModal')); modal.show(); diff --git a/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml b/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml index f86d598..61f8a87 100644 --- a/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml +++ b/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml @@ -53,7 +53,7 @@

UPDATE SALARY

- @* Enter Rate *@ + @* Enter Salary *@
diff --git a/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml b/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml index 42a8b0b..b40def3 100644 --- a/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml +++ b/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml @@ -142,7 +142,7 @@ }, mounted() { this.fetchUpdateDates(); - this.checkIncompleteSettings(); // Call the new method on mount + this.checkIncompleteSettings(); }, methods: { async fetchUpdateDates() { @@ -172,7 +172,6 @@ const data = await response.json(); if (data.hasIncompleteSettings) { - // Access the new property 'numberOfIncompleteUsers' const numberOfStaff = data.numberOfIncompleteUsers; let alertMessage = `Action Required!\n\nThere are ${numberOfStaff} staff with incomplete Rate / Flexi Hour / Approval Flow / State.`; alert(alertMessage); diff --git a/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml b/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml index f17a8b8..e72c3be 100644 --- a/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml +++ b/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml @@ -170,8 +170,8 @@ return { label, value: totalMinutes }; }), previousPage: document.referrer, - returnMonth: null, // Add this - returnYear: null, // Add this + returnMonth: null, + returnYear: null, validationErrors: { otDate: "", stationId: "", @@ -192,7 +192,7 @@ async mounted() { const urlParams = new URLSearchParams(window.location.search); const overtimeId = urlParams.get('overtimeId'); - // Capture month and year from URL parameters + this.returnMonth = urlParams.get('month'); this.returnYear = urlParams.get('year'); @@ -225,7 +225,7 @@ }, methods: { initializeSelect2Dropdowns() { - // Destroy any existing Select2 instances to prevent issues on re-initialization + if ($('#airstationDropdown').data('select2')) { $('#airstationDropdown').select2('destroy'); } @@ -396,9 +396,9 @@ this.totalBreakHours = `${totalBreakHours} hr ${totalBreakMinutes} min`; this.updateDayType(); }, - // Update the updateTime method to include midnight validation + updateTime(fieldName) { - // Round time first + if (fieldName === 'officeFrom') { this.editForm.officeFrom = this.roundToNearest30(this.editForm.officeFrom); } else if (fieldName === 'officeTo') { @@ -409,12 +409,10 @@ this.editForm.afterTo = this.roundToNearest30(this.editForm.afterTo); } - // Validate time ranges this.validateTimeRanges(); this.calculateOTAndBreak(); }, - // Add new validation method validateTimeRanges() { const validateRange = (fromTime, toTime, label) => { if (!fromTime || !toTime) return true; @@ -422,12 +420,12 @@ const start = this.parseTime(fromTime); const end = this.parseTime(toTime); - const minAllowedFromMinutes = 16 * 60 + 30; // 4:30 PM - const maxAllowedFromMinutes = 23 * 60 + 30; // 11:30 PM + const minAllowedFromMinutes = 16 * 60 + 30; + const maxAllowedFromMinutes = 23 * 60 + 30; const startMinutes = start.hours * 60 + start.minutes; const endMinutes = end.hours * 60 + end.minutes; - if (endMinutes === 0) { // If 'To' is 00:00 (midnight) + if (endMinutes === 0) { if (fromTime === "00:00") { alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`); return false; @@ -443,14 +441,13 @@ return true; }; - // Validate office hours + if (this.editForm.officeFrom && this.editForm.officeTo) { if (!validateRange(this.editForm.officeFrom, this.editForm.officeTo, 'Office Hour')) { this.editForm.officeTo = ''; } } - // Validate after hours if (this.editForm.afterFrom && this.editForm.afterTo) { if (!validateRange(this.editForm.afterFrom, this.editForm.afterTo, 'After Office Hour')) { this.editForm.afterTo = ''; @@ -458,23 +455,20 @@ } }, - // Update the roundToNearest30 method to match OtRegister roundToNearest30(timeStr) { if (!timeStr) return timeStr; const [hours, minutes] = timeStr.split(':').map(Number); const totalMinutes = hours * 60 + minutes; const remainder = totalMinutes % 30; - // If closer to the lower 30-min mark, round down. Otherwise, round up. const roundedMinutes = remainder < 15 ? totalMinutes - remainder : totalMinutes + (30 - remainder); - const adjustedHour = Math.floor(roundedMinutes / 60) % 24; // Ensure hours wrap around 24 + const adjustedHour = Math.floor(roundedMinutes / 60) % 24; const adjustedMinute = roundedMinutes % 60; return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`; }, - // Update calculateTimeDifference to handle midnight case calculateTimeDifference(startTime, endTime, breakMinutes) { if (!startTime || !endTime) { return { hours: 0, minutes: 0 }; @@ -485,19 +479,19 @@ let diffMinutes; - // If TO is 00:00 (midnight), calculate the duration until end of day (24:00) if (end.hours === 0 && end.minutes === 0 && (start.hours > 0 || start.minutes > 0)) { diffMinutes = (24 * 60) - (start.hours * 60 + start.minutes); } else if (end.hours * 60 + end.minutes <= start.hours * 60 + start.minutes) { - // For all other cases where 'To' time is on or before 'From' time, it's invalid. + return { hours: 0, minutes: 0 }; + } else { - // Standard calculation for times within the same 24-hour period on the same day. + diffMinutes = (end.hours * 60 + end.minutes) - (start.hours * 60 + start.minutes); } diffMinutes -= breakMinutes || 0; - if (diffMinutes < 0) diffMinutes = 0; // Ensure total hours don't go negative if break is too long + if (diffMinutes < 0) diffMinutes = 0; const hours = Math.floor(diffMinutes / 60); const minutes = diffMinutes % 60; @@ -558,7 +552,7 @@ }, validateForm() { - // Reset validation errors + this.validationErrors = { otDate: "", stationId: "", @@ -569,7 +563,7 @@ let isValid = true; let errorMessages = []; - // Validate date + if (!this.editForm.otDate) { this.validationErrors.otDate = "Date is required."; errorMessages.push("Date is required."); @@ -648,7 +642,6 @@ } } - // Display alert if not valid if (!isValid) { alert("Please correct the following issues:\n\n" + errorMessages.join("\n")); } @@ -709,11 +702,9 @@ }, goBack() { - // If we have stored month and year, use them to return to the specific view if (this.returnMonth && this.returnYear) { window.location.href = `/OTcalculate/Overtime/OtRecords?month=${this.returnMonth}&year=${this.returnYear}`; } else { - // Fallback to previous page if month/year are not available window.location.href = this.previousPage; } }, diff --git a/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml b/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml index 2b9bb7f..414684d 100644 --- a/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml +++ b/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml @@ -212,8 +212,8 @@ otRecords: [], userId: null, isPSTWAIR: false, - selectedMonth: initialMonth, // Use initialMonth - selectedYear: initialYear, // Use initialYear + selectedMonth: initialMonth, + selectedYear: initialYear, months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i), expandedDescriptions: {}, @@ -238,7 +238,6 @@ .sort((a, b) => new Date(a.otDate) - new Date(b.otDate)); }, noRecordsFound() { - // This new computed property checks if filteredRecords is empty return this.filteredRecords.length === 0; }, totalHours() { @@ -291,7 +290,6 @@ const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin"); const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin"); const isDepartmentTwo = this.currentUser?.department?.departmentId === 2; - // ADD THIS LINE TO INCLUDE DEPARTMENT ID 3 (PSTWMARINE) const isDepartmentThree = this.currentUser?.department?.departmentId === 3; this.isPSTWAIR = isSuperAdmin || isSystemAdmin || isDepartmentTwo || isDepartmentThree; @@ -320,14 +318,14 @@ const errorText = await res.text(); console.error("Failed to check submission status:", errorText); alert("Error checking submission status. Please try again."); - this.hasSubmitted = false; // Or handle as appropriate + this.hasSubmitted = false; return; } this.hasSubmitted = await res.json(); } catch (err) { console.error("Failed to parse submission status:", err); alert("An unexpected error occurred while checking submission status."); - this.hasSubmitted = false; // Or handle as appropriate + this.hasSubmitted = false; } }, toggleDescription(index) { @@ -357,13 +355,11 @@ let totalMinutes = (th * 60 + tm) - (fh * 60 + fm); - // If the 'to' time is earlier than the 'from' time, it means it's an overnight span. - // Add 24 hours (1440 minutes) to account for the next day. if (totalMinutes < 0) { - totalMinutes += 24 * 60; // Add 24 hours in minutes + totalMinutes += 24 * 60; } - return totalMinutes / 60; // Return total hours as a decimal + return totalMinutes / 60; }, calcTotalTime(r) { const totalMinutes = this.calcTotalHours(r) * 60; @@ -393,7 +389,6 @@ }, editRecord(index) { const record = this.filteredRecords[index]; - // Pass current month and year along with overtimeId window.location.href = `/OTcalculate/Overtime/EditOvertime?overtimeId=${record.overtimeId}&month=${this.selectedMonth}&year=${this.selectedYear}`; }, async deleteRecord(index) { @@ -443,7 +438,6 @@ } }, downloadExcel(month, year) { - // Use the new API endpoint window.open(`/OvertimeAPI/GenerateUserOvertimeExcel/${this.userId}/${month}/${year}`, '_blank'); }, openSubmitModal() { @@ -478,7 +472,7 @@ const modalInstance = bootstrap.Modal.getInstance(modalEl); modalInstance.hide(); - await this.getSubmissionStatus(); // Call this to refresh the hasSubmitted status + await this.getSubmissionStatus(); } else { alert('Submission failed.'); diff --git a/Areas/OTcalculate/Views/Overtime/OtRegister.cshtml b/Areas/OTcalculate/Views/Overtime/OtRegister.cshtml index c013deb..713acbc 100644 --- a/Areas/OTcalculate/Views/Overtime/OtRegister.cshtml +++ b/Areas/OTcalculate/Views/Overtime/OtRegister.cshtml @@ -148,7 +148,6 @@ afterFrom: "", afterTo: "", afterBreak: 0, - // New properties for separate station selections selectedAirStation: "", // Holds selected Air station ID selectedMarineStation: "", // Holds selected Marine station ID airStationList: [], // Stores stations for Air Department (DepartmentId = 2) @@ -163,10 +162,9 @@ userId: null, userState: null, publicHolidays: [], - // Keep user's actual department ID and admin flag for conditional rendering userDepartmentId: null, // The department ID from the current user's profile - isUserAdmin: false, // True if the user is a SuperAdmin or SystemAdmin - departmentName: "", // This will be dynamic based on the user's main department for hints + isUserAdmin: false, + departmentName: "", areUserSettingsComplete: false, breakOptions: Array.from({ length: 15 }, (_, i) => { const totalMinutes = i * 30; @@ -186,17 +184,12 @@ charCount() { return this.otDescription.length; }, - // Determines if the Air Station dropdown should be shown showAirDropdown() { return this.isUserAdmin || this.userDepartmentId === 2; }, - // Determines if the Marine Station dropdown should be shown showMarineDropdown() { return this.isUserAdmin || this.userDepartmentId === 3; }, - // This computed property selects the station ID to be sent to the backend. - // It assumes that if both are visible (for admins), only ONE should be selected. - // If both are selected by an admin, validation in addOvertime will catch it. stationIdForSubmission() { if (this.selectedAirStation) { return parseInt(this.selectedAirStation); @@ -204,15 +197,13 @@ if (this.selectedMarineStation) { return parseInt(this.selectedMarineStation); } - return null; // No station selected from either dropdown + return null; }, - // This indicates if *any* station selection is required based on visible dropdowns requiresStation() { return this.showAirDropdown || this.showMarineDropdown; } }, watch: { - // Watch for changes in airStationList to re-initialize Select2 for Air airStationList: { handler() { this.$nextTick(() => { @@ -222,11 +213,9 @@ selectElement.select2('destroy'); } selectElement.select2({ theme: 'bootstrap4', placeholder: 'Select Air Station', allowClear: true }); - // Set initial value if already selected if (this.selectedAirStation) { selectElement.val(this.selectedAirStation).trigger('change'); } - // Ensure event listener is set only once selectElement.off('change.v-model-air').on('change.v-model-air', (event) => { this.selectedAirStation = $(event.currentTarget).val(); }); @@ -235,7 +224,6 @@ }, deep: true }, - // Watch for changes in marineStationList to re-initialize Select2 for Marine marineStationList: { handler() { this.$nextTick(() => { @@ -245,11 +233,9 @@ selectElement.select2('destroy'); } selectElement.select2({ theme: 'bootstrap4', placeholder: 'Select Marine Station', allowClear: true }); - // Set initial value if already selected if (this.selectedMarineStation) { selectElement.val(this.selectedMarineStation).trigger('change'); } - // Ensure event listener is set only once selectElement.off('change.v-model-marine').on('change.v-model-marine', (event) => { this.selectedMarineStation = $(event.currentTarget).val(); }); @@ -258,14 +244,12 @@ }, deep: true }, - // Keep selectedAirStation in sync with Select2 if it's already rendered selectedAirStation(newVal) { const selectElement = $('#airStationDropdown'); if (selectElement.length && selectElement.val() !== newVal) { selectElement.val(newVal).trigger('change.select2'); } }, - // Keep selectedMarineStation in sync with Select2 if it's already rendered selectedMarineStation(newVal) { const selectElement = $('#marineStationDropdown'); if (selectElement.length && selectElement.val() !== newVal) { @@ -278,18 +262,14 @@ if (this.userId) { await this.checkUserSettings(); } - - // Fetch stations for Air if the dropdown will be visible if (this.showAirDropdown) { await this.fetchStations(2, 'air'); // Department ID 2 for Air } - // Fetch stations for Marine if the dropdown will be visible if (this.showMarineDropdown) { await this.fetchStations(3, 'marine'); // Department ID 3 for Marine } }, methods: { - // Modified fetchStations to populate specific lists based on listType async fetchStations(departmentId, listType) { try { const response = await fetch(`/OvertimeAPI/GetStationsByDepartment?departmentId=${departmentId}`); @@ -319,11 +299,10 @@ const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin"); const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin"); - this.userDepartmentId = this.currentUser?.department?.departmentId; // Store user's actual department ID + this.userDepartmentId = this.currentUser?.department?.departmentId; - this.isUserAdmin = isSuperAdmin || isSystemAdmin; // Set the admin flag + this.isUserAdmin = isSuperAdmin || isSystemAdmin; - // Set departmentName for the generic "Only for {{ departmentName }}" hint if not admin if (!this.isUserAdmin) { if (this.userDepartmentId === 2) { this.departmentName = "PSTW AIR"; @@ -333,7 +312,7 @@ this.departmentName = ""; } } else { - this.departmentName = ""; // Admins see both, so this specific hint is removed for them + this.departmentName = ""; } console.log("Fetched User:", this.currentUser); @@ -394,10 +373,9 @@ const totalMinutes = hours * 60 + minutes; const remainder = totalMinutes % 30; - // If closer to the lower 30-min mark, round down. Otherwise, round up. const roundedMinutes = remainder < 15 ? totalMinutes - remainder : totalMinutes + (30 - remainder); - const adjustedHour = Math.floor(roundedMinutes / 60) % 24; // Ensure hours wrap around 24 + const adjustedHour = Math.floor(roundedMinutes / 60) % 24; const adjustedMinute = roundedMinutes % 60; return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`; @@ -446,7 +424,7 @@ } diffMinutes -= breakMinutes || 0; - if (diffMinutes < 0) diffMinutes = 0; // Ensure total hours don't go negative if break is too long + if (diffMinutes < 0) diffMinutes = 0; const hours = Math.floor(diffMinutes / 60); const minutes = diffMinutes % 60; @@ -472,7 +450,6 @@ return; } - // --- Frontend Validation --- if (!this.selectedDate) { alert("Please select a date for the overtime."); return; @@ -510,7 +487,7 @@ // Validate time ranges according to the new rule: TO 00:00 only if FROM is 4:30 PM - 11:30 PM const validateTimeRangeForSubmission = (fromTime, toTime, label) => { - if (!fromTime || !toTime) return true; // Handled by outer checks + if (!fromTime || !toTime) return true; const start = this.parseTime(fromTime); const end = this.parseTime(toTime); @@ -542,8 +519,6 @@ if (hasAfterHours && !validateTimeRangeForSubmission(this.afterFrom, this.afterTo, 'After Office Hour')) { return; } - // --- End Frontend Validation --- - const requestData = { otDate: this.selectedDate, @@ -553,7 +528,7 @@ afterFrom: this.afterFrom ? this.formatTime(this.afterFrom) : null, afterTo: this.afterTo ? this.formatTime(this.afterTo) : null, afterBreak: this.afterBreak || null, - stationId: stationIdToSubmit, // Use the selected station from either dropdown + stationId: stationIdToSubmit, otDescription: this.otDescription.trim().split(/\s+/).slice(0, 50).join(' '), otDays: this.detectedDayType, userId: this.userId, @@ -635,10 +610,9 @@ this.afterFrom = ""; this.afterTo = ""; this.afterBreak = 0; - this.selectedAirStation = ""; // Clear specific station selections - this.selectedMarineStation = ""; // Clear specific station selections + this.selectedAirStation = ""; + this.selectedMarineStation = ""; - // Clear Select2 for both dropdowns if they exist const airSelect = $('#airStationDropdown'); if (airSelect.length && airSelect.data('select2')) { airSelect.val('').trigger('change.select2'); diff --git a/Areas/OTcalculate/Views/Overtime/OtStatus.cshtml b/Areas/OTcalculate/Views/Overtime/OtStatus.cshtml index f589485..0b86a04 100644 --- a/Areas/OTcalculate/Views/Overtime/OtStatus.cshtml +++ b/Areas/OTcalculate/Views/Overtime/OtStatus.cshtml @@ -112,6 +112,7 @@
+
@@ -344,12 +345,12 @@ computed: { columnCount() { - let count = 3; // Month/Year, SubmitDate, Edit History + let count = 3; if (this.includeHou) count++; if (this.includeHod) count++; if (this.includeManager) count++; if (this.includeHr) count++; - return count + 1; // File column + return count + 1; }, monthYearOptions() { @@ -374,7 +375,6 @@ { value: 'Rejected', text: 'Rejected' } ]; - // Add combined status options if multiple approval levels exist if ((this.includeHou && this.includeHod) || (this.includeHou && this.includeManager) || (this.includeHod && this.includeManager)) { @@ -390,7 +390,6 @@ filteredRecords() { let filtered = [...this.otRecords]; - // Apply filters if (this.filters.monthYear) { const [month, year] = this.filters.monthYear.split('/').map(Number); filtered = filtered.filter(item => @@ -434,7 +433,6 @@ } } - // Apply sorting filtered.sort((a, b) => { let aValue, bValue; @@ -472,7 +470,6 @@ return 0; }); - // Update pagination this.pagination.totalPages = Math.ceil(filtered.length / this.pagination.itemsPerPage); this.pagination.currentPage = Math.min(this.pagination.currentPage, this.pagination.totalPages || 1); @@ -727,7 +724,6 @@ this.parsedHistory = []; }, - // Sorting methods sortBy(field) { if (this.sort.field === field) { this.sort.order = this.sort.order === 'asc' ? 'desc' : 'asc'; @@ -735,10 +731,9 @@ this.sort.field = field; this.sort.order = 'asc'; } - this.pagination.currentPage = 1; // Reset to first page when sorting changes + this.pagination.currentPage = 1; }, - // Filter methods resetFilters() { this.filters = { monthYear: '', @@ -747,7 +742,6 @@ this.pagination.currentPage = 1; }, - // Pagination methods prevPage() { if (this.pagination.currentPage > 1) { this.pagination.currentPage--; diff --git a/Controllers/API/OvertimeAPI.cs b/Controllers/API/OvertimeAPI.cs index b40d6a7..70340b6 100644 --- a/Controllers/API/OvertimeAPI.cs +++ b/Controllers/API/OvertimeAPI.cs @@ -85,29 +85,24 @@ namespace PSTW_CentralSystem.Controllers.API { try { - // Use a list to hold IDs of users with incomplete settings var incompleteUserIds = new List(); - // Get all user IDs var allUserIds = await _userManager.Users.Select(u => u.Id).ToListAsync(); foreach (var userId in allUserIds) { bool isIncomplete = false; - // Check HrUserSettingModel var hrUserSetting = await _centralDbContext.Hrusersetting .Where(h => h.UserId == userId) .FirstOrDefaultAsync(); if (hrUserSetting == null) { - // No HR user setting found for this user isIncomplete = true; } else { - // Check for null/empty fields in HrUserSettingModel if (hrUserSetting.FlexiHourId == null || hrUserSetting.FlexiHourId == 0) { isIncomplete = true; @@ -122,19 +117,16 @@ namespace PSTW_CentralSystem.Controllers.API } } - // Check RateModel var rateSetting = await _centralDbContext.Rates .Where(r => r.UserId == userId) .FirstOrDefaultAsync(); if (rateSetting == null) { - // No Rate setting found for this user isIncomplete = true; } else { - // Check for default/empty RateValue (assuming 0.00 is considered empty) if (rateSetting.RateValue == 0.00m) { isIncomplete = true; @@ -143,13 +135,12 @@ namespace PSTW_CentralSystem.Controllers.API if (isIncomplete) { - incompleteUserIds.Add(userId); // Add only the ID + incompleteUserIds.Add(userId); } } if (incompleteUserIds.Any()) { - // Return just the count of incomplete users return Ok(new { hasIncompleteSettings = true, numberOfIncompleteUsers = incompleteUserIds.Count }); } else @@ -330,8 +321,7 @@ namespace PSTW_CentralSystem.Controllers.API .FirstOrDefault() ?? "N/A" }).ToList(); - // Log this data to inspect the response - Console.WriteLine(JsonConvert.SerializeObject(result)); // Debugging log + Console.WriteLine(JsonConvert.SerializeObject(result)); return Ok(result); @@ -418,7 +408,7 @@ namespace PSTW_CentralSystem.Controllers.API if (existingSetting != null) { existingSetting.StateId = update.StateId; - existingSetting.StateUpdate = DateTime.Now; // <-- ADD THIS LINE + existingSetting.StateUpdate = DateTime.Now; } else { @@ -426,8 +416,7 @@ namespace PSTW_CentralSystem.Controllers.API { UserId = update.UserId, StateId = update.StateId, - StateUpdate = DateTime.Now, // <-- ADD THIS LINE for new records - // Consider setting default for FlexiHourId and ApprovalFlowId if they are not nullable or have defaults + StateUpdate = DateTime.Now, }); } } @@ -580,7 +569,6 @@ namespace PSTW_CentralSystem.Controllers.API return NotFound("Approval flow not found."); } - // Update fields existingFlow.ApprovalName = model.ApprovalName; existingFlow.HoU = model.HoU; existingFlow.HoD = model.HoD; @@ -594,7 +582,6 @@ namespace PSTW_CentralSystem.Controllers.API } catch (Exception ex) { - // Log the exception return StatusCode(500, new { message = "Failed to update approval flow.", detail = ex.Message }); } } @@ -610,7 +597,6 @@ namespace PSTW_CentralSystem.Controllers.API return NotFound(new { message = "Approval flow not found." }); } - // Check if any users are using this approval flow var usersWithThisFlow = await _centralDbContext.Hrusersetting .AnyAsync(u => u.ApprovalFlowId == id); @@ -802,13 +788,11 @@ namespace PSTW_CentralSystem.Controllers.API if (existingState != null) { - // Corrected: Updating WeekendId existingState.WeekendId = state.WeekendId; _centralDbContext.States.Update(existingState); } else { - // Ensure new states are added correctly _centralDbContext.States.Add(new StateModel { StateId = state.StateId, @@ -874,15 +858,13 @@ namespace PSTW_CentralSystem.Controllers.API #endregion #region OtRegister - - // Modified to accept departmentId as a query parameter [HttpGet("GetStationsByDepartment")] public async Task GetStationsByDepartment([FromQuery] int? departmentId) { if (!departmentId.HasValue) { _logger.LogWarning("GetStationsByDepartment called without a departmentId."); - return Ok(new List()); // Return empty list if no department is specified + return Ok(new List()); } var stations = await _centralDbContext.Stations @@ -918,7 +900,6 @@ namespace PSTW_CentralSystem.Controllers.API return BadRequest("User ID is required."); } - // **Backend Validation for StationId based on user roles and department** var user = await _userManager.FindByIdAsync(request.UserId.ToString()); if (user == null) { @@ -930,22 +911,20 @@ namespace PSTW_CentralSystem.Controllers.API var isSuperAdmin = userRoles.Contains("SuperAdmin"); var isSystemAdmin = userRoles.Contains("SystemAdmin"); - // Get user's department info from the database (assuming it's eager loaded or can be fetched) var userWithDepartment = await _centralDbContext.Users .Include(u => u.Department) .FirstOrDefaultAsync(u => u.Id == request.UserId); int? userDepartmentId = userWithDepartment?.Department?.DepartmentId; - // Determine if a station is required and validate it bool stationRequired = false; - if (userDepartmentId == 2 || userDepartmentId == 3) // For regular Air/Marine users + if (userDepartmentId == 2 || userDepartmentId == 3) { stationRequired = true; } - else if (isSuperAdmin || isSystemAdmin) // For Admins, they see both, and must select one + else if (isSuperAdmin || isSystemAdmin) { - stationRequired = true; // Admins also must select a station if they submit overtime + stationRequired = true; } if (stationRequired && (!request.StationId.HasValue || request.StationId.Value <= 0)) @@ -958,7 +937,6 @@ namespace PSTW_CentralSystem.Controllers.API TimeSpan? afterFrom = string.IsNullOrEmpty(request.AfterFrom) ? (TimeSpan?)null : TimeSpan.Parse(request.AfterFrom); TimeSpan? afterTo = string.IsNullOrEmpty(request.AfterTo) ? (TimeSpan?)null : TimeSpan.Parse(request.AfterTo); - // Validation for time ranges (consolidated) if ((officeFrom != null && officeTo == null) || (officeFrom == null && officeTo != null)) { return BadRequest("Both Office From and To times must be provided if one is entered."); @@ -968,47 +946,44 @@ namespace PSTW_CentralSystem.Controllers.API return BadRequest("Both After Office From and To times must be provided if one is entered."); } - // Define allowed range for FROM when TO is 00:00 (midnight) - TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); // 4:30 PM - TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); // 11:30 PM + TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); + TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); - // Backend Validation for Office Hours if (officeFrom.HasValue && officeTo.HasValue) { - if (officeTo == TimeSpan.Zero) // If OfficeTo is exactly midnight (00:00:00) + if (officeTo == TimeSpan.Zero) { if (officeFrom == TimeSpan.Zero) { return BadRequest("Invalid Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."); } - // Check if OfficeFrom is within the specified range (4:30 PM to 11:30 PM) + if (officeFrom.Value < minAllowedFromMidnightTo || officeFrom.Value > maxAllowedFromMidnightTo) { return BadRequest("Invalid Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."); } } - else if (officeTo <= officeFrom) // For all other cases, "To" must be strictly greater than "From" + else if (officeTo <= officeFrom) { return BadRequest("Invalid Office Hour Time: 'To' time must be later than 'From' time (same day only)."); } } - // Backend Validation for After Office Hours if (afterFrom.HasValue && afterTo.HasValue) { - if (afterTo == TimeSpan.Zero) // If AfterTo is exactly midnight (00:00:00) + if (afterTo == TimeSpan.Zero) { if (afterFrom == TimeSpan.Zero) { return BadRequest("Invalid After Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."); } - // Check if AfterFrom is within the specified range (4:30 PM to 11:30 PM) + if (afterFrom.Value < minAllowedFromMidnightTo || afterFrom.Value > maxAllowedFromMidnightTo) { return BadRequest("Invalid After Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."); } } - else if (afterTo <= afterFrom) // For all other cases, "To" must be strictly greater than "From" + else if (afterTo <= afterFrom) { return BadRequest("Invalid After Office Hour Time: 'To' time must be later than 'From' time (same day only)."); } @@ -1024,10 +999,10 @@ namespace PSTW_CentralSystem.Controllers.API OtDate = request.OtDate, OfficeFrom = officeFrom, OfficeTo = officeTo, - OfficeBreak = request.OfficeBreak, // Assuming it's nullable or default to 0 + OfficeBreak = request.OfficeBreak, AfterFrom = afterFrom, AfterTo = afterTo, - AfterBreak = request.AfterBreak, // Assuming it's nullable or default to 0 + AfterBreak = request.AfterBreak, StationId = request.StationId, OtDescription = request.OtDescription, OtDays = request.OtDays, @@ -1142,7 +1117,7 @@ namespace PSTW_CentralSystem.Controllers.API try { var records = _centralDbContext.Otregisters - .Include(o => o.Stations) // <--- ADD THIS LINE + .Include(o => o.Stations) .Where(o => o.UserId == userId) .OrderByDescending(o => o.OtDate) .Select(o => new @@ -1233,7 +1208,6 @@ namespace PSTW_CentralSystem.Controllers.API var relativePath = Path.Combine("Media", "Overtime", uniqueFileName).Replace("\\", "/"); - // Create a NEW OtStatusModel for the resubmission var statusModel = new OtStatusModel { UserId = userId, @@ -1248,9 +1222,8 @@ namespace PSTW_CentralSystem.Controllers.API }; _centralDbContext.Otstatus.Add(statusModel); - await _centralDbContext.SaveChangesAsync(); // Save the new OtStatus record to get its StatusId + await _centralDbContext.SaveChangesAsync(); - // Update StatusId in OtRegister records for the current month/year var monthStart = new DateTime(model.Year, model.Month, 1); var monthEnd = monthStart.AddMonths(1); @@ -1266,7 +1239,6 @@ namespace PSTW_CentralSystem.Controllers.API _centralDbContext.Otregisters.UpdateRange(otRecords); await _centralDbContext.SaveChangesAsync(); - // Update HrUserSetting with the NEW StatusId var userSetting = _centralDbContext.Hrusersetting.FirstOrDefault(s => s.UserId == userId); if (userSetting != null) { @@ -1290,7 +1262,6 @@ namespace PSTW_CentralSystem.Controllers.API { try { - // Get the latest OtStatus record for the user, month, and year var latestStatus = _centralDbContext.Otstatus .Where(s => s.UserId == userId && s.Month == month && s.Year == year) .OrderByDescending(s => s.SubmitDate) @@ -1298,29 +1269,26 @@ namespace PSTW_CentralSystem.Controllers.API if (latestStatus == null) { - return Ok(false); // Not submitted yet + return Ok(false); } - // Check if the latest submission has been rejected at any level if (latestStatus.HouStatus?.ToLower() == "rejected" || latestStatus.HodStatus?.ToLower() == "rejected" || latestStatus.ManagerStatus?.ToLower() == "rejected" || latestStatus.HrStatus?.ToLower() == "rejected") { - return Ok(false); // Latest submission was rejected, enable submit + return Ok(false); } - // If not rejected, check if it's in a pending state (new submission) if (latestStatus.HouStatus?.ToLower() == "pending" || latestStatus.HodStatus?.ToLower() == "pending" || latestStatus.ManagerStatus?.ToLower() == "pending" || latestStatus.HrStatus?.ToLower() == "pending") { - return Ok(true); // Newly submitted or resubmitted, disable submit + return Ok(true); } - // If not pending and not rejected, it implies it's fully approved or in a final rejected state - return Ok(true); // Disable submit + return Ok(true); } catch (Exception ex) { @@ -1376,14 +1344,13 @@ namespace PSTW_CentralSystem.Controllers.API return Ok(record); } - // New API Endpoint to Get User's Flexi Hour [HttpGet("GetUserFlexiHour/{userId}")] public async Task GetUserFlexiHour(int userId) { try { var userSetting = await _centralDbContext.Hrusersetting - .Include(hs => hs.FlexiHour) // Include the FlexiHour navigation property + .Include(hs => hs.FlexiHour) .FirstOrDefaultAsync(hs => hs.UserId == userId); if (userSetting == null || userSetting.FlexiHour == null) @@ -1392,7 +1359,6 @@ namespace PSTW_CentralSystem.Controllers.API return NotFound(new { message = "Flexi hour not found for this user." }); } - // Return the FlexiHourModel object return Ok(new { flexiHour = userSetting.FlexiHour }); } catch (Exception ex) @@ -1401,6 +1367,7 @@ namespace PSTW_CentralSystem.Controllers.API return StatusCode(500, new { message = "An error occurred while fetching flexi hour." }); } } + [HttpPost] [Route("UpdateOvertimeRecord")] public IActionResult UpdateOvertimeRecord([FromBody] OtRegisterUpdateDto model) @@ -1414,7 +1381,6 @@ namespace PSTW_CentralSystem.Controllers.API return BadRequest(ModelState); } - // Validate time ranges var timeValidationError = ValidateTimeRanges(model); if (timeValidationError != null) { @@ -1427,7 +1393,6 @@ namespace PSTW_CentralSystem.Controllers.API return NotFound(new { message = "Overtime record not found." }); } - // Update properties existing.OtDate = model.OtDate; existing.OfficeFrom = TimeSpan.TryParse(model.OfficeFrom, out var officeFrom) ? officeFrom : null; existing.OfficeTo = TimeSpan.TryParse(model.OfficeTo, out var officeTo) ? officeTo : null; @@ -1457,47 +1422,44 @@ namespace PSTW_CentralSystem.Controllers.API TimeSpan? afterFrom = string.IsNullOrEmpty(model.AfterFrom) ? (TimeSpan?)null : TimeSpan.Parse(model.AfterFrom); TimeSpan? afterTo = string.IsNullOrEmpty(model.AfterTo) ? (TimeSpan?)null : TimeSpan.Parse(model.AfterTo); - // Define allowed range for FROM when TO is 00:00 (midnight) - TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); // 4:30 PM - TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); // 11:30 PM + TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); + TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); - // Validate office hours if (officeFrom.HasValue && officeTo.HasValue) { - if (officeTo == TimeSpan.Zero) // If OfficeTo is exactly midnight (00:00:00) + if (officeTo == TimeSpan.Zero) { if (officeFrom == TimeSpan.Zero) { return "Invalid Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."; } - // Check if OfficeFrom is within the specified range (4:30 PM to 11:30 PM) + if (officeFrom.Value < minAllowedFromMidnightTo || officeFrom.Value > maxAllowedFromMidnightTo) { return "Invalid Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."; } } - else if (officeTo <= officeFrom) // For all other cases, "To" must be strictly greater than "From" + else if (officeTo <= officeFrom) { return "Invalid Office Hour Time: 'To' time must be later than 'From' time (same day only)."; } } - // Validate after hours if (afterFrom.HasValue && afterTo.HasValue) { - if (afterTo == TimeSpan.Zero) // If AfterTo is exactly midnight (00:00:00) + if (afterTo == TimeSpan.Zero) { if (afterFrom == TimeSpan.Zero) { return "Invalid After Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."; } - // Check if AfterFrom is within the specified range (4:30 PM to 11:30 PM) + if (afterFrom.Value < minAllowedFromMidnightTo || afterFrom.Value > maxAllowedFromMidnightTo) { return "Invalid After Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."; } } - else if (afterTo <= afterFrom) // For all other cases, "To" must be strictly greater than "From" + else if (afterTo <= afterFrom) { return "Invalid After Office Hour Time: 'To' time must be later than 'From' time (same day only)."; } @@ -1529,7 +1491,6 @@ namespace PSTW_CentralSystem.Controllers.API .Select(x => x.ApprovalFlowId) .FirstOrDefault(); - // Default values when no approval flow exists bool includeHou = false; bool includeHod = false; bool includeManager = false; @@ -1594,7 +1555,6 @@ namespace PSTW_CentralSystem.Controllers.API .ToList(); if (!flows.Any()) - // Ensure OverallPendingMonths is always returned, even if empty return Json(new { Roles = new List(), Data = new List(), OverallPendingMonths = new List() }); var flowRoleMap = new Dictionary(); @@ -1606,8 +1566,6 @@ namespace PSTW_CentralSystem.Controllers.API if (flow.HR == currentUserId) flowRoleMap[flow.ApprovalFlowId] = "HR"; } - // --- Modified: Load ALL relevant OT entries for the current user for ALL months/years --- - // This is to populate the 'OverallPendingMonths' list accurately. var allRelevantOtEntries = (from status in _centralDbContext.Otstatus join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId where setting.ApprovalFlowId.HasValue && flowRoleMap.Keys.Contains(setting.ApprovalFlowId.Value) @@ -1624,19 +1582,16 @@ namespace PSTW_CentralSystem.Controllers.API var pendingMonthsAndYears = new HashSet(); - // We need to fetch the flows again here to get the full flow structure - // as `allRelevantOtEntries` only brings back ApprovalFlowId, not the full flow object - var allFlows = _centralDbContext.Approvalflow.ToList(); // Fetch all flows once + var allFlows = _centralDbContext.Approvalflow.ToList(); foreach (var entry in allRelevantOtEntries) { var role = flowRoleMap[entry.ApprovalFlowId.Value]; - var flow = allFlows.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); // Get the full flow object + var flow = allFlows.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); if (flow == null) continue; bool isPendingForCurrentUser = false; - // Determine if the current user has a pending action for this specific entry if (role == "HoU" && (entry.HouStatus == null || entry.HouStatus == "Pending") && !(entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected")) { @@ -1666,14 +1621,10 @@ namespace PSTW_CentralSystem.Controllers.API if (isPendingForCurrentUser) { - // Format as MM/YYYY and add to the set pendingMonthsAndYears.Add($"{entry.Month:D2}/{entry.Year}"); } } - // --- End of Modified Logic for Overall Pending Months --- - - // This part remains the same: load OT status entries for the *selected* month/year var otEntriesForSelectedMonth = (from status in _centralDbContext.Otstatus join user in _centralDbContext.Users on status.UserId equals user.Id join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId @@ -1702,7 +1653,7 @@ namespace PSTW_CentralSystem.Controllers.API var role = flowRoleMap[entry.ApprovalFlowId.Value]; distinctRoles.Add(role); - var flow = allFlows.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); // Use the already fetched allFlows + var flow = allFlows.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); if (flow == null) continue; bool canApprove = false; @@ -1765,7 +1716,7 @@ namespace PSTW_CentralSystem.Controllers.API { Roles = distinctRoles.ToList(), Data = processedList, - OverallPendingMonths = pendingMonthsAndYears.OrderByDescending(m => m).ToList() // Return sorted list of "MM/YYYY" + OverallPendingMonths = pendingMonthsAndYears.OrderByDescending(m => m).ToList() }); } @@ -1881,10 +1832,9 @@ namespace PSTW_CentralSystem.Controllers.API .Select(d => d.DepartmentName) .FirstOrDefault(); - // Assuming 'RateValue' in your 'Rates' table actually stores the Basic Salary var userBasicSalary = _centralDbContext.Rates .Where(r => r.UserId == user.Id) - .Select(r => r.RateValue) // Now this `RateValue` is intended to be the Basic Salary + .Select(r => r.RateValue) .FirstOrDefault(); var userSetting = _centralDbContext.Hrusersetting @@ -1900,10 +1850,9 @@ namespace PSTW_CentralSystem.Controllers.API if (userSetting?.Approvalflow == null) return NotFound("Approval flow information not found for the user."); - // Public holidays for the table display (already fetched and formatted) var publicHolidaysForTable = _centralDbContext.Holidays .Where(h => h.StateId == userSetting.State.StateId) - .Select(h => h.HolidayDate.Date.ToString("yyyy-MM-dd")) // Format to YYYY-MM-DD string + .Select(h => h.HolidayDate.Date.ToString("yyyy-MM-dd")) .ToList(); var otRecords = _centralDbContext.Otregisters @@ -1916,17 +1865,17 @@ namespace PSTW_CentralSystem.Controllers.API DateTime otDate = o.OtDate.Date; DayOfWeek dayOfWeek = otDate.DayOfWeek; - if (publicHolidaysForTable.Contains(otDate.ToString("yyyy-MM-dd"))) // Use formatted date for check + if (publicHolidaysForTable.Contains(otDate.ToString("yyyy-MM-dd"))) { dayType = "Public Holiday"; } - else if (userSetting.State.WeekendId == 1) // Friday/Saturday weekend (for specific states like Johor, Kedah, Kelantan, Terengganu) + else if (userSetting.State.WeekendId == 1) { if (dayOfWeek == DayOfWeek.Friday) dayType = "Off Day"; else if (dayOfWeek == DayOfWeek.Saturday) dayType = "Rest Day"; else dayType = "Normal Day"; } - else if (userSetting.State.WeekendId == 2) // Saturday/Sunday weekend (most other states) + else if (userSetting.State.WeekendId == 2) { if (dayOfWeek == DayOfWeek.Saturday) dayType = "Off Day"; else if (dayOfWeek == DayOfWeek.Sunday) dayType = "Rest Day"; @@ -1934,7 +1883,7 @@ namespace PSTW_CentralSystem.Controllers.API } else { - dayType = "Normal Day"; // Default if WeekendId is neither 1 nor 2 + dayType = "Normal Day"; } return new @@ -1953,7 +1902,7 @@ namespace PSTW_CentralSystem.Controllers.API o.OtDays, o.UserId, Rate = userBasicSalary, // Pass the Basic Salary as 'Rate' - DayType = dayType, // This is for the table display + DayType = dayType, }; }) .ToList(); @@ -1977,7 +1926,7 @@ namespace PSTW_CentralSystem.Controllers.API hasApproverActed = !string.IsNullOrEmpty(otStatus.HrStatus) && otStatus.HrStatus != "Pending"; break; default: - hasApproverActed = true; // Default to disabled if role not matched or already acted + hasApproverActed = true; break; } @@ -1991,9 +1940,9 @@ namespace PSTW_CentralSystem.Controllers.API filePath = otStatus.FilePath, fullNameLower = user.FullName?.ToLower(), flexiHour = userSetting.FlexiHour?.FlexiHour, - stateId = userSetting.State.StateId, // Ensure StateId is passed - weekendId = userSetting.State.WeekendId, // Ensure WeekendId is passed - rate = userBasicSalary // Send Basic Salary as 'rate' in userInfo as well for overall calculations + stateId = userSetting.State.StateId, + weekendId = userSetting.State.WeekendId, + rate = userBasicSalary }, records = otRecords, isHoU = userSetting.Approvalflow?.HoU == currentLoggedInUserId, @@ -2003,7 +1952,7 @@ namespace PSTW_CentralSystem.Controllers.API otStatus.HrStatus, approverRole, hasApproverActed - // public holidays are now fetched separately in the frontend for the modal + }; return Ok(result); @@ -2333,7 +2282,7 @@ namespace PSTW_CentralSystem.Controllers.API var currentLoggedInUserId = GetCurrentLoggedInUserId(); var userSetting = _centralDbContext.Hrusersetting - .Include(us => us.Approvalflow) // Include the ApprovalFlow to get approver IDs + .Include(us => us.Approvalflow) .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == user.Id); @@ -2345,33 +2294,27 @@ namespace PSTW_CentralSystem.Controllers.API string? flexiHour = userSetting?.FlexiHour?.FlexiHour; - // --- Logic for collecting approved signatures --- var approvedSignatures = new List(); - // Get the approval flow for the user whose OT is being viewed var approvalFlow = userSetting?.Approvalflow; if (approvalFlow != null) { - // Define the approval sequence and their corresponding status fields - // We'll also include the UserModel directly here for FullName - var approvalStages = new List<(int? approverId, string statusField, DateTime? submitDate, UserModel? approverUser)> - { - (approvalFlow.HoU, otStatus.HouStatus, otStatus.HouSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HoU)), - (approvalFlow.HoD, otStatus.HodStatus, otStatus.HodSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HoD)), - (approvalFlow.Manager, otStatus.ManagerStatus, otStatus.ManagerSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.Manager)), - (approvalFlow.HR, otStatus.HrStatus, otStatus.HrSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HR)) - }; - // Order by submit date to maintain flow, ensuring non-null submitDate are sorted - // Approvals without a submit date (null) would appear first if not handled with care. - // For a proper flow, we only consider stages with an Approved status and a valid submit date. + var approvalStages = new List<(int? approverId, string statusField, DateTime? submitDate, UserModel? approverUser)> + { + (approvalFlow.HoU, otStatus.HouStatus, otStatus.HouSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HoU)), + (approvalFlow.HoD, otStatus.HodStatus, otStatus.HodSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HoD)), + (approvalFlow.Manager, otStatus.ManagerStatus, otStatus.ManagerSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.Manager)), + (approvalFlow.HR, otStatus.HrStatus, otStatus.HrSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HR)) + }; + foreach (var stage in approvalStages .Where(s => s.approverUser != null && s.statusField == "Approved" && s.submitDate.HasValue) .OrderBy(s => s.submitDate)) { byte[]? signatureImageBytes = null; - // Directly query StaffSign using approver's UserId + var staffSign = _centralDbContext.Staffsign .FirstOrDefault(ss => ss.UserId == stage.approverId.Value); @@ -2379,11 +2322,10 @@ namespace PSTW_CentralSystem.Controllers.API { ApproverName = stage.approverUser.FullName, SignatureImage = signatureImageBytes, - ApprovedDate = stage.submitDate // Pass the submit date here + ApprovedDate = stage.submitDate }); } } - // --- End logic for collecting approved signatures --- var pdfGenerator = new OvertimePDF(_centralDbContext); var stream = pdfGenerator.GenerateOvertimeTablePdf(otRecords, user, userRate, selectedMonth, logoImage, isHoU, flexiHour, approvedSignatures); @@ -2402,7 +2344,6 @@ namespace PSTW_CentralSystem.Controllers.API if (user == null) return NotFound("User not found."); - // Get the user's rate var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) @@ -2436,7 +2377,6 @@ namespace PSTW_CentralSystem.Controllers.API logoImage = System.IO.File.ReadAllBytes(logoPath); } - // Get flexi hour if exists var flexiHour = _centralDbContext.Hrusersetting .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == userId)?.FlexiHour?.FlexiHour; @@ -2461,7 +2401,6 @@ namespace PSTW_CentralSystem.Controllers.API if (user == null) return NotFound("User not found."); - // Get the user's rate var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) @@ -2476,7 +2415,7 @@ namespace PSTW_CentralSystem.Controllers.API ? otRecords.First().OtDate : DateTime.Now; - var currentLoggedInUserId = GetCurrentLoggedInUserId(); // Assuming this is defined elsewhere in your API controller + var currentLoggedInUserId = GetCurrentLoggedInUserId(); var userSetting = _centralDbContext.Hrusersetting .Include(us => us.Approvalflow) @@ -2491,13 +2430,11 @@ namespace PSTW_CentralSystem.Controllers.API string? flexiHour = userSetting?.FlexiHour?.FlexiHour; - // Pass _env to the OvertimeExcel constructor var excelGenerator = new OvertimeExcel(_centralDbContext, _env); var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg"); byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null; - // Pass the otStatus object to the GenerateOvertimeExcel method var stream = excelGenerator.GenerateOvertimeExcel(otRecords, user, userRate, selectedMonth, isHoU, flexiHour, logoImage, isSimplifiedExport: false, otStatus); string fileName = $"OvertimeReport_{user.FullName}_{DateTime.Now:yyyyMMdd}.xlsx"; @@ -2516,20 +2453,17 @@ namespace PSTW_CentralSystem.Controllers.API if (user == null) return NotFound("User not found."); - // Get the user's rate var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) .FirstOrDefault()?.RateValue ?? 0m; - // Get the user's flexi hour setting var userSetting = _centralDbContext.Hrusersetting .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == userId); string flexiHour = userSetting?.FlexiHour?.FlexiHour; - // Get records for the selected month/year var startDate = new DateTime(year, month, 1); var endDate = startDate.AddMonths(1); @@ -2538,28 +2472,22 @@ namespace PSTW_CentralSystem.Controllers.API .Where(o => o.UserId == userId && o.OtDate >= startDate && o.OtDate < endDate) .ToList(); - // Check if user is admin/PSTWAIR to show station column (logic remains same) - bool isAdminUser = IsAdmin(user.Id); // Assuming IsAdmin is defined elsewhere in your API controller - // bool isPSTWAIR = user.Department?.DepartmentId == 2 || isAdminUser; // This variable is not used after declaration. + bool isAdminUser = IsAdmin(user.Id); - // Corrected line: Pass _env to the OvertimeExcel constructor var excelGenerator = new OvertimeExcel(_centralDbContext, _env); - // Get logo var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg"); byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null; - // Call GenerateOvertimeExcel with isSimplifiedExport = true - // For regular users, isHoU is typically false as they are not generating approver-specific reports. var stream = excelGenerator.GenerateOvertimeExcel( otRecords, user, userRate, startDate, - isHoU: false, // Set to false for regular user report + isHoU: false, flexiHour: flexiHour, logoImage: logoImage, - isSimplifiedExport: true // Set to true for the simplified report from OtRecords + isSimplifiedExport: true ); string fileName = $"OvertimeReport_{user.FullName}_{month}_{year}.xlsx"; diff --git a/Program.cs b/Program.cs index 6861b30..29bd396 100644 --- a/Program.cs +++ b/Program.cs @@ -40,6 +40,7 @@ internal class Program outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}")) .WriteTo.Console() .CreateLogger(); + // Set default session to 30 minutes builder.Services.AddSession(options => { @@ -54,6 +55,7 @@ internal class Program mysqlOptions => mysqlOptions.CommandTimeout(120) ); }); + //builder.Services.AddDbContext(options => //{ // options.UseMySql(inventoryConnectionString, new MySqlServerVersion(new Version(8, 0, 39)),