Latest
This commit is contained in:
parent
5efe8e13c4
commit
efd69601ec
@ -20,7 +20,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers
|
|||||||
}
|
}
|
||||||
public IActionResult OtReview(int statusId)
|
public IActionResult OtReview(int statusId)
|
||||||
{
|
{
|
||||||
ViewBag.StatusId = statusId; // If needed in the view
|
ViewBag.StatusId = statusId;
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,36 +47,34 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Models
|
|||||||
return TimeSpan.TryParse(time, out TimeSpan result) ? result : null;
|
return TimeSpan.TryParse(time, out TimeSpan result) ? result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// OtRegisterEditDto.cs
|
|
||||||
public class OtRegisterEditDto
|
public class OtRegisterEditDto
|
||||||
{
|
{
|
||||||
public int OvertimeId { get; set; }
|
public int OvertimeId { get; set; }
|
||||||
public DateTime OtDate { get; set; }
|
public DateTime OtDate { get; set; }
|
||||||
public string? OfficeFrom { get; set; } // Use string to match input type (e.g., "09:00")
|
public string? OfficeFrom { get; set; }
|
||||||
public string? OfficeTo { get; set; } // Use string to match input type
|
public string? OfficeTo { get; set; }
|
||||||
public int? OfficeBreak { get; set; }
|
public int? OfficeBreak { get; set; }
|
||||||
public string? AfterFrom { get; set; } // Use string to match input type
|
public string? AfterFrom { get; set; }
|
||||||
public string? AfterTo { get; set; } // Use string to match input type
|
public string? AfterTo { get; set; }
|
||||||
public int? AfterBreak { get; set; }
|
public int? AfterBreak { get; set; }
|
||||||
public int? StationId { get; set; }
|
public int? StationId { get; set; }
|
||||||
public string? OtDescription { 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 int StatusId { get; set; }
|
||||||
public string? OtDays { get; set; }
|
public string? OtDays { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OtUpdateLog
|
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 int ApproverUserId { get; set; }
|
||||||
public DateTime UpdateTimestamp { 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 OtRegisterModel? BeforeEdit { get; set; }
|
||||||
public OtRegisterEditDto? AfterEdit { get; set; }
|
public OtRegisterEditDto? AfterEdit { get; set; }
|
||||||
|
|
||||||
// For "Delete" type
|
|
||||||
public OtRegisterModel? DeletedRecord { get; set; }
|
public OtRegisterModel? DeletedRecord { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Models
|
|||||||
{
|
{
|
||||||
public string ApproverName { get; set; }
|
public string ApproverName { get; set; }
|
||||||
public byte[]? SignatureImage { get; set; }
|
public byte[]? SignatureImage { get; set; }
|
||||||
public DateTime? ApprovedDate { get; set; } // New property for the approval date
|
public DateTime? ApprovedDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using PSTW_CentralSystem.Areas.OTcalculate.Models;
|
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 Microsoft.EntityFrameworkCore;
|
||||||
using PSTW_CentralSystem.DBContext;
|
using PSTW_CentralSystem.DBContext;
|
||||||
|
|
||||||
@ -22,14 +22,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MemoryStream GenerateOvertimeTablePdf(
|
public MemoryStream GenerateOvertimeTablePdf(
|
||||||
List<OtRegisterModel> records,
|
List<OtRegisterModel> records,
|
||||||
UserModel user,
|
UserModel user,
|
||||||
decimal userRate,
|
decimal userRate,
|
||||||
DateTime? selectedMonth = null,
|
DateTime? selectedMonth = null,
|
||||||
byte[]? logoImage = null,
|
byte[]? logoImage = null,
|
||||||
bool isHoU = false,
|
bool isHoU = false,
|
||||||
string? flexiHour = null,
|
string? flexiHour = null,
|
||||||
List<ApprovalSignatureData>? approvedSignatures = null)
|
List<ApprovalSignatureData>? approvedSignatures = null)
|
||||||
{
|
{
|
||||||
bool isAdminUser = IsAdmin(user.Id);
|
bool isAdminUser = IsAdmin(user.Id);
|
||||||
bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser;
|
bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser;
|
||||||
@ -39,15 +39,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
|
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
|
||||||
|
|
||||||
// Fetch user setting here once
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.State)
|
.Include(us => us.State)
|
||||||
.FirstOrDefault(us => us.UserId == user.Id);
|
.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
|
var publicHolidaysForUser = _centralDbContext.Holidays
|
||||||
.Where(h => userSetting != null && h.StateId == userSetting.State.StateId && h.HolidayDate.Month == displayMonth.Month && h.HolidayDate.Year == displayMonth.Year)
|
.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();
|
.ToList();
|
||||||
|
|
||||||
records = records.OrderBy(r => r.OtDate).ToList();
|
records = records.OrderBy(r => r.OtDate).ToList();
|
||||||
@ -62,7 +60,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
page.Content().Column(column =>
|
page.Content().Column(column =>
|
||||||
{
|
{
|
||||||
// Top Section: Logo, User Info, Generated Date
|
|
||||||
column.Item().Row(row =>
|
column.Item().Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem(2).Column(col =>
|
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);
|
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 =>
|
col.Item().PaddingBottom(2).Text(text =>
|
||||||
{
|
{
|
||||||
text.Span("Name: ").SemiBold().FontSize(9);
|
text.Span("Name: ").SemiBold().FontSize(9);
|
||||||
@ -93,12 +89,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
.FontSize(9).FontColor(Colors.Grey.Medium);
|
.FontSize(9).FontColor(Colors.Grey.Medium);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Overtime Record title remains separate for clarity
|
|
||||||
column.Item().PaddingTop(3).Row(row =>
|
column.Item().PaddingTop(3).Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem(2).Text($"Overtime Record: {displayMonth:MMMM yyyy}").FontSize(9).Italic();
|
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 =>
|
row.RelativeItem(1).AlignRight().Column(legendCol =>
|
||||||
{
|
{
|
||||||
legendCol.Item().Element(e => e.ShowEntire().Row(subRow =>
|
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);
|
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()));
|
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 =>
|
column.Item().PaddingTop(20).Element(container =>
|
||||||
{
|
{
|
||||||
container.ShowEntire().Row(row =>
|
container.ShowEntire().Row(row =>
|
||||||
{
|
{
|
||||||
// Left side - Approval Signatures
|
|
||||||
row.RelativeItem().Element(e => e.ShowEntire().Column(approvalCol =>
|
row.RelativeItem().Element(e => e.ShowEntire().Column(approvalCol =>
|
||||||
{
|
{
|
||||||
if (approvedSignatures != null && approvedSignatures.Any())
|
if (approvedSignatures != null && approvedSignatures.Any())
|
||||||
@ -161,8 +153,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
if (approval.ApprovedDate.HasValue)
|
if (approval.ApprovedDate.HasValue)
|
||||||
{
|
{
|
||||||
individualApprovalColumn.Item().PaddingTop(2)
|
individualApprovalColumn.Item().PaddingTop(2)
|
||||||
.Text(approval.ApprovedDate.Value.ToString("dd MMMM yyyy")) // Changed format to avoid special characters
|
.Text(approval.ApprovedDate.Value.ToString("dd MMMM yyyy"))
|
||||||
.FontSize(8).AlignCenter();
|
.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 =>
|
row.RelativeItem().Element(e => e.ShowEntire().Column(remarksCol =>
|
||||||
{
|
{
|
||||||
remarksCol.Item().Element(e => e.ShowEntire().Row(remarksRow =>
|
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 =>
|
remarksRow.RelativeItem(2).Element(e => e.ShowEntire().AlignRight().Column(holidayListCol =>
|
||||||
{
|
{
|
||||||
holidayListCol.Item().Text("Public Holidays:").FontSize(9).SemiBold();
|
holidayListCol.Item().Text("Public Holidays:").FontSize(9).SemiBold();
|
||||||
@ -183,8 +173,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
{
|
{
|
||||||
table.ColumnsDefinition(columns =>
|
table.ColumnsDefinition(columns =>
|
||||||
{
|
{
|
||||||
columns.RelativeColumn(1); // For Date
|
columns.RelativeColumn(1);
|
||||||
columns.RelativeColumn(2); // For Holiday Name
|
columns.RelativeColumn(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
table.Header(header =>
|
table.Header(header =>
|
||||||
@ -204,8 +194,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
table.Cell().ColumnSpan(2).Border(0.25f).Padding(2)
|
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
|
.Text($"No public holidays found for {userSetting?.State?.StateName ?? "this state"} in {displayMonth:MMMM yyyy}.")
|
||||||
.FontSize(7).Italic().AlignCenter();
|
.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())
|
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);
|
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;
|
bool alternate = false;
|
||||||
|
|
||||||
// Changed calculations as per your request
|
|
||||||
var basicSalary = userRate; // userRate is now treated as basic salary
|
var basicSalary = userRate; // userRate is now treated as basic salary
|
||||||
var orp = basicSalary / 26m; // Calculate ORP from basicSalary
|
var orp = basicSalary / 26m; // Calculate ORP from basicSalary
|
||||||
var hrp = orp / 8m; // Calculate HRP from ORP
|
var hrp = orp / 8m; // Calculate HRP from ORP
|
||||||
@ -431,7 +420,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
AddCell(!hasPrintedSalaryDetails ? $"{orp:F2}" : "");
|
AddCell(!hasPrintedSalaryDetails ? $"{orp:F2}" : "");
|
||||||
AddCell(!hasPrintedSalaryDetails ? $"{hrp:F2}" : "");
|
AddCell(!hasPrintedSalaryDetails ? $"{hrp:F2}" : "");
|
||||||
}
|
}
|
||||||
hasPrintedSalaryDetails = true; // Ensure these values are printed only once
|
hasPrintedSalaryDetails = true;
|
||||||
|
|
||||||
if (date.Date != previousDate?.Date)
|
if (date.Date != previousDate?.Date)
|
||||||
{
|
{
|
||||||
@ -554,7 +543,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
if (!isHoU)
|
if (!isHoU)
|
||||||
{
|
{
|
||||||
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
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)
|
if (showStationColumn)
|
||||||
@ -566,12 +555,12 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MemoryStream GenerateSimpleOvertimeTablePdf(
|
public MemoryStream GenerateSimpleOvertimeTablePdf(
|
||||||
List<OtRegisterModel> records,
|
List<OtRegisterModel> records,
|
||||||
UserModel user, // User parameter added here
|
UserModel user,
|
||||||
decimal userRate,
|
decimal userRate,
|
||||||
DateTime? selectedMonth = null,
|
DateTime? selectedMonth = null,
|
||||||
byte[]? logoImage = null,
|
byte[]? logoImage = null,
|
||||||
string? flexiHour = null)
|
string? flexiHour = null)
|
||||||
{
|
{
|
||||||
bool isAdminUser = IsAdmin(user.Id);
|
bool isAdminUser = IsAdmin(user.Id);
|
||||||
bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser;
|
bool showStationColumn = user.departmentId == 2 || user.departmentId == 3 || isAdminUser;
|
||||||
@ -581,15 +570,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
|
var allDatesInMonth = GetAllDatesInMonth(displayMonth);
|
||||||
|
|
||||||
// Fetch user setting here once
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.State)
|
.Include(us => us.State)
|
||||||
.FirstOrDefault(us => us.UserId == user.Id);
|
.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
|
var publicHolidaysForUser = _centralDbContext.Holidays
|
||||||
.Where(h => userSetting != null && h.StateId == userSetting.State.StateId && h.HolidayDate.Month == displayMonth.Month && h.HolidayDate.Year == displayMonth.Year)
|
.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();
|
.ToList();
|
||||||
|
|
||||||
records = records.OrderBy(r => r.OtDate).ToList();
|
records = records.OrderBy(r => r.OtDate).ToList();
|
||||||
@ -604,7 +591,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
page.Content().Column(column =>
|
page.Content().Column(column =>
|
||||||
{
|
{
|
||||||
// Top Section: Logo, User Info, Generated Date
|
|
||||||
column.Item().Row(row =>
|
column.Item().Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem(2).Column(col =>
|
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);
|
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 =>
|
col.Item().PaddingBottom(2).Text(text =>
|
||||||
{
|
{
|
||||||
text.Span("Name: ").SemiBold().FontSize(9);
|
text.Span("Name: ").SemiBold().FontSize(9);
|
||||||
@ -635,12 +620,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
.FontSize(9).FontColor(Colors.Grey.Medium);
|
.FontSize(9).FontColor(Colors.Grey.Medium);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Overtime Record title remains separate for clarity
|
|
||||||
column.Item().PaddingTop(3).Row(row =>
|
column.Item().PaddingTop(3).Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem(2).Text($"Overtime Record: {displayMonth:MMMM yyyy}").SemiBold().FontSize(9).Italic();
|
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 =>
|
row.RelativeItem(1).AlignRight().Column(legendCol =>
|
||||||
{
|
{
|
||||||
legendCol.Item().Element(e => e.ShowEntire().Row(subRow =>
|
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);
|
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()));
|
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 =>
|
column.Item().PaddingTop(20).Element(container =>
|
||||||
{
|
{
|
||||||
container.ShowEntire().Row(row =>
|
container.ShowEntire().Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem().Column(legendCol =>
|
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 =>
|
row.RelativeItem(2).AlignRight().Column(holidayListCol =>
|
||||||
{
|
{
|
||||||
holidayListCol.Item().Element(e => e.ShowEntire().Text("Public Holidays:").FontSize(9).SemiBold());
|
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 =>
|
table.ColumnsDefinition(columns =>
|
||||||
{
|
{
|
||||||
columns.RelativeColumn(1); // For Date
|
columns.RelativeColumn(1);
|
||||||
columns.RelativeColumn(2); // For Holiday Name
|
columns.RelativeColumn(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
table.Header(header =>
|
table.Header(header =>
|
||||||
@ -708,7 +688,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
{
|
{
|
||||||
table.Cell().ColumnSpan(2).Border(0.25f).Padding(2)
|
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
|
.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;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT: The ComposeSimpleTable method signature now accepts List<DateTime> for public holidays.
|
|
||||||
private void ComposeSimpleTable(IContainer container, List<OtRegisterModel> records, UserModel user, bool showStationColumn, List<DateTime> allDatesInMonth, List<DateTime> publicHolidays)
|
private void ComposeSimpleTable(IContainer container, List<OtRegisterModel> records, UserModel user, bool showStationColumn, List<DateTime> allDatesInMonth, List<DateTime> publicHolidays)
|
||||||
{
|
{
|
||||||
var recordsGroupedByDate = records
|
var recordsGroupedByDate = records
|
||||||
.GroupBy(r => r.OtDate.Date)
|
.GroupBy(r => r.OtDate.Date)
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
// Fetch user settings and public holidays within ComposeSimpleTable
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.State)
|
.Include(us => us.State)
|
||||||
.FirstOrDefault(us => us.UserId == user.Id);
|
.FirstOrDefault(us => us.UserId == user.Id);
|
||||||
|
|
||||||
container.Table(table =>
|
container.Table(table =>
|
||||||
{
|
{
|
||||||
// Define columns
|
|
||||||
table.ColumnsDefinition(columns =>
|
table.ColumnsDefinition(columns =>
|
||||||
{
|
{
|
||||||
columns.RelativeColumn(0.8f); // Day (ddd)
|
columns.RelativeColumn(0.8f); // Day (ddd)
|
||||||
@ -766,53 +743,52 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
// Header
|
// Header
|
||||||
table.Header(header =>
|
table.Header(header =>
|
||||||
{
|
{
|
||||||
// First row of the header
|
|
||||||
header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3)
|
header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3)
|
||||||
.Text("Day").FontSize(6).Bold().AlignCenter();
|
.Text("Day").FontSize(6).Bold().AlignCenter();
|
||||||
|
|
||||||
header.Cell().RowSpan(2).Background("#d9ead3").Border(0.25f).Padding(3)
|
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)
|
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)
|
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)
|
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)
|
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)
|
if (showStationColumn)
|
||||||
{
|
{
|
||||||
header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(3)
|
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)
|
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)
|
// Second row of the header (sub-headers)
|
||||||
header.Cell().Background("#cfe2f3").Border(0.25f).Padding(3)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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 totalHours = 0;
|
||||||
decimal totalBreak = 0;
|
decimal totalBreak = 0;
|
||||||
bool alternate = false;
|
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)
|
foreach (var date in allDatesInMonth)
|
||||||
{
|
{
|
||||||
@ -822,7 +798,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
foreach (var record in recordsToShow)
|
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);
|
var backgroundColor = GetDayCellBackgroundColor(date, userSetting?.State?.WeekendId, publicHolidays);
|
||||||
|
|
||||||
string rowBg = alternate ? "#f9f9f9" : "#ffffff";
|
string rowBg = alternate ? "#f9f9f9" : "#ffffff";
|
||||||
@ -836,7 +811,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
if (center) text.AlignCenter();
|
if (center) text.AlignCenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply background color to Day and Date cells
|
|
||||||
if (date.Date != previousDate?.Date)
|
if (date.Date != previousDate?.Date)
|
||||||
{
|
{
|
||||||
AddCell(date.ToString("ddd"), true, backgroundColor);
|
AddCell(date.ToString("ddd"), true, backgroundColor);
|
||||||
@ -844,14 +818,11 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
}
|
}
|
||||||
else
|
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);
|
||||||
AddCell("", true, backgroundColor);
|
AddCell("", true, backgroundColor);
|
||||||
}
|
}
|
||||||
previousDate = date; // Update previousDate for the next iteration
|
previousDate = date;
|
||||||
|
|
||||||
|
|
||||||
// Calculate values
|
|
||||||
var officeHours = CalculateOfficeOtHours(record);
|
var officeHours = CalculateOfficeOtHours(record);
|
||||||
var afterHours = CalculateAfterOfficeOtHours(record);
|
var afterHours = CalculateAfterOfficeOtHours(record);
|
||||||
var totalTime = ConvertTimeToDecimal(officeHours) + ConvertTimeToDecimal(afterHours);
|
var totalTime = ConvertTimeToDecimal(officeHours) + ConvertTimeToDecimal(afterHours);
|
||||||
@ -874,7 +845,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
AddCell(FormatAsHourMinute(totalTime, isMinutes: false));
|
AddCell(FormatAsHourMinute(totalTime, isMinutes: false));
|
||||||
AddCell(FormatAsHourMinute(breakTime, isMinutes: true));
|
AddCell(FormatAsHourMinute(breakTime, isMinutes: true));
|
||||||
|
|
||||||
// Station (if applicable)
|
// Station
|
||||||
if (showStationColumn)
|
if (showStationColumn)
|
||||||
{
|
{
|
||||||
AddCell(record.Stations?.StationName ?? "");
|
AddCell(record.Stations?.StationName ?? "");
|
||||||
@ -882,7 +853,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
// Description
|
// Description
|
||||||
table.Cell().Background(rowBg).Border(0.25f).Padding(2)
|
table.Cell().Background(rowBg).Border(0.25f).Padding(2)
|
||||||
.Text(record.OtDescription ?? "").FontSize(6).AlignLeft();
|
.Text(record.OtDescription ?? "").FontSize(6).AlignLeft();
|
||||||
|
|
||||||
alternate = !alternate;
|
alternate = !alternate;
|
||||||
}
|
}
|
||||||
@ -891,21 +862,17 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
// Footer with totals
|
// Footer with totals
|
||||||
table.Footer(footer =>
|
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)
|
footer.Cell().ColumnSpan(2).Background("#d8d1f5").Border(0.80f).Padding(3)
|
||||||
.Text("TOTAL").Bold().FontSize(6).AlignCenter();
|
.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();
|
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)
|
footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
||||||
.Text(FormatAsHourMinute(totalHours, isMinutes: false)).Bold().FontSize(6).AlignCenter();
|
.Text(FormatAsHourMinute(totalHours, isMinutes: false)).Bold().FontSize(6).AlignCenter();
|
||||||
|
|
||||||
footer.Cell().Background("#d8d1f5").Border(0.80f).Padding(3)
|
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)
|
if (showStationColumn)
|
||||||
{
|
{
|
||||||
@ -935,9 +902,9 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
var lastDate = records.Last().OtDate;
|
var lastDate = records.Last().OtDate;
|
||||||
|
|
||||||
if (firstDate.Month == lastDate.Month && firstDate.Year == lastDate.Year)
|
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)
|
private static IContainer CellStyle(IContainer container)
|
||||||
@ -956,8 +923,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
return "";
|
return "";
|
||||||
|
|
||||||
TimeSpan ts = isMinutes
|
TimeSpan ts = isMinutes
|
||||||
? TimeSpan.FromMinutes((double)hoursOrMinutes.Value)
|
? TimeSpan.FromMinutes((double)hoursOrMinutes.Value)
|
||||||
: TimeSpan.FromHours((double)hoursOrMinutes.Value);
|
: TimeSpan.FromHours((double)hoursOrMinutes.Value);
|
||||||
|
|
||||||
int totalHours = (int)(ts.TotalHours);
|
int totalHours = (int)(ts.TotalHours);
|
||||||
int minutes = (int)(ts.Minutes);
|
int minutes = (int)(ts.Minutes);
|
||||||
@ -974,14 +941,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
DayOfWeek dayOfWeek = date.DayOfWeek;
|
DayOfWeek dayOfWeek = date.DayOfWeek;
|
||||||
|
|
||||||
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Saturday) || // Assuming weekendId 1 means Saturday is Rest Day
|
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Saturday) ||
|
||||||
(weekendId == 2 && dayOfWeek == DayOfWeek.Sunday)) // Assuming weekendId 2 means Sunday is Rest Day
|
(weekendId == 2 && dayOfWeek == DayOfWeek.Sunday))
|
||||||
{
|
{
|
||||||
return "Rest Day";
|
return "Rest Day";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Friday) || // Assuming weekendId 1 means Friday is Off Day
|
if ((weekendId == 1 && dayOfWeek == DayOfWeek.Friday) ||
|
||||||
(weekendId == 2 && dayOfWeek == DayOfWeek.Saturday)) // Assuming weekendId 2 means Saturday is Off Day
|
(weekendId == 2 && dayOfWeek == DayOfWeek.Saturday))
|
||||||
{
|
{
|
||||||
return "Off Day";
|
return "Off Day";
|
||||||
}
|
}
|
||||||
@ -989,7 +956,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
return "Normal Day";
|
return "Normal Day";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updated these methods to reflect the new calculation logic and accept the correct input.
|
|
||||||
private decimal CalculateOrp(decimal basicSalary)
|
private decimal CalculateOrp(decimal basicSalary)
|
||||||
{
|
{
|
||||||
return basicSalary / 26m;
|
return basicSalary / 26m;
|
||||||
@ -1019,20 +985,18 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
{
|
{
|
||||||
if (!record.OfficeFrom.HasValue || !record.OfficeTo.HasValue) return "";
|
if (!record.OfficeFrom.HasValue || !record.OfficeTo.HasValue) return "";
|
||||||
|
|
||||||
// Convert TimeSpan to minutes from start of the day
|
|
||||||
double fromMinutes = record.OfficeFrom.Value.TotalMinutes;
|
double fromMinutes = record.OfficeFrom.Value.TotalMinutes;
|
||||||
double toMinutes = record.OfficeTo.Value.TotalMinutes;
|
double toMinutes = record.OfficeTo.Value.TotalMinutes;
|
||||||
|
|
||||||
double totalMinutes = toMinutes - fromMinutes;
|
double totalMinutes = toMinutes - fromMinutes;
|
||||||
|
|
||||||
// Handle overnight shifts (e.g., 21:00 to 02:00)
|
|
||||||
if (totalMinutes < 0)
|
if (totalMinutes < 0)
|
||||||
{
|
{
|
||||||
totalMinutes += 24 * 60; // Add 24 hours in minutes
|
totalMinutes += 24 * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalMinutes -= (record.OfficeBreak ?? 0); // Subtract break
|
totalMinutes -= (record.OfficeBreak ?? 0);
|
||||||
totalMinutes = Math.Max(0, totalMinutes); // Ensure total hours are not negative
|
totalMinutes = Math.Max(0, totalMinutes);
|
||||||
|
|
||||||
int hours = (int)(totalMinutes / 60);
|
int hours = (int)(totalMinutes / 60);
|
||||||
int minutes = (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 "";
|
if (!record.AfterFrom.HasValue || !record.AfterTo.HasValue) return "";
|
||||||
|
|
||||||
// Convert TimeSpan to minutes from start of the day
|
|
||||||
double fromMinutes = record.AfterFrom.Value.TotalMinutes;
|
double fromMinutes = record.AfterFrom.Value.TotalMinutes;
|
||||||
double toMinutes = record.AfterTo.Value.TotalMinutes;
|
double toMinutes = record.AfterTo.Value.TotalMinutes;
|
||||||
|
|
||||||
double totalMinutes = toMinutes - fromMinutes;
|
double totalMinutes = toMinutes - fromMinutes;
|
||||||
|
|
||||||
// Handle overnight shifts (e.g., 21:00 to 02:00)
|
|
||||||
if (totalMinutes < 0)
|
if (totalMinutes < 0)
|
||||||
{
|
{
|
||||||
totalMinutes += 24 * 60; // Add 24 hours in minutes
|
totalMinutes += 24 * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalMinutes -= (record.AfterBreak ?? 0); // Subtract break
|
totalMinutes -= (record.AfterBreak ?? 0);
|
||||||
totalMinutes = Math.Max(0, totalMinutes); // Ensure total hours are not negative
|
totalMinutes = Math.Max(0, totalMinutes);
|
||||||
|
|
||||||
int hours = (int)(totalMinutes / 60);
|
int hours = (int)(totalMinutes / 60);
|
||||||
int minutes = (int)(totalMinutes % 60);
|
int minutes = (int)(totalMinutes % 60);
|
||||||
@ -1073,7 +1035,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
var officeHrs = ConvertTimeToDecimal(officeRaw);
|
var officeHrs = ConvertTimeToDecimal(officeRaw);
|
||||||
var afterHrs = ConvertTimeToDecimal(afterRaw);
|
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 toFixedOrEmpty = (decimal num) => num > 0 ? num.ToString("N2") : "";
|
||||||
|
|
||||||
var dayType = GetDayType(record.OtDate, weekendId, publicHolidays);
|
var dayType = GetDayType(record.OtDate, weekendId, publicHolidays);
|
||||||
@ -1094,7 +1055,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
switch (dayType)
|
switch (dayType)
|
||||||
{
|
{
|
||||||
case "Normal Day":
|
case "Normal Day":
|
||||||
result["ndAfter"] = toFixedOrEmpty(afterHrs); // Only after-office OT on normal days
|
result["ndAfter"] = toFixedOrEmpty(afterHrs);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Off Day":
|
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)
|
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);
|
result["odAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["odAfter"]) + afterHrs);
|
||||||
break;
|
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)
|
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);
|
result["rdAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["rdAfter"]) + afterHrs);
|
||||||
break;
|
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)
|
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);
|
result["phAfter"] = toFixedOrEmpty(ConvertTimeToDecimal(result["phAfter"]) + afterHrs);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1210,16 +1170,8 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
private string CalculateOtAmount(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
private string CalculateOtAmount(OtRegisterModel record, decimal hrp, List<DateTime> publicHolidays, int? weekendId)
|
||||||
{
|
{
|
||||||
// Now, orp and hrp are already correctly calculated based on the incoming userRate (which is now Basic Salary)
|
decimal orp = hrp * 8m;
|
||||||
// in the ComposeTable method, so we pass hrp directly.
|
|
||||||
// However, the existing logic in this method relies on 'orp' and 'hrp' being derived
|
|
||||||
// in a specific way for the calculations.
|
|
||||||
// If the incoming 'hrp' to this method is indeed the HRP value, we need to calculate ORP from it.
|
|
||||||
// Re-calculating ORP and HRP here based on the 'hrp' received by this method to align with existing calculation logic.
|
|
||||||
decimal orpFromHrp = hrp * 8m; // This is the ORP used in calculations within this method
|
|
||||||
decimal basicSalaryFromOrp = orpFromHrp * 26m; // This is the Basic Salary derived from orpFromHrp
|
|
||||||
|
|
||||||
var orp = orpFromHrp; // Use the ORP derived from the HRP passed to this function
|
|
||||||
var dayType = GetDayType(record.OtDate, weekendId, publicHolidays);
|
var dayType = GetDayType(record.OtDate, weekendId, publicHolidays);
|
||||||
|
|
||||||
var officeRaw = CalculateOfficeOtHours(record);
|
var officeRaw = CalculateOfficeOtHours(record);
|
||||||
@ -1273,22 +1225,22 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
private List<DateTime> GetAllDatesInMonth(DateTime month)
|
private List<DateTime> GetAllDatesInMonth(DateTime month)
|
||||||
{
|
{
|
||||||
return Enumerable.Range(1, DateTime.DaysInMonth(month.Year, month.Month))
|
return Enumerable.Range(1, DateTime.DaysInMonth(month.Year, month.Month))
|
||||||
.Select(day => new DateTime(month.Year, month.Month, day))
|
.Select(day => new DateTime(month.Year, month.Month, day))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDayCellBackgroundColor(DateTime date, int? weekendId, List<DateTime> publicHolidays)
|
private string GetDayCellBackgroundColor(DateTime date, int? weekendId, List<DateTime> publicHolidays)
|
||||||
{
|
{
|
||||||
if (publicHolidays.Contains(date.Date))
|
if (publicHolidays.Contains(date.Date))
|
||||||
return "#ffc0cb"; // Light red for Public Holiday
|
return "#ffc0cb";
|
||||||
|
|
||||||
var dayOfWeek = date.DayOfWeek;
|
var dayOfWeek = date.DayOfWeek;
|
||||||
// Assuming weekendId 1 for Friday/Saturday weekend, 2 for Saturday/Sunday
|
|
||||||
if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) ||
|
if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) ||
|
||||||
(weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday)))
|
(weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday)))
|
||||||
return "#add8e6"; // Light blue for Weekend
|
return "#add8e6";
|
||||||
|
|
||||||
return "#ffffff"; // White for Normal Day
|
return "#ffffff";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,7 +4,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Your existing CSS styles remain here */
|
|
||||||
body {
|
body {
|
||||||
background-color: #f3f4f6;
|
background-color: #f3f4f6;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@ -15,27 +14,26 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
margin-top: 20px; /* Adjusted margin-top for table-layer */
|
margin-top: 20px;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container table {
|
.table-container table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 0; /* Remove default table margin */
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle; /* Center header text vertically */
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-sm {
|
.btn-sm {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles for status badges */
|
|
||||||
.badge-pending {
|
.badge-pending {
|
||||||
background-color: #ffc107;
|
background-color: #ffc107;
|
||||||
color: #343a40;
|
color: #343a40;
|
||||||
@ -56,7 +54,6 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom style for action column buttons */
|
|
||||||
.action-buttons button {
|
.action-buttons button {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
@ -65,7 +62,6 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sorting indicator styles */
|
|
||||||
.sortable-header {
|
.sortable-header {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -83,42 +79,40 @@
|
|||||||
|
|
||||||
.pagination-controls {
|
.pagination-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap; /* Allow wrapping on smaller screens */
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 0; /* Add padding for separation */
|
padding: 10px 0;
|
||||||
border-top: 1px solid #e9ecef; /* Light border to separate from table */
|
border-top: 1px solid #e9ecef;
|
||||||
margin-top: 15px; /* Margin above pagination controls */
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-info {
|
.pagination-info {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: #555;
|
color: #555;
|
||||||
margin-right: 15px; /* Space between info and pagination links */
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Adjustments for "Items per page" dropdown */
|
|
||||||
.pagination-items-per-page {
|
.pagination-items-per-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-items-per-page label {
|
.pagination-items-per-page label {
|
||||||
white-space: nowrap; /* Prevent label from wrapping */
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* New style for the formal overall pending notice */
|
|
||||||
.formal-pending-notice {
|
.formal-pending-notice {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 12px 20px; /* Slightly more padding for formal look */
|
padding: 12px 20px;
|
||||||
border-radius: 8px; /* More rounded corners */
|
border-radius: 8px;
|
||||||
background-color: #f8d7da; /* Light red/danger background for attention */
|
background-color: #f8d7da;
|
||||||
border: 1px solid #dc3545; /* Deeper red border */
|
border: 1px solid #dc3545;
|
||||||
color: #721c24; /* Dark red text for formal warning */
|
color: #721c24;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left; /* Align text to the left */
|
text-align: left;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -284,7 +278,7 @@
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
itemsPerPage: 10,
|
itemsPerPage: 10,
|
||||||
overallPendingMonths: [] // To store the list of "MM/YYYY" strings
|
overallPendingMonths: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Your existing styles (excluding the previously added .date-column specific styles) */
|
|
||||||
.table-container {
|
.table-container {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
@ -386,7 +385,7 @@
|
|||||||
flexiHour: '',
|
flexiHour: '',
|
||||||
stateId: null,
|
stateId: null,
|
||||||
weekendId: null,
|
weekendId: null,
|
||||||
basicSalary: null // This will now hold the Basic Salary retrieved from API
|
basicSalary: null
|
||||||
},
|
},
|
||||||
isHoU: false,
|
isHoU: false,
|
||||||
expandedDescriptions: [],
|
expandedDescriptions: [],
|
||||||
@ -468,7 +467,6 @@
|
|||||||
this.otRecords.forEach(record => {
|
this.otRecords.forEach(record => {
|
||||||
const classified = this.classifyOt(record);
|
const classified = this.classifyOt(record);
|
||||||
|
|
||||||
// Breaks are already in minutes, no need for parseFloat and then toFixed for summing
|
|
||||||
totals.officeBreak += record.officeBreak || 0;
|
totals.officeBreak += record.officeBreak || 0;
|
||||||
totals.afterBreak += record.afterBreak || 0;
|
totals.afterBreak += record.afterBreak || 0;
|
||||||
|
|
||||||
@ -484,7 +482,6 @@
|
|||||||
totals.phUnder8 += parseFloat(classified.phUnder8) || 0;
|
totals.phUnder8 += parseFloat(classified.phUnder8) || 0;
|
||||||
totals.phAfter += parseFloat(classified.phAfter) || 0;
|
totals.phAfter += parseFloat(classified.phAfter) || 0;
|
||||||
|
|
||||||
// Ensure these values are numeric before summing
|
|
||||||
totals.totalOtHrs += parseFloat(this.calculateTotalOtHrs(record)) || 0;
|
totals.totalOtHrs += parseFloat(this.calculateTotalOtHrs(record)) || 0;
|
||||||
totals.totalNdOd += parseFloat(this.calculateNdOdTotal(record)) || 0;
|
totals.totalNdOd += parseFloat(this.calculateNdOdTotal(record)) || 0;
|
||||||
totals.totalRd += parseFloat(this.calculateRdTotal(record)) || 0;
|
totals.totalRd += parseFloat(this.calculateRdTotal(record)) || 0;
|
||||||
@ -494,13 +491,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (let key in totals) {
|
for (let key in totals) {
|
||||||
// Apply standard rounding (toNearestEven, like C# decimal.ToString("N2"))
|
if (key.endsWith('Hrs') || key.endsWith('Amt')) {
|
||||||
// Only apply to total amounts, individual cell values are formatted as needed
|
|
||||||
if (key.endsWith('Hrs') || key.endsWith('Amt')) { // Target hour and amount totals
|
if (key === 'otAmt') {
|
||||||
totals[key] = totals[key].toFixed(2);
|
totals[key] = Math.round(totals[key]).toFixed(2);
|
||||||
} else if (key.endsWith('Break')) { // Break totals remain in minutes
|
} else {
|
||||||
// Already summed as integers/floats, format later if needed for display
|
totals[key] = totals[key].toFixed(2);
|
||||||
} else { // Classified hours (ndAfter, odUnder4 etc.) should be toFixed(2)
|
}
|
||||||
|
} else if (key.endsWith('Break')) {
|
||||||
|
|
||||||
|
} else {
|
||||||
totals[key] = totals[key].toFixed(2);
|
totals[key] = totals[key].toFixed(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -524,11 +524,7 @@
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'currentEditRecord.otDate': function(newDate) {
|
'currentEditRecord.otDate': function(newDate) {
|
||||||
// This watcher will trigger the getDayType computed property indirectly
|
|
||||||
// ensuring the 'Day' input in the modal updates reactively.
|
|
||||||
// No explicit action needed here, as the computed property handles it.
|
|
||||||
},
|
},
|
||||||
// Watch for changes in stationId to update Select2
|
|
||||||
'currentEditRecord.stationId': function(newVal) {
|
'currentEditRecord.stationId': function(newVal) {
|
||||||
if (this.showAirStationDropdown) {
|
if (this.showAirStationDropdown) {
|
||||||
$('#airstationDropdown').val(newVal).trigger('change.select2');
|
$('#airstationDropdown').val(newVal).trigger('change.select2');
|
||||||
@ -536,7 +532,7 @@
|
|||||||
$('#marinestationDropdown').val(newVal).trigger('change.select2');
|
$('#marinestationDropdown').val(newVal).trigger('change.select2');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Watch for changes in airStations and marineStations to update Select2 options
|
|
||||||
airStations: function() {
|
airStations: function() {
|
||||||
if (this.showAirStationDropdown) {
|
if (this.showAirStationDropdown) {
|
||||||
this.initSelect2('#airstationDropdown');
|
this.initSelect2('#airstationDropdown');
|
||||||
@ -549,9 +545,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// REMOVED: Custom rounding function (roundUpToTwoDecimals) as C# uses standard rounding
|
|
||||||
// and we'll use toFixed(2) directly for consistency.
|
|
||||||
|
|
||||||
toggleDescription(index) {
|
toggleDescription(index) {
|
||||||
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
|
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
|
||||||
},
|
},
|
||||||
@ -564,10 +557,10 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
this.otRecords = data.records;
|
this.otRecords = data.records;
|
||||||
// Assuming data.userInfo.rate now holds the Basic Salary
|
|
||||||
this.userInfo = {
|
this.userInfo = {
|
||||||
...data.userInfo,
|
...data.userInfo,
|
||||||
basicSalary: data.userInfo.rate // Assign the API's 'rate' (which is now Basic Salary) to basicSalary
|
basicSalary: data.userInfo.rate
|
||||||
};
|
};
|
||||||
this.isHoU = data.isHoU;
|
this.isHoU = data.isHoU;
|
||||||
this.currentEditRecord.statusId = parseInt(statusId);
|
this.currentEditRecord.statusId = parseInt(statusId);
|
||||||
@ -602,38 +595,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
initSelect2(selector) {
|
initSelect2(selector) {
|
||||||
// Destroy existing Select2 instance if any
|
|
||||||
if ($(selector).data('select2')) {
|
if ($(selector).data('select2')) {
|
||||||
$(selector).select2('destroy');
|
$(selector).select2('destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Select2
|
|
||||||
$(selector).select2({
|
$(selector).select2({
|
||||||
dropdownParent: $('#editOtModal') // Ensure dropdown is within the modal boundaries
|
dropdownParent: $('#editOtModal')
|
||||||
}).on('change', (e) => {
|
}).on('change', (e) => {
|
||||||
// Update Vue data when Select2 value changes
|
|
||||||
this.currentEditRecord.stationId = $(e.currentTarget).val();
|
this.currentEditRecord.stationId = $(e.currentTarget).val();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the initial value if currentEditRecord.stationId is already set
|
|
||||||
// This needs to be called after options are populated and Select2 is initialized
|
|
||||||
if (this.currentEditRecord.stationId) {
|
if (this.currentEditRecord.stationId) {
|
||||||
$(selector).val(this.currentEditRecord.stationId).trigger('change.select2');
|
$(selector).val(this.currentEditRecord.stationId).trigger('change.select2');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// HRP = ORP / 8
|
|
||||||
calculateHrp() {
|
calculateHrp() {
|
||||||
const orp = parseFloat(this.calculateOrp()); // Get ORP from the calculated value
|
const orp = parseFloat(this.calculateOrp());
|
||||||
if (isNaN(orp) || orp <= 0) return 'N/A';
|
if (isNaN(orp) || orp <= 0) return 'N/A';
|
||||||
return (orp / 8).toFixed(2);
|
return (orp / 8).toFixed(2);
|
||||||
},
|
},
|
||||||
// ORP = Basic Salary / 26
|
|
||||||
calculateOrp() {
|
calculateOrp() {
|
||||||
const basicSalary = parseFloat(this.userInfo.basicSalary);
|
const basicSalary = parseFloat(this.userInfo.basicSalary);
|
||||||
if (isNaN(basicSalary) || basicSalary <= 0) return 'N/A';
|
if (isNaN(basicSalary) || basicSalary <= 0) return 'N/A';
|
||||||
return (basicSalary / 26).toFixed(2);
|
return (basicSalary / 26).toFixed(2);
|
||||||
},
|
},
|
||||||
// Basic Salary will now directly come from userInfo.basicSalary
|
|
||||||
calculateBasicSalary() {
|
calculateBasicSalary() {
|
||||||
const basicSalary = parseFloat(this.userInfo.basicSalary);
|
const basicSalary = parseFloat(this.userInfo.basicSalary);
|
||||||
if (isNaN(basicSalary) || basicSalary <= 0) return 'N/A';
|
if (isNaN(basicSalary) || basicSalary <= 0) return 'N/A';
|
||||||
@ -661,20 +649,18 @@
|
|||||||
const minutes = parseFloat(breakValue || 0);
|
const minutes = parseFloat(breakValue || 0);
|
||||||
if (isNaN(minutes) || minutes <= 0) return showZero ? '0:00' : '';
|
if (isNaN(minutes) || minutes <= 0) return showZero ? '0:00' : '';
|
||||||
const hrs = Math.floor(minutes / 60);
|
const hrs = Math.floor(minutes / 60);
|
||||||
const mins = Math.round(minutes % 60); // Round minutes to nearest integer
|
const mins = Math.round(minutes % 60);
|
||||||
return `${hrs.toString().padStart(1, '0')}:${mins.toString().padStart(2, '0')}`;
|
return `${hrs.toString().padStart(1, '0')}:${mins.toString().padStart(2, '0')}`;
|
||||||
},
|
},
|
||||||
// New helper to format decimal hours into H:MM string for display columns
|
|
||||||
formatTimeFromDecimal(decimalHours) {
|
formatTimeFromDecimal(decimalHours) {
|
||||||
if (decimalHours === null || isNaN(decimalHours) || decimalHours <= 0) return '';
|
if (decimalHours === null || isNaN(decimalHours) || decimalHours <= 0) return '';
|
||||||
const totalMinutes = Math.round(decimalHours * 60); // Convert to minutes and round
|
const totalMinutes = Math.round(decimalHours * 60);
|
||||||
const hours = Math.floor(totalMinutes / 60);
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
const minutes = totalMinutes % 60;
|
const minutes = totalMinutes % 60;
|
||||||
return `${hours}:${minutes.toString().padStart(2, '0')}`;
|
return `${hours}:${minutes.toString().padStart(2, '0')}`;
|
||||||
},
|
},
|
||||||
// Renamed from convertTimeToDecimal to better reflect its purpose: parsing "HH:MM" to decimal hours
|
|
||||||
parseTimeSpanToDecimalHours(timeStr) {
|
parseTimeSpanToDecimalHours(timeStr) {
|
||||||
if (!timeStr || typeof timeStr !== 'string') return 0; // Return 0 for empty/invalid
|
if (!timeStr || typeof timeStr !== 'string') return 0;
|
||||||
const parts = timeStr.split(':');
|
const parts = timeStr.split(':');
|
||||||
if (parts.length !== 2) return 0;
|
if (parts.length !== 2) return 0;
|
||||||
|
|
||||||
@ -685,7 +671,6 @@
|
|||||||
|
|
||||||
return hours + (minutes / 60);
|
return hours + (minutes / 60);
|
||||||
},
|
},
|
||||||
// This function now returns decimal hours, consistent with C# `TotalHours`
|
|
||||||
calculateRawDuration(fromTime, toTime, breakMins) {
|
calculateRawDuration(fromTime, toTime, breakMins) {
|
||||||
if (!fromTime || !toTime) return 0;
|
if (!fromTime || !toTime) return 0;
|
||||||
|
|
||||||
@ -699,25 +684,13 @@
|
|||||||
const breakMinutes = parseFloat(breakMins || 0);
|
const breakMinutes = parseFloat(breakMins || 0);
|
||||||
|
|
||||||
let totalMinutes = to - from - breakMinutes;
|
let totalMinutes = to - from - breakMinutes;
|
||||||
if (totalMinutes < 0) totalMinutes += 24 * 60; // Handle overnight OT
|
if (totalMinutes < 0) totalMinutes += 24 * 60;
|
||||||
|
|
||||||
return Math.max(0, totalMinutes / 60); // Return hours as decimal
|
return Math.max(0, totalMinutes / 60);
|
||||||
},
|
},
|
||||||
|
|
||||||
// calculateOfficeOtHours and calculateAfterOfficeOtHours now call the new formatTimeFromDecimal
|
|
||||||
// to display H:MM format, but internally use calculateRawDuration which returns decimal hours.
|
|
||||||
// The C# version also returns H:MM strings.
|
|
||||||
// This also ensures 'OT Hrs (Office Hour)' column is blank for Normal Day.
|
|
||||||
// (Original C# `CalculateOfficeOtHours` also had the `totalMinutes = Math.Max(0, totalMinutes);` safeguard.)
|
|
||||||
// (Original C# `CalculateAfterOfficeOtHours` also had the `totalMinutes = Math.Max(0, totalMinutes);` safeguard.)
|
|
||||||
|
|
||||||
// No changes needed for these methods themselves, as they now correctly use `formatTimeFromDecimal`
|
|
||||||
// and `calculateRawDuration`.
|
|
||||||
// The `if (record.dayType === 'Normal Day') { return ''; }` part remains for display purposes,
|
|
||||||
// matching the C# PDF's decision to show empty for normal day office OT.
|
|
||||||
calculateOfficeOtHours(record) {
|
calculateOfficeOtHours(record) {
|
||||||
if (!record.officeFrom || !record.officeTo) return '';
|
if (!record.officeFrom || !record.officeTo) return '';
|
||||||
if (record.dayType === 'Normal Day') { // Added as per PDF logic
|
if (record.dayType === 'Normal Day') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const totalHours = this.calculateRawDuration(record.officeFrom, record.officeTo, record.officeBreak);
|
const totalHours = this.calculateRawDuration(record.officeFrom, record.officeTo, record.officeBreak);
|
||||||
@ -733,9 +706,6 @@
|
|||||||
classifyOt(record) {
|
classifyOt(record) {
|
||||||
const officeHrs = this.calculateRawDuration(record.officeFrom, record.officeTo, record.officeBreak);
|
const officeHrs = this.calculateRawDuration(record.officeFrom, record.officeTo, record.officeBreak);
|
||||||
const afterHrs = this.calculateRawDuration(record.afterFrom, record.afterTo, record.afterBreak);
|
const afterHrs = this.calculateRawDuration(record.afterFrom, record.afterTo, record.afterBreak);
|
||||||
|
|
||||||
// Changed toFixed(2) to directly return number if num > 0, then toFixed(2) when used in table cells
|
|
||||||
// This aligns with C# returning decimal and then formatting later.
|
|
||||||
const toFixedOrEmpty = (num) => num > 0 ? num.toFixed(2) : '';
|
const toFixedOrEmpty = (num) => num > 0 ? num.toFixed(2) : '';
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
@ -753,21 +723,19 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Off Day':
|
case 'Off Day':
|
||||||
case 'Weekend': // Keep 'Weekend' here, it correctly maps to Off Day/Rest Day via getDayType
|
case 'Weekend':
|
||||||
if (officeHrs > 0) {
|
if (officeHrs > 0) {
|
||||||
// C# puts entire officeHrs into odAfter/rdAfter if > 8.
|
|
||||||
// It seems the column mapping implies odUnder4 (<=4), odBetween4And8 (>4, <=8), odAfter (>8) for office hrs,
|
|
||||||
// AND also after office hrs go to odAfter.
|
|
||||||
if (officeHrs <= 4) {
|
if (officeHrs <= 4) {
|
||||||
result.odUnder4 = toFixedOrEmpty(officeHrs);
|
result.odUnder4 = toFixedOrEmpty(officeHrs);
|
||||||
} else if (officeHrs <= 8) { // C# uses else if here, covering > 4 and <= 8
|
} else if (officeHrs <= 8) {
|
||||||
result.odBetween4And8 = toFixedOrEmpty(officeHrs);
|
result.odBetween4And8 = toFixedOrEmpty(officeHrs);
|
||||||
} else { // For officeHrs > 8. C# puts all officeHrs into odAfter
|
} else {
|
||||||
result.odAfter = toFixedOrEmpty(officeHrs); // Changed to match C# logic directly
|
result.odAfter = toFixedOrEmpty(officeHrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// C# then adds afterHrs to odAfter.
|
|
||||||
result.odAfter = toFixedOrEmpty( (parseFloat(result.odAfter) || 0) + afterHrs ); // Ensure numeric addition
|
result.odAfter = toFixedOrEmpty( (parseFloat(result.odAfter) || 0) + afterHrs );
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -775,28 +743,27 @@
|
|||||||
if (officeHrs > 0) {
|
if (officeHrs > 0) {
|
||||||
if (officeHrs <= 4) {
|
if (officeHrs <= 4) {
|
||||||
result.rdUnder4 = toFixedOrEmpty(officeHrs);
|
result.rdUnder4 = toFixedOrEmpty(officeHrs);
|
||||||
} else if (officeHrs <= 8) { // C# uses else if here, covering > 4 and <= 8
|
} else if (officeHrs <= 8) {
|
||||||
result.rdBetween4And8 = toFixedOrEmpty(officeHrs);
|
result.rdBetween4And8 = toFixedOrEmpty(officeHrs);
|
||||||
} else { // For officeHrs > 8. C# puts all officeHrs into rdAfter
|
} else {
|
||||||
result.rdAfter = toFixedOrEmpty(officeHrs); // Changed to match C# logic directly
|
result.rdAfter = toFixedOrEmpty(officeHrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// C# then adds afterHrs to rdAfter.
|
|
||||||
result.rdAfter = toFixedOrEmpty( (parseFloat(result.rdAfter) || 0) + afterHrs ); // Ensure numeric addition
|
result.rdAfter = toFixedOrEmpty( (parseFloat(result.rdAfter) || 0) + afterHrs );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Public Holiday':
|
case 'Public Holiday':
|
||||||
// C# logic: if officeHrs <= 8, it's phUnder8. ELSE (if officeHrs > 8) it's phAfter.
|
|
||||||
// Then afterHrs are added to phAfter.
|
|
||||||
if (officeHrs > 0) {
|
if (officeHrs > 0) {
|
||||||
if (officeHrs <= 8) {
|
if (officeHrs <= 8) {
|
||||||
result.phUnder8 = toFixedOrEmpty(officeHrs);
|
result.phUnder8 = toFixedOrEmpty(officeHrs);
|
||||||
} else { // officeHrs > 8
|
} else {
|
||||||
result.phAfter = toFixedOrEmpty(officeHrs); // Changed to match C# logic
|
result.phAfter = toFixedOrEmpty(officeHrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// C# then adds afterHrs to phAfter.
|
|
||||||
result.phAfter = toFixedOrEmpty( (parseFloat(result.phAfter) || 0) + afterHrs ); // Ensure numeric addition
|
result.phAfter = toFixedOrEmpty( (parseFloat(result.phAfter) || 0) + afterHrs );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -811,7 +778,7 @@
|
|||||||
if (!isNaN(value)) total += value;
|
if (!isNaN(value)) total += value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return total.toFixed(2); // Keep .toFixed(2) for display
|
return total.toFixed(2);
|
||||||
},
|
},
|
||||||
calculateNdOdTotal(record) {
|
calculateNdOdTotal(record) {
|
||||||
const classified = this.classifyOt(record);
|
const classified = this.classifyOt(record);
|
||||||
@ -857,34 +824,22 @@
|
|||||||
let amountAfter = 0;
|
let amountAfter = 0;
|
||||||
|
|
||||||
if (officeHours > 0) {
|
if (officeHours > 0) {
|
||||||
if (otType === 'Off Day' || otType === 'Rest Day') { // Removed 'Weekend' here, GetDayType handles it
|
if (otType === 'Off Day' || otType === 'Rest Day') {
|
||||||
if (officeHours <= 4) {
|
if (officeHours <= 4) {
|
||||||
amountOffice = 0.5 * orp;
|
amountOffice = 0.5 * orp;
|
||||||
} else if (officeHours <= 8) {
|
} else if (officeHours <= 8) {
|
||||||
amountOffice = 1 * orp;
|
amountOffice = 1 * orp;
|
||||||
}
|
}
|
||||||
// C# code logic does NOT have an else if (officeHours > 8) here.
|
|
||||||
// If officeHours > 8, it would fall through and use the last matching condition, which for Off/Rest day is `1 * orp;`
|
|
||||||
// So, this is the C# logic for office hours on Off/Rest days:
|
|
||||||
// The C# code doesn't explicitly state what happens for officeHours > 8 on Off/Rest Day.
|
|
||||||
// Based on the C# code, the `CalculateOtAmount` method for 'Off Day' and 'Rest Day' has:
|
|
||||||
// `if (officeHours <= 4) { amountOffice = 0.5m * orp; }`
|
|
||||||
// `else if (officeHours > 4 && officeHours <= 8) { amountOffice = 1 * orp; }`
|
|
||||||
// There is *no `else` branch* for `officeHours > 8`. This means if `officeHours` is 9, it would still get `1 * orp`.
|
|
||||||
// This indicates that the PDF's logic currently only defines fixed amounts for <=4h and >4h<=8h.
|
|
||||||
// To perfectly match PDF, the JS should also stop calculating `amountOffice` after `1 * orp` for >8 hours on Off/Rest.
|
|
||||||
// This confirms the previous analysis: C# does not have a special rule for `officeHours > 8` on Off/Rest days.
|
|
||||||
} else if (otType === 'Public Holiday') {
|
} else if (otType === 'Public Holiday') {
|
||||||
// C# logic: `amountOffice = 2 * orp;` without conditions for PH Office Hours.
|
amountOffice = 2 * orp;
|
||||||
// This means it's a fixed 2 * ORP regardless of hours.
|
|
||||||
amountOffice = 2 * orp; // Match C# logic directly
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (afterOfficeHours > 0) {
|
if (afterOfficeHours > 0) {
|
||||||
switch (otType) {
|
switch (otType) {
|
||||||
case 'Normal Day':
|
case 'Normal Day':
|
||||||
case 'Off Day': // Changed to match C# logic (single case for ND and Off Day after-office hours)
|
case 'Off Day':
|
||||||
amountAfter = 1.5 * hrp * afterOfficeHours;
|
amountAfter = 1.5 * hrp * afterOfficeHours;
|
||||||
break;
|
break;
|
||||||
case 'Rest Day':
|
case 'Rest Day':
|
||||||
@ -897,9 +852,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalAmount = amountOffice + amountAfter;
|
const totalAmount = amountOffice + amountAfter;
|
||||||
return totalAmount.toFixed(2); // Use toFixed(2) for standard rounding
|
return totalAmount.toFixed(2);
|
||||||
},
|
},
|
||||||
// --- NEW METHOD FOR ROUNDING MINUTES ---
|
|
||||||
roundMinutes(fieldName) {
|
roundMinutes(fieldName) {
|
||||||
const timeValue = this.currentEditRecord[fieldName];
|
const timeValue = this.currentEditRecord[fieldName];
|
||||||
if (!timeValue) return;
|
if (!timeValue) return;
|
||||||
@ -909,14 +863,12 @@
|
|||||||
|
|
||||||
if (minutes >= 45 || minutes < 15) {
|
if (minutes >= 45 || minutes < 15) {
|
||||||
roundedMinutes = 0;
|
roundedMinutes = 0;
|
||||||
if (minutes >= 45) { // If minutes are 45-59, round up to next hour (00 minutes)
|
if (minutes >= 45) {
|
||||||
let currentHour = hours;
|
let currentHour = hours;
|
||||||
currentHour = (currentHour + 1) % 24; // Handle 23:xx rounding to 00:00
|
currentHour = (currentHour + 1) % 24;
|
||||||
this.currentEditRecord[fieldName] = `${String(currentHour).padStart(2, '0')}:00`;
|
this.currentEditRecord[fieldName] = `${String(currentHour).padStart(2, '0')}:00`;
|
||||||
// No need to call validateTimeFields here, as it's called after the if condition
|
this.validateTimeFields();
|
||||||
// and it will be called for all cases after rounding.
|
return;
|
||||||
this.validateTimeFields(); // Still call here to update immediately if input changes
|
|
||||||
return; // Exit after setting
|
|
||||||
}
|
}
|
||||||
} else if (minutes >= 15 && minutes < 45) {
|
} else if (minutes >= 15 && minutes < 45) {
|
||||||
roundedMinutes = 30;
|
roundedMinutes = 30;
|
||||||
@ -924,10 +876,8 @@
|
|||||||
|
|
||||||
this.currentEditRecord[fieldName] = `${String(hours).padStart(2, '0')}:${String(roundedMinutes).padStart(2, '0')}`;
|
this.currentEditRecord[fieldName] = `${String(hours).padStart(2, '0')}:${String(roundedMinutes).padStart(2, '0')}`;
|
||||||
|
|
||||||
// Trigger validation after rounding
|
|
||||||
this.validateTimeFields();
|
this.validateTimeFields();
|
||||||
},
|
},
|
||||||
// --- END NEW METHOD ---
|
|
||||||
editRecord(id) {
|
editRecord(id) {
|
||||||
if (this.hasApproverActedLocal) {
|
if (this.hasApproverActedLocal) {
|
||||||
alert("You cannot edit this record as you have already approved/rejected this OT submission.");
|
alert("You cannot edit this record as you have already approved/rejected this OT submission.");
|
||||||
@ -957,7 +907,6 @@
|
|||||||
otDays: recordToEdit.otDays
|
otDays: recordToEdit.otDays
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize Select2 after Vue has updated the DOM with the new data
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.showAirStationDropdown) {
|
if (this.showAirStationDropdown) {
|
||||||
this.initSelect2('#airstationDropdown');
|
this.initSelect2('#airstationDropdown');
|
||||||
@ -975,7 +924,6 @@
|
|||||||
|
|
||||||
async submitEdit() {
|
async submitEdit() {
|
||||||
try {
|
try {
|
||||||
// Validate time ranges
|
|
||||||
const hasOfficeHours = this.currentEditRecord.officeFrom && this.currentEditRecord.officeTo;
|
const hasOfficeHours = this.currentEditRecord.officeFrom && this.currentEditRecord.officeTo;
|
||||||
const hasAfterHours = this.currentEditRecord.afterFrom && this.currentEditRecord.afterTo;
|
const hasAfterHours = this.currentEditRecord.afterFrom && this.currentEditRecord.afterTo;
|
||||||
|
|
||||||
@ -1053,23 +1001,20 @@
|
|||||||
return 'Public Holiday';
|
return 'Public Holiday';
|
||||||
}
|
}
|
||||||
|
|
||||||
// WeekendId 1: Friday (5) & Saturday (6)
|
|
||||||
// WeekendId 2: Saturday (6) & Sunday (0)
|
|
||||||
if (this.userState.weekendId === 1) {
|
if (this.userState.weekendId === 1) {
|
||||||
if (dayOfWeek === 5) return 'Off Day'; // Friday
|
if (dayOfWeek === 5) return 'Off Day';
|
||||||
if (dayOfWeek === 6) return 'Rest Day'; // Saturday
|
if (dayOfWeek === 6) return 'Rest Day';
|
||||||
else return 'Normal Day';
|
else return 'Normal Day';
|
||||||
} else if (this.userState.weekendId === 2) {
|
} else if (this.userState.weekendId === 2) {
|
||||||
if (dayOfWeek === 6) return 'Off Day'; // Saturday
|
if (dayOfWeek === 6) return 'Off Day';
|
||||||
if (dayOfWeek === 0) return 'Rest Day'; // Sunday
|
if (dayOfWeek === 0) return 'Rest Day';
|
||||||
else return 'Normal Day';
|
else return 'Normal Day';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Normal Day'; // Default if not a public holiday or weekend
|
return 'Normal Day';
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateModalTotalOtHrs() {
|
calculateModalTotalOtHrs() {
|
||||||
// Use calculateRawDuration which returns decimal hours
|
|
||||||
const officeHrsDecimal = this.calculateRawDuration(
|
const officeHrsDecimal = this.calculateRawDuration(
|
||||||
this.currentEditRecord.officeFrom,
|
this.currentEditRecord.officeFrom,
|
||||||
this.currentEditRecord.officeTo,
|
this.currentEditRecord.officeTo,
|
||||||
@ -1082,7 +1027,7 @@
|
|||||||
this.currentEditRecord.afterBreak
|
this.currentEditRecord.afterBreak
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalMinutes = Math.round((officeHrsDecimal + afterHrsDecimal) * 60); // Total minutes from decimal hours
|
const totalMinutes = Math.round((officeHrsDecimal + afterHrsDecimal) * 60);
|
||||||
const totalHours = Math.floor(totalMinutes / 60);
|
const totalHours = Math.floor(totalMinutes / 60);
|
||||||
const remainingMinutes = totalMinutes % 60;
|
const remainingMinutes = totalMinutes % 60;
|
||||||
|
|
||||||
@ -1238,21 +1183,20 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
validateTimeRangeForSubmission(fromTime, toTime, label) {
|
validateTimeRangeForSubmission(fromTime, toTime, label) {
|
||||||
if (!fromTime || !toTime) return true; // Handled by outer checks
|
if (!fromTime || !toTime) return true;
|
||||||
|
|
||||||
const start = this.parseTime(fromTime);
|
const start = this.parseTime(fromTime);
|
||||||
const end = this.parseTime(toTime);
|
const end = this.parseTime(toTime);
|
||||||
|
|
||||||
const minAllowedFromMinutesForMidnightTo = 16 * 60 + 30; // 4:30 PM
|
const minAllowedFromMinutesForMidnightTo = 16 * 60 + 30;
|
||||||
const maxAllowedFromMinutesForMidnightTo = 23 * 60 + 30; // 11:30 PM
|
const maxAllowedFromMinutesForMidnightTo = 23 * 60 + 30;
|
||||||
const startMinutes = start.hours * 60 + start.minutes;
|
const startMinutes = start.hours * 60 + start.minutes;
|
||||||
|
|
||||||
if (end.hours === 0 && end.minutes === 0) { // If 'To' is 00:00 (midnight)
|
if (end.hours === 0 && end.minutes === 0) {
|
||||||
if (fromTime === "00:00") {
|
if (fromTime === "00:00") {
|
||||||
alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`);
|
alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// This is the specific rule: if 'To' is 00:00, 'From' must be within 4:30 PM and 11:30 PM
|
|
||||||
if (startMinutes < minAllowedFromMinutesForMidnightTo || startMinutes > maxAllowedFromMinutesForMidnightTo) {
|
if (startMinutes < minAllowedFromMinutesForMidnightTo || startMinutes > maxAllowedFromMinutesForMidnightTo) {
|
||||||
alert(`Invalid ${label} 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.`);
|
alert(`Invalid ${label} 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.`);
|
||||||
return false;
|
return false;
|
||||||
@ -1264,7 +1208,6 @@
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
validateTimeFields() {
|
validateTimeFields() {
|
||||||
// This will validate the fields whenever they change
|
|
||||||
const hasOfficeHours = this.currentEditRecord.officeFrom && this.currentEditRecord.officeTo;
|
const hasOfficeHours = this.currentEditRecord.officeFrom && this.currentEditRecord.officeTo;
|
||||||
const hasAfterHours = this.currentEditRecord.afterFrom && this.currentEditRecord.afterTo;
|
const hasAfterHours = this.currentEditRecord.afterFrom && this.currentEditRecord.afterTo;
|
||||||
|
|
||||||
@ -1288,8 +1231,6 @@
|
|||||||
await this.fetchStations();
|
await this.fetchStations();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// Initialize Select2 when the component is mounted
|
|
||||||
// These will apply to the elements if they are visible initially based on userInfo
|
|
||||||
if (this.showAirStationDropdown) {
|
if (this.showAirStationDropdown) {
|
||||||
this.initSelect2('#airstationDropdown');
|
this.initSelect2('#airstationDropdown');
|
||||||
}
|
}
|
||||||
@ -1297,12 +1238,9 @@
|
|||||||
this.initSelect2('#marinestationDropdown');
|
this.initSelect2('#marinestationDropdown');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-initialize Select2 when the modal is shown
|
|
||||||
var editModalElement = document.getElementById('editOtModal');
|
var editModalElement = document.getElementById('editOtModal');
|
||||||
if (editModalElement) {
|
if (editModalElement) {
|
||||||
editModalElement.addEventListener('shown.bs.modal', () => {
|
editModalElement.addEventListener('shown.bs.modal', () => {
|
||||||
// Re-initialize Select2 and set its value when the modal becomes visible
|
|
||||||
// This ensures Select2 is active and displays the correct value when the modal opens
|
|
||||||
if (this.showAirStationDropdown) {
|
if (this.showAirStationDropdown) {
|
||||||
this.initSelect2('#airstationDropdown');
|
this.initSelect2('#airstationDropdown');
|
||||||
$('#airstationDropdown').val(this.currentEditRecord.stationId).trigger('change.select2');
|
$('#airstationDropdown').val(this.currentEditRecord.stationId).trigger('change.select2');
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<h1 class="font-light text-white">
|
<h1 class="font-light text-white">
|
||||||
<i class="mdi mdi-currency-usd"></i>
|
<i class="mdi mdi-currency-usd"></i>
|
||||||
</h1>
|
</h1>
|
||||||
<h6 class="text-white">Rate</h6>
|
<h6 class="text-white">Salary</h6>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
#ApprovalFlowTable th,
|
#ApprovalFlowTable th,
|
||||||
#ApprovalFlowTable td {
|
#ApprovalFlowTable td {
|
||||||
background-color: white !important; /* Ensure no transparency from Bootstrap */
|
background-color: white !important;
|
||||||
color: #323;
|
color: #323;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Rate">
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Rate">
|
||||||
<div class="box bg-success text-center">
|
<div class="box bg-success text-center">
|
||||||
<h1 class="font-light text-white"><i class="mdi mdi-currency-usd"></i></h1>
|
<h1 class="font-light text-white"><i class="mdi mdi-currency-usd"></i></h1>
|
||||||
<h6 class="text-white">Rate</h6>
|
<h6 class="text-white">Salary</h6>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -383,7 +383,7 @@
|
|||||||
if (!this.stateUserList.length) {
|
if (!this.stateUserList.length) {
|
||||||
this.fetchUsersState().then(() => {
|
this.fetchUsersState().then(() => {
|
||||||
this.initiateStateTable();
|
this.initiateStateTable();
|
||||||
this.fetchApprovalFlows(); // Ensure approval flows are fetched on tab change
|
this.fetchApprovalFlows();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.initiateStateTable();
|
this.initiateStateTable();
|
||||||
@ -408,8 +408,8 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(successMessage);
|
alert(successMessage);
|
||||||
clearCallback(); // Clears form selections
|
clearCallback();
|
||||||
await fetchCallback(); // Fetches the updated data
|
await fetchCallback();
|
||||||
} else {
|
} else {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
alert(errorData.message || "Failed to update. Please try again.");
|
alert(errorData.message || "Failed to update. Please try again.");
|
||||||
@ -439,7 +439,6 @@
|
|||||||
this.userList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin");
|
this.userList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin");
|
||||||
console.log("Fetched User data", this.userList);
|
console.log("Fetched User data", this.userList);
|
||||||
|
|
||||||
// Reinitialize DataTable with updated data
|
|
||||||
this.initiateTable();
|
this.initiateTable();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -464,7 +463,6 @@
|
|||||||
this.stateUserList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin");
|
this.stateUserList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin");
|
||||||
console.log("Fetched state users", this.stateUserList);
|
console.log("Fetched state users", this.stateUserList);
|
||||||
|
|
||||||
// Reinitialize the state table to reflect updated data
|
|
||||||
this.initiateStateTable();
|
this.initiateStateTable();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -569,8 +567,8 @@
|
|||||||
this.selectedUsersState,
|
this.selectedUsersState,
|
||||||
this.selectedStateAll,
|
this.selectedStateAll,
|
||||||
"State updated successfully!",
|
"State updated successfully!",
|
||||||
() => {}, // Don't clear yet
|
() => {},
|
||||||
() => {}, // Don't fetch yet
|
() => {},
|
||||||
"StateId"
|
"StateId"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -602,7 +600,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After updates, refresh and clear
|
|
||||||
await this.fetchUsersState();
|
await this.fetchUsersState();
|
||||||
this.initiateStateTable();
|
this.initiateStateTable();
|
||||||
this.clearAllSelectionsStateFlow();
|
this.clearAllSelectionsStateFlow();
|
||||||
@ -643,8 +640,8 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/OvertimeAPI/GetApprovalFlowList');
|
const response = await fetch('/OvertimeAPI/GetApprovalFlowList');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.approvalFlowList = data; // Update the data correctly
|
this.approvalFlowList = data;
|
||||||
console.log('Fetched approval flows:', this.approvalFlowList); // Verify data
|
console.log('Fetched approval flows:', this.approvalFlowList);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching approval flows:', error);
|
console.error('Error fetching approval flows:', error);
|
||||||
}
|
}
|
||||||
@ -690,7 +687,6 @@
|
|||||||
|
|
||||||
async deleteApprovalFlow(approvalId) {
|
async deleteApprovalFlow(approvalId) {
|
||||||
if (!approvalId) {
|
if (!approvalId) {
|
||||||
// This handles the "undefined" ID case more gracefully
|
|
||||||
console.error("No approval ID provided for deletion.");
|
console.error("No approval ID provided for deletion.");
|
||||||
alert("An error occurred: No approval flow selected for deletion.");
|
alert("An error occurred: No approval flow selected for deletion.");
|
||||||
return;
|
return;
|
||||||
@ -707,22 +703,17 @@
|
|||||||
alert("Approval flow deleted successfully.");
|
alert("Approval flow deleted successfully.");
|
||||||
await this.fetchApprovalFlows();
|
await this.fetchApprovalFlows();
|
||||||
} else {
|
} else {
|
||||||
// Parse the error response from the server
|
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
const errorMessage = errorData.message || "Failed to delete approval flow.";
|
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
|
if (response.status === 400) {
|
||||||
// Only log to console for unexpected server errors (e.g., 500 status codes)
|
|
||||||
if (response.status === 400) { // Check for Bad Request specifically
|
|
||||||
alert(`Error: ${errorMessage}`);
|
alert(`Error: ${errorMessage}`);
|
||||||
} else {
|
} else {
|
||||||
// For other errors (e.g., 500 Internal Server Error), still log to console
|
|
||||||
console.error("Error deleting flow:", errorMessage);
|
console.error("Error deleting flow:", errorMessage);
|
||||||
alert(`Error: ${errorMessage}`);
|
alert(`Error: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// This catch block handles network errors or errors that prevent a valid response
|
|
||||||
console.error("An unexpected error occurred during deletion:", error);
|
console.error("An unexpected error occurred during deletion:", error);
|
||||||
alert(`An unexpected error occurred: ${error.message || "Please try again."}`);
|
alert(`An unexpected error occurred: ${error.message || "Please try again."}`);
|
||||||
}
|
}
|
||||||
@ -749,13 +740,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openEditModal(flow) {
|
openEditModal(flow) {
|
||||||
// Set the editFlow to the selected flow
|
|
||||||
this.editFlow = { ...flow };
|
this.editFlow = { ...flow };
|
||||||
|
|
||||||
// Optionally, log the data to ensure it's correct
|
|
||||||
console.log(this.editFlow);
|
console.log(this.editFlow);
|
||||||
|
|
||||||
// Show the modal
|
|
||||||
this.fetchAllUsers().then(() => {
|
this.fetchAllUsers().then(() => {
|
||||||
const modal = new bootstrap.Modal(document.getElementById('editApprovalModal'));
|
const modal = new bootstrap.Modal(document.getElementById('editApprovalModal'));
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
<div class="card-header bg-white text-center">
|
<div class="card-header bg-white text-center">
|
||||||
<h3 class="rate-heading">UPDATE SALARY</h3>
|
<h3 class="rate-heading">UPDATE SALARY</h3>
|
||||||
</div>
|
</div>
|
||||||
@* Enter Rate *@
|
@* Enter Salary *@
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<label for="rate" class="mb-0">Salary</label>
|
<label for="rate" class="mb-0">Salary</label>
|
||||||
|
|||||||
@ -142,7 +142,7 @@
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchUpdateDates();
|
this.fetchUpdateDates();
|
||||||
this.checkIncompleteSettings(); // Call the new method on mount
|
this.checkIncompleteSettings();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchUpdateDates() {
|
async fetchUpdateDates() {
|
||||||
@ -172,7 +172,6 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.hasIncompleteSettings) {
|
if (data.hasIncompleteSettings) {
|
||||||
// Access the new property 'numberOfIncompleteUsers'
|
|
||||||
const numberOfStaff = data.numberOfIncompleteUsers;
|
const numberOfStaff = data.numberOfIncompleteUsers;
|
||||||
let alertMessage = `Action Required!\n\nThere are ${numberOfStaff} staff with incomplete Rate / Flexi Hour / Approval Flow / State.`;
|
let alertMessage = `Action Required!\n\nThere are ${numberOfStaff} staff with incomplete Rate / Flexi Hour / Approval Flow / State.`;
|
||||||
alert(alertMessage);
|
alert(alertMessage);
|
||||||
|
|||||||
@ -170,8 +170,8 @@
|
|||||||
return { label, value: totalMinutes };
|
return { label, value: totalMinutes };
|
||||||
}),
|
}),
|
||||||
previousPage: document.referrer,
|
previousPage: document.referrer,
|
||||||
returnMonth: null, // Add this
|
returnMonth: null,
|
||||||
returnYear: null, // Add this
|
returnYear: null,
|
||||||
validationErrors: {
|
validationErrors: {
|
||||||
otDate: "",
|
otDate: "",
|
||||||
stationId: "",
|
stationId: "",
|
||||||
@ -192,7 +192,7 @@
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const overtimeId = urlParams.get('overtimeId');
|
const overtimeId = urlParams.get('overtimeId');
|
||||||
// Capture month and year from URL parameters
|
|
||||||
this.returnMonth = urlParams.get('month');
|
this.returnMonth = urlParams.get('month');
|
||||||
this.returnYear = urlParams.get('year');
|
this.returnYear = urlParams.get('year');
|
||||||
|
|
||||||
@ -225,7 +225,7 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initializeSelect2Dropdowns() {
|
initializeSelect2Dropdowns() {
|
||||||
// Destroy any existing Select2 instances to prevent issues on re-initialization
|
|
||||||
if ($('#airstationDropdown').data('select2')) {
|
if ($('#airstationDropdown').data('select2')) {
|
||||||
$('#airstationDropdown').select2('destroy');
|
$('#airstationDropdown').select2('destroy');
|
||||||
}
|
}
|
||||||
@ -396,9 +396,9 @@
|
|||||||
this.totalBreakHours = `${totalBreakHours} hr ${totalBreakMinutes} min`;
|
this.totalBreakHours = `${totalBreakHours} hr ${totalBreakMinutes} min`;
|
||||||
this.updateDayType();
|
this.updateDayType();
|
||||||
},
|
},
|
||||||
// Update the updateTime method to include midnight validation
|
|
||||||
updateTime(fieldName) {
|
updateTime(fieldName) {
|
||||||
// Round time first
|
|
||||||
if (fieldName === 'officeFrom') {
|
if (fieldName === 'officeFrom') {
|
||||||
this.editForm.officeFrom = this.roundToNearest30(this.editForm.officeFrom);
|
this.editForm.officeFrom = this.roundToNearest30(this.editForm.officeFrom);
|
||||||
} else if (fieldName === 'officeTo') {
|
} else if (fieldName === 'officeTo') {
|
||||||
@ -409,12 +409,10 @@
|
|||||||
this.editForm.afterTo = this.roundToNearest30(this.editForm.afterTo);
|
this.editForm.afterTo = this.roundToNearest30(this.editForm.afterTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate time ranges
|
|
||||||
this.validateTimeRanges();
|
this.validateTimeRanges();
|
||||||
this.calculateOTAndBreak();
|
this.calculateOTAndBreak();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Add new validation method
|
|
||||||
validateTimeRanges() {
|
validateTimeRanges() {
|
||||||
const validateRange = (fromTime, toTime, label) => {
|
const validateRange = (fromTime, toTime, label) => {
|
||||||
if (!fromTime || !toTime) return true;
|
if (!fromTime || !toTime) return true;
|
||||||
@ -422,12 +420,12 @@
|
|||||||
const start = this.parseTime(fromTime);
|
const start = this.parseTime(fromTime);
|
||||||
const end = this.parseTime(toTime);
|
const end = this.parseTime(toTime);
|
||||||
|
|
||||||
const minAllowedFromMinutes = 16 * 60 + 30; // 4:30 PM
|
const minAllowedFromMinutes = 16 * 60 + 30;
|
||||||
const maxAllowedFromMinutes = 23 * 60 + 30; // 11:30 PM
|
const maxAllowedFromMinutes = 23 * 60 + 30;
|
||||||
const startMinutes = start.hours * 60 + start.minutes;
|
const startMinutes = start.hours * 60 + start.minutes;
|
||||||
const endMinutes = end.hours * 60 + end.minutes;
|
const endMinutes = end.hours * 60 + end.minutes;
|
||||||
|
|
||||||
if (endMinutes === 0) { // If 'To' is 00:00 (midnight)
|
if (endMinutes === 0) {
|
||||||
if (fromTime === "00:00") {
|
if (fromTime === "00:00") {
|
||||||
alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`);
|
alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`);
|
||||||
return false;
|
return false;
|
||||||
@ -443,14 +441,13 @@
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate office hours
|
|
||||||
if (this.editForm.officeFrom && this.editForm.officeTo) {
|
if (this.editForm.officeFrom && this.editForm.officeTo) {
|
||||||
if (!validateRange(this.editForm.officeFrom, this.editForm.officeTo, 'Office Hour')) {
|
if (!validateRange(this.editForm.officeFrom, this.editForm.officeTo, 'Office Hour')) {
|
||||||
this.editForm.officeTo = '';
|
this.editForm.officeTo = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate after hours
|
|
||||||
if (this.editForm.afterFrom && this.editForm.afterTo) {
|
if (this.editForm.afterFrom && this.editForm.afterTo) {
|
||||||
if (!validateRange(this.editForm.afterFrom, this.editForm.afterTo, 'After Office Hour')) {
|
if (!validateRange(this.editForm.afterFrom, this.editForm.afterTo, 'After Office Hour')) {
|
||||||
this.editForm.afterTo = '';
|
this.editForm.afterTo = '';
|
||||||
@ -458,23 +455,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update the roundToNearest30 method to match OtRegister
|
|
||||||
roundToNearest30(timeStr) {
|
roundToNearest30(timeStr) {
|
||||||
if (!timeStr) return timeStr;
|
if (!timeStr) return timeStr;
|
||||||
const [hours, minutes] = timeStr.split(':').map(Number);
|
const [hours, minutes] = timeStr.split(':').map(Number);
|
||||||
const totalMinutes = hours * 60 + minutes;
|
const totalMinutes = hours * 60 + minutes;
|
||||||
const remainder = totalMinutes % 30;
|
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 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;
|
const adjustedMinute = roundedMinutes % 60;
|
||||||
|
|
||||||
return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`;
|
return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update calculateTimeDifference to handle midnight case
|
|
||||||
calculateTimeDifference(startTime, endTime, breakMinutes) {
|
calculateTimeDifference(startTime, endTime, breakMinutes) {
|
||||||
if (!startTime || !endTime) {
|
if (!startTime || !endTime) {
|
||||||
return { hours: 0, minutes: 0 };
|
return { hours: 0, minutes: 0 };
|
||||||
@ -485,19 +479,19 @@
|
|||||||
|
|
||||||
let diffMinutes;
|
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)) {
|
if (end.hours === 0 && end.minutes === 0 && (start.hours > 0 || start.minutes > 0)) {
|
||||||
diffMinutes = (24 * 60) - (start.hours * 60 + start.minutes);
|
diffMinutes = (24 * 60) - (start.hours * 60 + start.minutes);
|
||||||
} else if (end.hours * 60 + end.minutes <= 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 };
|
return { hours: 0, minutes: 0 };
|
||||||
|
|
||||||
} else {
|
} 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 = (end.hours * 60 + end.minutes) - (start.hours * 60 + start.minutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
diffMinutes -= breakMinutes || 0;
|
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 hours = Math.floor(diffMinutes / 60);
|
||||||
const minutes = diffMinutes % 60;
|
const minutes = diffMinutes % 60;
|
||||||
@ -558,7 +552,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
validateForm() {
|
validateForm() {
|
||||||
// Reset validation errors
|
|
||||||
this.validationErrors = {
|
this.validationErrors = {
|
||||||
otDate: "",
|
otDate: "",
|
||||||
stationId: "",
|
stationId: "",
|
||||||
@ -569,7 +563,7 @@
|
|||||||
let isValid = true;
|
let isValid = true;
|
||||||
let errorMessages = [];
|
let errorMessages = [];
|
||||||
|
|
||||||
// Validate date
|
|
||||||
if (!this.editForm.otDate) {
|
if (!this.editForm.otDate) {
|
||||||
this.validationErrors.otDate = "Date is required.";
|
this.validationErrors.otDate = "Date is required.";
|
||||||
errorMessages.push("Date is required.");
|
errorMessages.push("Date is required.");
|
||||||
@ -648,7 +642,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display alert if not valid
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
alert("Please correct the following issues:\n\n" + errorMessages.join("\n"));
|
alert("Please correct the following issues:\n\n" + errorMessages.join("\n"));
|
||||||
}
|
}
|
||||||
@ -709,11 +702,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
// If we have stored month and year, use them to return to the specific view
|
|
||||||
if (this.returnMonth && this.returnYear) {
|
if (this.returnMonth && this.returnYear) {
|
||||||
window.location.href = `/OTcalculate/Overtime/OtRecords?month=${this.returnMonth}&year=${this.returnYear}`;
|
window.location.href = `/OTcalculate/Overtime/OtRecords?month=${this.returnMonth}&year=${this.returnYear}`;
|
||||||
} else {
|
} else {
|
||||||
// Fallback to previous page if month/year are not available
|
|
||||||
window.location.href = this.previousPage;
|
window.location.href = this.previousPage;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -212,8 +212,8 @@
|
|||||||
otRecords: [],
|
otRecords: [],
|
||||||
userId: null,
|
userId: null,
|
||||||
isPSTWAIR: false,
|
isPSTWAIR: false,
|
||||||
selectedMonth: initialMonth, // Use initialMonth
|
selectedMonth: initialMonth,
|
||||||
selectedYear: initialYear, // Use initialYear
|
selectedYear: initialYear,
|
||||||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||||
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i),
|
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i),
|
||||||
expandedDescriptions: {},
|
expandedDescriptions: {},
|
||||||
@ -238,7 +238,6 @@
|
|||||||
.sort((a, b) => new Date(a.otDate) - new Date(b.otDate));
|
.sort((a, b) => new Date(a.otDate) - new Date(b.otDate));
|
||||||
},
|
},
|
||||||
noRecordsFound() {
|
noRecordsFound() {
|
||||||
// This new computed property checks if filteredRecords is empty
|
|
||||||
return this.filteredRecords.length === 0;
|
return this.filteredRecords.length === 0;
|
||||||
},
|
},
|
||||||
totalHours() {
|
totalHours() {
|
||||||
@ -291,7 +290,6 @@
|
|||||||
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
|
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
|
||||||
const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin");
|
const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin");
|
||||||
const isDepartmentTwo = this.currentUser?.department?.departmentId === 2;
|
const isDepartmentTwo = this.currentUser?.department?.departmentId === 2;
|
||||||
// ADD THIS LINE TO INCLUDE DEPARTMENT ID 3 (PSTWMARINE)
|
|
||||||
const isDepartmentThree = this.currentUser?.department?.departmentId === 3;
|
const isDepartmentThree = this.currentUser?.department?.departmentId === 3;
|
||||||
|
|
||||||
this.isPSTWAIR = isSuperAdmin || isSystemAdmin || isDepartmentTwo || isDepartmentThree;
|
this.isPSTWAIR = isSuperAdmin || isSystemAdmin || isDepartmentTwo || isDepartmentThree;
|
||||||
@ -320,14 +318,14 @@
|
|||||||
const errorText = await res.text();
|
const errorText = await res.text();
|
||||||
console.error("Failed to check submission status:", errorText);
|
console.error("Failed to check submission status:", errorText);
|
||||||
alert("Error checking submission status. Please try again.");
|
alert("Error checking submission status. Please try again.");
|
||||||
this.hasSubmitted = false; // Or handle as appropriate
|
this.hasSubmitted = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.hasSubmitted = await res.json();
|
this.hasSubmitted = await res.json();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to parse submission status:", err);
|
console.error("Failed to parse submission status:", err);
|
||||||
alert("An unexpected error occurred while checking submission status.");
|
alert("An unexpected error occurred while checking submission status.");
|
||||||
this.hasSubmitted = false; // Or handle as appropriate
|
this.hasSubmitted = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleDescription(index) {
|
toggleDescription(index) {
|
||||||
@ -357,13 +355,11 @@
|
|||||||
|
|
||||||
let totalMinutes = (th * 60 + tm) - (fh * 60 + fm);
|
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) {
|
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) {
|
calcTotalTime(r) {
|
||||||
const totalMinutes = this.calcTotalHours(r) * 60;
|
const totalMinutes = this.calcTotalHours(r) * 60;
|
||||||
@ -393,7 +389,6 @@
|
|||||||
},
|
},
|
||||||
editRecord(index) {
|
editRecord(index) {
|
||||||
const record = this.filteredRecords[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}`;
|
window.location.href = `/OTcalculate/Overtime/EditOvertime?overtimeId=${record.overtimeId}&month=${this.selectedMonth}&year=${this.selectedYear}`;
|
||||||
},
|
},
|
||||||
async deleteRecord(index) {
|
async deleteRecord(index) {
|
||||||
@ -443,7 +438,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
downloadExcel(month, year) {
|
downloadExcel(month, year) {
|
||||||
// Use the new API endpoint
|
|
||||||
window.open(`/OvertimeAPI/GenerateUserOvertimeExcel/${this.userId}/${month}/${year}`, '_blank');
|
window.open(`/OvertimeAPI/GenerateUserOvertimeExcel/${this.userId}/${month}/${year}`, '_blank');
|
||||||
},
|
},
|
||||||
openSubmitModal() {
|
openSubmitModal() {
|
||||||
@ -478,7 +472,7 @@
|
|||||||
const modalInstance = bootstrap.Modal.getInstance(modalEl);
|
const modalInstance = bootstrap.Modal.getInstance(modalEl);
|
||||||
modalInstance.hide();
|
modalInstance.hide();
|
||||||
|
|
||||||
await this.getSubmissionStatus(); // Call this to refresh the hasSubmitted status
|
await this.getSubmissionStatus();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert('Submission failed.');
|
alert('Submission failed.');
|
||||||
|
|||||||
@ -148,7 +148,6 @@
|
|||||||
afterFrom: "",
|
afterFrom: "",
|
||||||
afterTo: "",
|
afterTo: "",
|
||||||
afterBreak: 0,
|
afterBreak: 0,
|
||||||
// New properties for separate station selections
|
|
||||||
selectedAirStation: "", // Holds selected Air station ID
|
selectedAirStation: "", // Holds selected Air station ID
|
||||||
selectedMarineStation: "", // Holds selected Marine station ID
|
selectedMarineStation: "", // Holds selected Marine station ID
|
||||||
airStationList: [], // Stores stations for Air Department (DepartmentId = 2)
|
airStationList: [], // Stores stations for Air Department (DepartmentId = 2)
|
||||||
@ -163,10 +162,9 @@
|
|||||||
userId: null,
|
userId: null,
|
||||||
userState: null,
|
userState: null,
|
||||||
publicHolidays: [],
|
publicHolidays: [],
|
||||||
// Keep user's actual department ID and admin flag for conditional rendering
|
|
||||||
userDepartmentId: null, // The department ID from the current user's profile
|
userDepartmentId: null, // The department ID from the current user's profile
|
||||||
isUserAdmin: false, // True if the user is a SuperAdmin or SystemAdmin
|
isUserAdmin: false,
|
||||||
departmentName: "", // This will be dynamic based on the user's main department for hints
|
departmentName: "",
|
||||||
areUserSettingsComplete: false,
|
areUserSettingsComplete: false,
|
||||||
breakOptions: Array.from({ length: 15 }, (_, i) => {
|
breakOptions: Array.from({ length: 15 }, (_, i) => {
|
||||||
const totalMinutes = i * 30;
|
const totalMinutes = i * 30;
|
||||||
@ -186,17 +184,12 @@
|
|||||||
charCount() {
|
charCount() {
|
||||||
return this.otDescription.length;
|
return this.otDescription.length;
|
||||||
},
|
},
|
||||||
// Determines if the Air Station dropdown should be shown
|
|
||||||
showAirDropdown() {
|
showAirDropdown() {
|
||||||
return this.isUserAdmin || this.userDepartmentId === 2;
|
return this.isUserAdmin || this.userDepartmentId === 2;
|
||||||
},
|
},
|
||||||
// Determines if the Marine Station dropdown should be shown
|
|
||||||
showMarineDropdown() {
|
showMarineDropdown() {
|
||||||
return this.isUserAdmin || this.userDepartmentId === 3;
|
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() {
|
stationIdForSubmission() {
|
||||||
if (this.selectedAirStation) {
|
if (this.selectedAirStation) {
|
||||||
return parseInt(this.selectedAirStation);
|
return parseInt(this.selectedAirStation);
|
||||||
@ -204,15 +197,13 @@
|
|||||||
if (this.selectedMarineStation) {
|
if (this.selectedMarineStation) {
|
||||||
return parseInt(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() {
|
requiresStation() {
|
||||||
return this.showAirDropdown || this.showMarineDropdown;
|
return this.showAirDropdown || this.showMarineDropdown;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// Watch for changes in airStationList to re-initialize Select2 for Air
|
|
||||||
airStationList: {
|
airStationList: {
|
||||||
handler() {
|
handler() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -222,11 +213,9 @@
|
|||||||
selectElement.select2('destroy');
|
selectElement.select2('destroy');
|
||||||
}
|
}
|
||||||
selectElement.select2({ theme: 'bootstrap4', placeholder: 'Select Air Station', allowClear: true });
|
selectElement.select2({ theme: 'bootstrap4', placeholder: 'Select Air Station', allowClear: true });
|
||||||
// Set initial value if already selected
|
|
||||||
if (this.selectedAirStation) {
|
if (this.selectedAirStation) {
|
||||||
selectElement.val(this.selectedAirStation).trigger('change');
|
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) => {
|
selectElement.off('change.v-model-air').on('change.v-model-air', (event) => {
|
||||||
this.selectedAirStation = $(event.currentTarget).val();
|
this.selectedAirStation = $(event.currentTarget).val();
|
||||||
});
|
});
|
||||||
@ -235,7 +224,6 @@
|
|||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
// Watch for changes in marineStationList to re-initialize Select2 for Marine
|
|
||||||
marineStationList: {
|
marineStationList: {
|
||||||
handler() {
|
handler() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -245,11 +233,9 @@
|
|||||||
selectElement.select2('destroy');
|
selectElement.select2('destroy');
|
||||||
}
|
}
|
||||||
selectElement.select2({ theme: 'bootstrap4', placeholder: 'Select Marine Station', allowClear: true });
|
selectElement.select2({ theme: 'bootstrap4', placeholder: 'Select Marine Station', allowClear: true });
|
||||||
// Set initial value if already selected
|
|
||||||
if (this.selectedMarineStation) {
|
if (this.selectedMarineStation) {
|
||||||
selectElement.val(this.selectedMarineStation).trigger('change');
|
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) => {
|
selectElement.off('change.v-model-marine').on('change.v-model-marine', (event) => {
|
||||||
this.selectedMarineStation = $(event.currentTarget).val();
|
this.selectedMarineStation = $(event.currentTarget).val();
|
||||||
});
|
});
|
||||||
@ -258,14 +244,12 @@
|
|||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
// Keep selectedAirStation in sync with Select2 if it's already rendered
|
|
||||||
selectedAirStation(newVal) {
|
selectedAirStation(newVal) {
|
||||||
const selectElement = $('#airStationDropdown');
|
const selectElement = $('#airStationDropdown');
|
||||||
if (selectElement.length && selectElement.val() !== newVal) {
|
if (selectElement.length && selectElement.val() !== newVal) {
|
||||||
selectElement.val(newVal).trigger('change.select2');
|
selectElement.val(newVal).trigger('change.select2');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Keep selectedMarineStation in sync with Select2 if it's already rendered
|
|
||||||
selectedMarineStation(newVal) {
|
selectedMarineStation(newVal) {
|
||||||
const selectElement = $('#marineStationDropdown');
|
const selectElement = $('#marineStationDropdown');
|
||||||
if (selectElement.length && selectElement.val() !== newVal) {
|
if (selectElement.length && selectElement.val() !== newVal) {
|
||||||
@ -278,18 +262,14 @@
|
|||||||
if (this.userId) {
|
if (this.userId) {
|
||||||
await this.checkUserSettings();
|
await this.checkUserSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch stations for Air if the dropdown will be visible
|
|
||||||
if (this.showAirDropdown) {
|
if (this.showAirDropdown) {
|
||||||
await this.fetchStations(2, 'air'); // Department ID 2 for Air
|
await this.fetchStations(2, 'air'); // Department ID 2 for Air
|
||||||
}
|
}
|
||||||
// Fetch stations for Marine if the dropdown will be visible
|
|
||||||
if (this.showMarineDropdown) {
|
if (this.showMarineDropdown) {
|
||||||
await this.fetchStations(3, 'marine'); // Department ID 3 for Marine
|
await this.fetchStations(3, 'marine'); // Department ID 3 for Marine
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// Modified fetchStations to populate specific lists based on listType
|
|
||||||
async fetchStations(departmentId, listType) {
|
async fetchStations(departmentId, listType) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/OvertimeAPI/GetStationsByDepartment?departmentId=${departmentId}`);
|
const response = await fetch(`/OvertimeAPI/GetStationsByDepartment?departmentId=${departmentId}`);
|
||||||
@ -319,11 +299,10 @@
|
|||||||
|
|
||||||
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
|
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
|
||||||
const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin");
|
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.isUserAdmin) {
|
||||||
if (this.userDepartmentId === 2) {
|
if (this.userDepartmentId === 2) {
|
||||||
this.departmentName = "PSTW AIR";
|
this.departmentName = "PSTW AIR";
|
||||||
@ -333,7 +312,7 @@
|
|||||||
this.departmentName = "";
|
this.departmentName = "";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.departmentName = ""; // Admins see both, so this specific hint is removed for them
|
this.departmentName = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Fetched User:", this.currentUser);
|
console.log("Fetched User:", this.currentUser);
|
||||||
@ -394,10 +373,9 @@
|
|||||||
const totalMinutes = hours * 60 + minutes;
|
const totalMinutes = hours * 60 + minutes;
|
||||||
const remainder = totalMinutes % 30;
|
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 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;
|
const adjustedMinute = roundedMinutes % 60;
|
||||||
|
|
||||||
return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`;
|
return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`;
|
||||||
@ -446,7 +424,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
diffMinutes -= breakMinutes || 0;
|
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 hours = Math.floor(diffMinutes / 60);
|
||||||
const minutes = diffMinutes % 60;
|
const minutes = diffMinutes % 60;
|
||||||
@ -472,7 +450,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Frontend Validation ---
|
|
||||||
if (!this.selectedDate) {
|
if (!this.selectedDate) {
|
||||||
alert("Please select a date for the overtime.");
|
alert("Please select a date for the overtime.");
|
||||||
return;
|
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
|
// 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) => {
|
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 start = this.parseTime(fromTime);
|
||||||
const end = this.parseTime(toTime);
|
const end = this.parseTime(toTime);
|
||||||
@ -542,8 +519,6 @@
|
|||||||
if (hasAfterHours && !validateTimeRangeForSubmission(this.afterFrom, this.afterTo, 'After Office Hour')) {
|
if (hasAfterHours && !validateTimeRangeForSubmission(this.afterFrom, this.afterTo, 'After Office Hour')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// --- End Frontend Validation ---
|
|
||||||
|
|
||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
otDate: this.selectedDate,
|
otDate: this.selectedDate,
|
||||||
@ -553,7 +528,7 @@
|
|||||||
afterFrom: this.afterFrom ? this.formatTime(this.afterFrom) : null,
|
afterFrom: this.afterFrom ? this.formatTime(this.afterFrom) : null,
|
||||||
afterTo: this.afterTo ? this.formatTime(this.afterTo) : null,
|
afterTo: this.afterTo ? this.formatTime(this.afterTo) : null,
|
||||||
afterBreak: this.afterBreak || 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(' '),
|
otDescription: this.otDescription.trim().split(/\s+/).slice(0, 50).join(' '),
|
||||||
otDays: this.detectedDayType,
|
otDays: this.detectedDayType,
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
@ -635,10 +610,9 @@
|
|||||||
this.afterFrom = "";
|
this.afterFrom = "";
|
||||||
this.afterTo = "";
|
this.afterTo = "";
|
||||||
this.afterBreak = 0;
|
this.afterBreak = 0;
|
||||||
this.selectedAirStation = ""; // Clear specific station selections
|
this.selectedAirStation = "";
|
||||||
this.selectedMarineStation = ""; // Clear specific station selections
|
this.selectedMarineStation = "";
|
||||||
|
|
||||||
// Clear Select2 for both dropdowns if they exist
|
|
||||||
const airSelect = $('#airStationDropdown');
|
const airSelect = $('#airStationDropdown');
|
||||||
if (airSelect.length && airSelect.data('select2')) {
|
if (airSelect.length && airSelect.data('select2')) {
|
||||||
airSelect.val('').trigger('change.select2');
|
airSelect.val('').trigger('change.select2');
|
||||||
|
|||||||
@ -112,6 +112,7 @@
|
|||||||
<div id="app" style="max-width: 1300px; margin: auto; font-size: 13px;">
|
<div id="app" style="max-width: 1300px; margin: auto; font-size: 13px;">
|
||||||
<div class="table-layer">
|
<div class="table-layer">
|
||||||
<div class="white-box">
|
<div class="white-box">
|
||||||
|
|
||||||
<!-- Simplified Filter Section -->
|
<!-- Simplified Filter Section -->
|
||||||
<div class="filter-container">
|
<div class="filter-container">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@ -344,12 +345,12 @@
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
columnCount() {
|
columnCount() {
|
||||||
let count = 3; // Month/Year, SubmitDate, Edit History
|
let count = 3;
|
||||||
if (this.includeHou) count++;
|
if (this.includeHou) count++;
|
||||||
if (this.includeHod) count++;
|
if (this.includeHod) count++;
|
||||||
if (this.includeManager) count++;
|
if (this.includeManager) count++;
|
||||||
if (this.includeHr) count++;
|
if (this.includeHr) count++;
|
||||||
return count + 1; // File column
|
return count + 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
monthYearOptions() {
|
monthYearOptions() {
|
||||||
@ -374,7 +375,6 @@
|
|||||||
{ value: 'Rejected', text: 'Rejected' }
|
{ value: 'Rejected', text: 'Rejected' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add combined status options if multiple approval levels exist
|
|
||||||
if ((this.includeHou && this.includeHod) ||
|
if ((this.includeHou && this.includeHod) ||
|
||||||
(this.includeHou && this.includeManager) ||
|
(this.includeHou && this.includeManager) ||
|
||||||
(this.includeHod && this.includeManager)) {
|
(this.includeHod && this.includeManager)) {
|
||||||
@ -390,7 +390,6 @@
|
|||||||
filteredRecords() {
|
filteredRecords() {
|
||||||
let filtered = [...this.otRecords];
|
let filtered = [...this.otRecords];
|
||||||
|
|
||||||
// Apply filters
|
|
||||||
if (this.filters.monthYear) {
|
if (this.filters.monthYear) {
|
||||||
const [month, year] = this.filters.monthYear.split('/').map(Number);
|
const [month, year] = this.filters.monthYear.split('/').map(Number);
|
||||||
filtered = filtered.filter(item =>
|
filtered = filtered.filter(item =>
|
||||||
@ -434,7 +433,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply sorting
|
|
||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
let aValue, bValue;
|
let aValue, bValue;
|
||||||
|
|
||||||
@ -472,7 +470,6 @@
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update pagination
|
|
||||||
this.pagination.totalPages = Math.ceil(filtered.length / this.pagination.itemsPerPage);
|
this.pagination.totalPages = Math.ceil(filtered.length / this.pagination.itemsPerPage);
|
||||||
this.pagination.currentPage = Math.min(this.pagination.currentPage, this.pagination.totalPages || 1);
|
this.pagination.currentPage = Math.min(this.pagination.currentPage, this.pagination.totalPages || 1);
|
||||||
|
|
||||||
@ -727,7 +724,6 @@
|
|||||||
this.parsedHistory = [];
|
this.parsedHistory = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sorting methods
|
|
||||||
sortBy(field) {
|
sortBy(field) {
|
||||||
if (this.sort.field === field) {
|
if (this.sort.field === field) {
|
||||||
this.sort.order = this.sort.order === 'asc' ? 'desc' : 'asc';
|
this.sort.order = this.sort.order === 'asc' ? 'desc' : 'asc';
|
||||||
@ -735,10 +731,9 @@
|
|||||||
this.sort.field = field;
|
this.sort.field = field;
|
||||||
this.sort.order = 'asc';
|
this.sort.order = 'asc';
|
||||||
}
|
}
|
||||||
this.pagination.currentPage = 1; // Reset to first page when sorting changes
|
this.pagination.currentPage = 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Filter methods
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.filters = {
|
this.filters = {
|
||||||
monthYear: '',
|
monthYear: '',
|
||||||
@ -747,7 +742,6 @@
|
|||||||
this.pagination.currentPage = 1;
|
this.pagination.currentPage = 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Pagination methods
|
|
||||||
prevPage() {
|
prevPage() {
|
||||||
if (this.pagination.currentPage > 1) {
|
if (this.pagination.currentPage > 1) {
|
||||||
this.pagination.currentPage--;
|
this.pagination.currentPage--;
|
||||||
|
|||||||
@ -85,29 +85,24 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Use a list to hold IDs of users with incomplete settings
|
|
||||||
var incompleteUserIds = new List<int>();
|
var incompleteUserIds = new List<int>();
|
||||||
|
|
||||||
// Get all user IDs
|
|
||||||
var allUserIds = await _userManager.Users.Select(u => u.Id).ToListAsync();
|
var allUserIds = await _userManager.Users.Select(u => u.Id).ToListAsync();
|
||||||
|
|
||||||
foreach (var userId in allUserIds)
|
foreach (var userId in allUserIds)
|
||||||
{
|
{
|
||||||
bool isIncomplete = false;
|
bool isIncomplete = false;
|
||||||
|
|
||||||
// Check HrUserSettingModel
|
|
||||||
var hrUserSetting = await _centralDbContext.Hrusersetting
|
var hrUserSetting = await _centralDbContext.Hrusersetting
|
||||||
.Where(h => h.UserId == userId)
|
.Where(h => h.UserId == userId)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (hrUserSetting == null)
|
if (hrUserSetting == null)
|
||||||
{
|
{
|
||||||
// No HR user setting found for this user
|
|
||||||
isIncomplete = true;
|
isIncomplete = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check for null/empty fields in HrUserSettingModel
|
|
||||||
if (hrUserSetting.FlexiHourId == null || hrUserSetting.FlexiHourId == 0)
|
if (hrUserSetting.FlexiHourId == null || hrUserSetting.FlexiHourId == 0)
|
||||||
{
|
{
|
||||||
isIncomplete = true;
|
isIncomplete = true;
|
||||||
@ -122,19 +117,16 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check RateModel
|
|
||||||
var rateSetting = await _centralDbContext.Rates
|
var rateSetting = await _centralDbContext.Rates
|
||||||
.Where(r => r.UserId == userId)
|
.Where(r => r.UserId == userId)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (rateSetting == null)
|
if (rateSetting == null)
|
||||||
{
|
{
|
||||||
// No Rate setting found for this user
|
|
||||||
isIncomplete = true;
|
isIncomplete = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check for default/empty RateValue (assuming 0.00 is considered empty)
|
|
||||||
if (rateSetting.RateValue == 0.00m)
|
if (rateSetting.RateValue == 0.00m)
|
||||||
{
|
{
|
||||||
isIncomplete = true;
|
isIncomplete = true;
|
||||||
@ -143,13 +135,12 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
if (isIncomplete)
|
if (isIncomplete)
|
||||||
{
|
{
|
||||||
incompleteUserIds.Add(userId); // Add only the ID
|
incompleteUserIds.Add(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (incompleteUserIds.Any())
|
if (incompleteUserIds.Any())
|
||||||
{
|
{
|
||||||
// Return just the count of incomplete users
|
|
||||||
return Ok(new { hasIncompleteSettings = true, numberOfIncompleteUsers = incompleteUserIds.Count });
|
return Ok(new { hasIncompleteSettings = true, numberOfIncompleteUsers = incompleteUserIds.Count });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -330,8 +321,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
.FirstOrDefault() ?? "N/A"
|
.FirstOrDefault() ?? "N/A"
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// Log this data to inspect the response
|
Console.WriteLine(JsonConvert.SerializeObject(result));
|
||||||
Console.WriteLine(JsonConvert.SerializeObject(result)); // Debugging log
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
|
|
||||||
|
|
||||||
@ -418,7 +408,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
if (existingSetting != null)
|
if (existingSetting != null)
|
||||||
{
|
{
|
||||||
existingSetting.StateId = update.StateId;
|
existingSetting.StateId = update.StateId;
|
||||||
existingSetting.StateUpdate = DateTime.Now; // <-- ADD THIS LINE
|
existingSetting.StateUpdate = DateTime.Now;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -426,8 +416,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
{
|
{
|
||||||
UserId = update.UserId,
|
UserId = update.UserId,
|
||||||
StateId = update.StateId,
|
StateId = update.StateId,
|
||||||
StateUpdate = DateTime.Now, // <-- ADD THIS LINE for new records
|
StateUpdate = DateTime.Now,
|
||||||
// Consider setting default for FlexiHourId and ApprovalFlowId if they are not nullable or have defaults
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -580,7 +569,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return NotFound("Approval flow not found.");
|
return NotFound("Approval flow not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fields
|
|
||||||
existingFlow.ApprovalName = model.ApprovalName;
|
existingFlow.ApprovalName = model.ApprovalName;
|
||||||
existingFlow.HoU = model.HoU;
|
existingFlow.HoU = model.HoU;
|
||||||
existingFlow.HoD = model.HoD;
|
existingFlow.HoD = model.HoD;
|
||||||
@ -594,7 +582,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Log the exception
|
|
||||||
return StatusCode(500, new { message = "Failed to update approval flow.", detail = ex.Message });
|
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." });
|
return NotFound(new { message = "Approval flow not found." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any users are using this approval flow
|
|
||||||
var usersWithThisFlow = await _centralDbContext.Hrusersetting
|
var usersWithThisFlow = await _centralDbContext.Hrusersetting
|
||||||
.AnyAsync(u => u.ApprovalFlowId == id);
|
.AnyAsync(u => u.ApprovalFlowId == id);
|
||||||
|
|
||||||
@ -802,13 +788,11 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
if (existingState != null)
|
if (existingState != null)
|
||||||
{
|
{
|
||||||
// Corrected: Updating WeekendId
|
|
||||||
existingState.WeekendId = state.WeekendId;
|
existingState.WeekendId = state.WeekendId;
|
||||||
_centralDbContext.States.Update(existingState);
|
_centralDbContext.States.Update(existingState);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Ensure new states are added correctly
|
|
||||||
_centralDbContext.States.Add(new StateModel
|
_centralDbContext.States.Add(new StateModel
|
||||||
{
|
{
|
||||||
StateId = state.StateId,
|
StateId = state.StateId,
|
||||||
@ -874,15 +858,13 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region OtRegister
|
#region OtRegister
|
||||||
|
|
||||||
// Modified to accept departmentId as a query parameter
|
|
||||||
[HttpGet("GetStationsByDepartment")]
|
[HttpGet("GetStationsByDepartment")]
|
||||||
public async Task<IActionResult> GetStationsByDepartment([FromQuery] int? departmentId)
|
public async Task<IActionResult> GetStationsByDepartment([FromQuery] int? departmentId)
|
||||||
{
|
{
|
||||||
if (!departmentId.HasValue)
|
if (!departmentId.HasValue)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("GetStationsByDepartment called without a departmentId.");
|
_logger.LogWarning("GetStationsByDepartment called without a departmentId.");
|
||||||
return Ok(new List<object>()); // Return empty list if no department is specified
|
return Ok(new List<object>());
|
||||||
}
|
}
|
||||||
|
|
||||||
var stations = await _centralDbContext.Stations
|
var stations = await _centralDbContext.Stations
|
||||||
@ -918,7 +900,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return BadRequest("User ID is required.");
|
return BadRequest("User ID is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// **Backend Validation for StationId based on user roles and department**
|
|
||||||
var user = await _userManager.FindByIdAsync(request.UserId.ToString());
|
var user = await _userManager.FindByIdAsync(request.UserId.ToString());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -930,22 +911,20 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
var isSuperAdmin = userRoles.Contains("SuperAdmin");
|
var isSuperAdmin = userRoles.Contains("SuperAdmin");
|
||||||
var isSystemAdmin = userRoles.Contains("SystemAdmin");
|
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
|
var userWithDepartment = await _centralDbContext.Users
|
||||||
.Include(u => u.Department)
|
.Include(u => u.Department)
|
||||||
.FirstOrDefaultAsync(u => u.Id == request.UserId);
|
.FirstOrDefaultAsync(u => u.Id == request.UserId);
|
||||||
|
|
||||||
int? userDepartmentId = userWithDepartment?.Department?.DepartmentId;
|
int? userDepartmentId = userWithDepartment?.Department?.DepartmentId;
|
||||||
|
|
||||||
// Determine if a station is required and validate it
|
|
||||||
bool stationRequired = false;
|
bool stationRequired = false;
|
||||||
if (userDepartmentId == 2 || userDepartmentId == 3) // For regular Air/Marine users
|
if (userDepartmentId == 2 || userDepartmentId == 3)
|
||||||
{
|
{
|
||||||
stationRequired = true;
|
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))
|
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? afterFrom = string.IsNullOrEmpty(request.AfterFrom) ? (TimeSpan?)null : TimeSpan.Parse(request.AfterFrom);
|
||||||
TimeSpan? afterTo = string.IsNullOrEmpty(request.AfterTo) ? (TimeSpan?)null : TimeSpan.Parse(request.AfterTo);
|
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))
|
if ((officeFrom != null && officeTo == null) || (officeFrom == null && officeTo != null))
|
||||||
{
|
{
|
||||||
return BadRequest("Both Office From and To times must be provided if one is entered.");
|
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.");
|
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);
|
||||||
TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); // 4:30 PM
|
TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0);
|
||||||
TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); // 11:30 PM
|
|
||||||
|
|
||||||
// Backend Validation for Office Hours
|
|
||||||
if (officeFrom.HasValue && officeTo.HasValue)
|
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)
|
if (officeFrom == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight).");
|
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)
|
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.");
|
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).");
|
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 (afterFrom.HasValue && afterTo.HasValue)
|
||||||
{
|
{
|
||||||
if (afterTo == TimeSpan.Zero) // If AfterTo is exactly midnight (00:00:00)
|
if (afterTo == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
if (afterFrom == TimeSpan.Zero)
|
if (afterFrom == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid After Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight).");
|
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)
|
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.");
|
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).");
|
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,
|
OtDate = request.OtDate,
|
||||||
OfficeFrom = officeFrom,
|
OfficeFrom = officeFrom,
|
||||||
OfficeTo = officeTo,
|
OfficeTo = officeTo,
|
||||||
OfficeBreak = request.OfficeBreak, // Assuming it's nullable or default to 0
|
OfficeBreak = request.OfficeBreak,
|
||||||
AfterFrom = afterFrom,
|
AfterFrom = afterFrom,
|
||||||
AfterTo = afterTo,
|
AfterTo = afterTo,
|
||||||
AfterBreak = request.AfterBreak, // Assuming it's nullable or default to 0
|
AfterBreak = request.AfterBreak,
|
||||||
StationId = request.StationId,
|
StationId = request.StationId,
|
||||||
OtDescription = request.OtDescription,
|
OtDescription = request.OtDescription,
|
||||||
OtDays = request.OtDays,
|
OtDays = request.OtDays,
|
||||||
@ -1142,7 +1117,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var records = _centralDbContext.Otregisters
|
var records = _centralDbContext.Otregisters
|
||||||
.Include(o => o.Stations) // <--- ADD THIS LINE
|
.Include(o => o.Stations)
|
||||||
.Where(o => o.UserId == userId)
|
.Where(o => o.UserId == userId)
|
||||||
.OrderByDescending(o => o.OtDate)
|
.OrderByDescending(o => o.OtDate)
|
||||||
.Select(o => new
|
.Select(o => new
|
||||||
@ -1233,7 +1208,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
var relativePath = Path.Combine("Media", "Overtime", uniqueFileName).Replace("\\", "/");
|
var relativePath = Path.Combine("Media", "Overtime", uniqueFileName).Replace("\\", "/");
|
||||||
|
|
||||||
// Create a NEW OtStatusModel for the resubmission
|
|
||||||
var statusModel = new OtStatusModel
|
var statusModel = new OtStatusModel
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
@ -1248,9 +1222,8 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
};
|
};
|
||||||
|
|
||||||
_centralDbContext.Otstatus.Add(statusModel);
|
_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 monthStart = new DateTime(model.Year, model.Month, 1);
|
||||||
var monthEnd = monthStart.AddMonths(1);
|
var monthEnd = monthStart.AddMonths(1);
|
||||||
|
|
||||||
@ -1266,7 +1239,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
_centralDbContext.Otregisters.UpdateRange(otRecords);
|
_centralDbContext.Otregisters.UpdateRange(otRecords);
|
||||||
await _centralDbContext.SaveChangesAsync();
|
await _centralDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
// Update HrUserSetting with the NEW StatusId
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting.FirstOrDefault(s => s.UserId == userId);
|
var userSetting = _centralDbContext.Hrusersetting.FirstOrDefault(s => s.UserId == userId);
|
||||||
if (userSetting != null)
|
if (userSetting != null)
|
||||||
{
|
{
|
||||||
@ -1290,7 +1262,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get the latest OtStatus record for the user, month, and year
|
|
||||||
var latestStatus = _centralDbContext.Otstatus
|
var latestStatus = _centralDbContext.Otstatus
|
||||||
.Where(s => s.UserId == userId && s.Month == month && s.Year == year)
|
.Where(s => s.UserId == userId && s.Month == month && s.Year == year)
|
||||||
.OrderByDescending(s => s.SubmitDate)
|
.OrderByDescending(s => s.SubmitDate)
|
||||||
@ -1298,29 +1269,26 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
if (latestStatus == null)
|
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" ||
|
if (latestStatus.HouStatus?.ToLower() == "rejected" ||
|
||||||
latestStatus.HodStatus?.ToLower() == "rejected" ||
|
latestStatus.HodStatus?.ToLower() == "rejected" ||
|
||||||
latestStatus.ManagerStatus?.ToLower() == "rejected" ||
|
latestStatus.ManagerStatus?.ToLower() == "rejected" ||
|
||||||
latestStatus.HrStatus?.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" ||
|
if (latestStatus.HouStatus?.ToLower() == "pending" ||
|
||||||
latestStatus.HodStatus?.ToLower() == "pending" ||
|
latestStatus.HodStatus?.ToLower() == "pending" ||
|
||||||
latestStatus.ManagerStatus?.ToLower() == "pending" ||
|
latestStatus.ManagerStatus?.ToLower() == "pending" ||
|
||||||
latestStatus.HrStatus?.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);
|
||||||
return Ok(true); // Disable submit
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -1376,14 +1344,13 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return Ok(record);
|
return Ok(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
// New API Endpoint to Get User's Flexi Hour
|
|
||||||
[HttpGet("GetUserFlexiHour/{userId}")]
|
[HttpGet("GetUserFlexiHour/{userId}")]
|
||||||
public async Task<IActionResult> GetUserFlexiHour(int userId)
|
public async Task<IActionResult> GetUserFlexiHour(int userId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userSetting = await _centralDbContext.Hrusersetting
|
var userSetting = await _centralDbContext.Hrusersetting
|
||||||
.Include(hs => hs.FlexiHour) // Include the FlexiHour navigation property
|
.Include(hs => hs.FlexiHour)
|
||||||
.FirstOrDefaultAsync(hs => hs.UserId == userId);
|
.FirstOrDefaultAsync(hs => hs.UserId == userId);
|
||||||
|
|
||||||
if (userSetting == null || userSetting.FlexiHour == null)
|
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 NotFound(new { message = "Flexi hour not found for this user." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the FlexiHourModel object
|
|
||||||
return Ok(new { flexiHour = userSetting.FlexiHour });
|
return Ok(new { flexiHour = userSetting.FlexiHour });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -1401,6 +1367,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return StatusCode(500, new { message = "An error occurred while fetching flexi hour." });
|
return StatusCode(500, new { message = "An error occurred while fetching flexi hour." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("UpdateOvertimeRecord")]
|
[Route("UpdateOvertimeRecord")]
|
||||||
public IActionResult UpdateOvertimeRecord([FromBody] OtRegisterUpdateDto model)
|
public IActionResult UpdateOvertimeRecord([FromBody] OtRegisterUpdateDto model)
|
||||||
@ -1414,7 +1381,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate time ranges
|
|
||||||
var timeValidationError = ValidateTimeRanges(model);
|
var timeValidationError = ValidateTimeRanges(model);
|
||||||
if (timeValidationError != null)
|
if (timeValidationError != null)
|
||||||
{
|
{
|
||||||
@ -1427,7 +1393,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return NotFound(new { message = "Overtime record not found." });
|
return NotFound(new { message = "Overtime record not found." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update properties
|
|
||||||
existing.OtDate = model.OtDate;
|
existing.OtDate = model.OtDate;
|
||||||
existing.OfficeFrom = TimeSpan.TryParse(model.OfficeFrom, out var officeFrom) ? officeFrom : null;
|
existing.OfficeFrom = TimeSpan.TryParse(model.OfficeFrom, out var officeFrom) ? officeFrom : null;
|
||||||
existing.OfficeTo = TimeSpan.TryParse(model.OfficeTo, out var officeTo) ? officeTo : 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? afterFrom = string.IsNullOrEmpty(model.AfterFrom) ? (TimeSpan?)null : TimeSpan.Parse(model.AfterFrom);
|
||||||
TimeSpan? afterTo = string.IsNullOrEmpty(model.AfterTo) ? (TimeSpan?)null : TimeSpan.Parse(model.AfterTo);
|
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);
|
||||||
TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); // 4:30 PM
|
TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0);
|
||||||
TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); // 11:30 PM
|
|
||||||
|
|
||||||
// Validate office hours
|
|
||||||
if (officeFrom.HasValue && officeTo.HasValue)
|
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)
|
if (officeFrom == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
return "Invalid Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight).";
|
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)
|
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.";
|
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).";
|
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 (afterFrom.HasValue && afterTo.HasValue)
|
||||||
{
|
{
|
||||||
if (afterTo == TimeSpan.Zero) // If AfterTo is exactly midnight (00:00:00)
|
if (afterTo == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
if (afterFrom == TimeSpan.Zero)
|
if (afterFrom == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
return "Invalid After Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight).";
|
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)
|
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.";
|
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).";
|
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)
|
.Select(x => x.ApprovalFlowId)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
// Default values when no approval flow exists
|
|
||||||
bool includeHou = false;
|
bool includeHou = false;
|
||||||
bool includeHod = false;
|
bool includeHod = false;
|
||||||
bool includeManager = false;
|
bool includeManager = false;
|
||||||
@ -1594,7 +1555,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (!flows.Any())
|
if (!flows.Any())
|
||||||
// Ensure OverallPendingMonths is always returned, even if empty
|
|
||||||
return Json(new { Roles = new List<string>(), Data = new List<object>(), OverallPendingMonths = new List<string>() });
|
return Json(new { Roles = new List<string>(), Data = new List<object>(), OverallPendingMonths = new List<string>() });
|
||||||
|
|
||||||
var flowRoleMap = new Dictionary<int, string>();
|
var flowRoleMap = new Dictionary<int, string>();
|
||||||
@ -1606,8 +1566,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
if (flow.HR == currentUserId) flowRoleMap[flow.ApprovalFlowId] = "HR";
|
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
|
var allRelevantOtEntries = (from status in _centralDbContext.Otstatus
|
||||||
join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId
|
join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId
|
||||||
where setting.ApprovalFlowId.HasValue && flowRoleMap.Keys.Contains(setting.ApprovalFlowId.Value)
|
where setting.ApprovalFlowId.HasValue && flowRoleMap.Keys.Contains(setting.ApprovalFlowId.Value)
|
||||||
@ -1624,19 +1582,16 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
var pendingMonthsAndYears = new HashSet<string>();
|
var pendingMonthsAndYears = new HashSet<string>();
|
||||||
|
|
||||||
// We need to fetch the flows again here to get the full flow structure
|
var allFlows = _centralDbContext.Approvalflow.ToList();
|
||||||
// as `allRelevantOtEntries` only brings back ApprovalFlowId, not the full flow object
|
|
||||||
var allFlows = _centralDbContext.Approvalflow.ToList(); // Fetch all flows once
|
|
||||||
|
|
||||||
foreach (var entry in allRelevantOtEntries)
|
foreach (var entry in allRelevantOtEntries)
|
||||||
{
|
{
|
||||||
var role = flowRoleMap[entry.ApprovalFlowId.Value];
|
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;
|
if (flow == null) continue;
|
||||||
|
|
||||||
bool isPendingForCurrentUser = false;
|
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") &&
|
if (role == "HoU" && (entry.HouStatus == null || entry.HouStatus == "Pending") &&
|
||||||
!(entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected"))
|
!(entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected"))
|
||||||
{
|
{
|
||||||
@ -1666,14 +1621,10 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
if (isPendingForCurrentUser)
|
if (isPendingForCurrentUser)
|
||||||
{
|
{
|
||||||
// Format as MM/YYYY and add to the set
|
|
||||||
pendingMonthsAndYears.Add($"{entry.Month:D2}/{entry.Year}");
|
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
|
var otEntriesForSelectedMonth = (from status in _centralDbContext.Otstatus
|
||||||
join user in _centralDbContext.Users on status.UserId equals user.Id
|
join user in _centralDbContext.Users on status.UserId equals user.Id
|
||||||
join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId
|
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];
|
var role = flowRoleMap[entry.ApprovalFlowId.Value];
|
||||||
distinctRoles.Add(role);
|
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;
|
if (flow == null) continue;
|
||||||
|
|
||||||
bool canApprove = false;
|
bool canApprove = false;
|
||||||
@ -1765,7 +1716,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
{
|
{
|
||||||
Roles = distinctRoles.ToList(),
|
Roles = distinctRoles.ToList(),
|
||||||
Data = processedList,
|
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)
|
.Select(d => d.DepartmentName)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
// Assuming 'RateValue' in your 'Rates' table actually stores the Basic Salary
|
|
||||||
var userBasicSalary = _centralDbContext.Rates
|
var userBasicSalary = _centralDbContext.Rates
|
||||||
.Where(r => r.UserId == user.Id)
|
.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();
|
.FirstOrDefault();
|
||||||
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
@ -1900,10 +1850,9 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
if (userSetting?.Approvalflow == null)
|
if (userSetting?.Approvalflow == null)
|
||||||
return NotFound("Approval flow information not found for the user.");
|
return NotFound("Approval flow information not found for the user.");
|
||||||
|
|
||||||
// Public holidays for the table display (already fetched and formatted)
|
|
||||||
var publicHolidaysForTable = _centralDbContext.Holidays
|
var publicHolidaysForTable = _centralDbContext.Holidays
|
||||||
.Where(h => h.StateId == userSetting.State.StateId)
|
.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();
|
.ToList();
|
||||||
|
|
||||||
var otRecords = _centralDbContext.Otregisters
|
var otRecords = _centralDbContext.Otregisters
|
||||||
@ -1916,17 +1865,17 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
DateTime otDate = o.OtDate.Date;
|
DateTime otDate = o.OtDate.Date;
|
||||||
DayOfWeek dayOfWeek = otDate.DayOfWeek;
|
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";
|
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";
|
if (dayOfWeek == DayOfWeek.Friday) dayType = "Off Day";
|
||||||
else if (dayOfWeek == DayOfWeek.Saturday) dayType = "Rest Day";
|
else if (dayOfWeek == DayOfWeek.Saturday) dayType = "Rest Day";
|
||||||
else dayType = "Normal 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";
|
if (dayOfWeek == DayOfWeek.Saturday) dayType = "Off Day";
|
||||||
else if (dayOfWeek == DayOfWeek.Sunday) dayType = "Rest Day";
|
else if (dayOfWeek == DayOfWeek.Sunday) dayType = "Rest Day";
|
||||||
@ -1934,7 +1883,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dayType = "Normal Day"; // Default if WeekendId is neither 1 nor 2
|
dayType = "Normal Day";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new
|
return new
|
||||||
@ -1953,7 +1902,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
o.OtDays,
|
o.OtDays,
|
||||||
o.UserId,
|
o.UserId,
|
||||||
Rate = userBasicSalary, // Pass the Basic Salary as 'Rate'
|
Rate = userBasicSalary, // Pass the Basic Salary as 'Rate'
|
||||||
DayType = dayType, // This is for the table display
|
DayType = dayType,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -1977,7 +1926,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
hasApproverActed = !string.IsNullOrEmpty(otStatus.HrStatus) && otStatus.HrStatus != "Pending";
|
hasApproverActed = !string.IsNullOrEmpty(otStatus.HrStatus) && otStatus.HrStatus != "Pending";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
hasApproverActed = true; // Default to disabled if role not matched or already acted
|
hasApproverActed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1991,9 +1940,9 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
filePath = otStatus.FilePath,
|
filePath = otStatus.FilePath,
|
||||||
fullNameLower = user.FullName?.ToLower(),
|
fullNameLower = user.FullName?.ToLower(),
|
||||||
flexiHour = userSetting.FlexiHour?.FlexiHour,
|
flexiHour = userSetting.FlexiHour?.FlexiHour,
|
||||||
stateId = userSetting.State.StateId, // Ensure StateId is passed
|
stateId = userSetting.State.StateId,
|
||||||
weekendId = userSetting.State.WeekendId, // Ensure WeekendId is passed
|
weekendId = userSetting.State.WeekendId,
|
||||||
rate = userBasicSalary // Send Basic Salary as 'rate' in userInfo as well for overall calculations
|
rate = userBasicSalary
|
||||||
},
|
},
|
||||||
records = otRecords,
|
records = otRecords,
|
||||||
isHoU = userSetting.Approvalflow?.HoU == currentLoggedInUserId,
|
isHoU = userSetting.Approvalflow?.HoU == currentLoggedInUserId,
|
||||||
@ -2003,7 +1952,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
otStatus.HrStatus,
|
otStatus.HrStatus,
|
||||||
approverRole,
|
approverRole,
|
||||||
hasApproverActed
|
hasApproverActed
|
||||||
// public holidays are now fetched separately in the frontend for the modal
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
@ -2333,7 +2282,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
var currentLoggedInUserId = GetCurrentLoggedInUserId();
|
var currentLoggedInUserId = GetCurrentLoggedInUserId();
|
||||||
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.Approvalflow) // Include the ApprovalFlow to get approver IDs
|
.Include(us => us.Approvalflow)
|
||||||
.Include(us => us.FlexiHour)
|
.Include(us => us.FlexiHour)
|
||||||
.FirstOrDefault(us => us.UserId == user.Id);
|
.FirstOrDefault(us => us.UserId == user.Id);
|
||||||
|
|
||||||
@ -2345,33 +2294,27 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
string? flexiHour = userSetting?.FlexiHour?.FlexiHour;
|
string? flexiHour = userSetting?.FlexiHour?.FlexiHour;
|
||||||
|
|
||||||
// --- Logic for collecting approved signatures ---
|
|
||||||
var approvedSignatures = new List<ApprovalSignatureData>();
|
var approvedSignatures = new List<ApprovalSignatureData>();
|
||||||
|
|
||||||
// Get the approval flow for the user whose OT is being viewed
|
|
||||||
var approvalFlow = userSetting?.Approvalflow;
|
var approvalFlow = userSetting?.Approvalflow;
|
||||||
|
|
||||||
if (approvalFlow != null)
|
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
|
var approvalStages = new List<(int? approverId, string statusField, DateTime? submitDate, UserModel? approverUser)>
|
||||||
// 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.
|
(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
|
foreach (var stage in approvalStages
|
||||||
.Where(s => s.approverUser != null && s.statusField == "Approved" && s.submitDate.HasValue)
|
.Where(s => s.approverUser != null && s.statusField == "Approved" && s.submitDate.HasValue)
|
||||||
.OrderBy(s => s.submitDate))
|
.OrderBy(s => s.submitDate))
|
||||||
{
|
{
|
||||||
byte[]? signatureImageBytes = null;
|
byte[]? signatureImageBytes = null;
|
||||||
// Directly query StaffSign using approver's UserId
|
|
||||||
var staffSign = _centralDbContext.Staffsign
|
var staffSign = _centralDbContext.Staffsign
|
||||||
.FirstOrDefault(ss => ss.UserId == stage.approverId.Value);
|
.FirstOrDefault(ss => ss.UserId == stage.approverId.Value);
|
||||||
|
|
||||||
@ -2379,11 +2322,10 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
{
|
{
|
||||||
ApproverName = stage.approverUser.FullName,
|
ApproverName = stage.approverUser.FullName,
|
||||||
SignatureImage = signatureImageBytes,
|
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 pdfGenerator = new OvertimePDF(_centralDbContext);
|
||||||
var stream = pdfGenerator.GenerateOvertimeTablePdf(otRecords, user, userRate, selectedMonth, logoImage, isHoU, flexiHour, approvedSignatures);
|
var stream = pdfGenerator.GenerateOvertimeTablePdf(otRecords, user, userRate, selectedMonth, logoImage, isHoU, flexiHour, approvedSignatures);
|
||||||
@ -2402,7 +2344,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
return NotFound("User not found.");
|
return NotFound("User not found.");
|
||||||
|
|
||||||
// Get the user's rate
|
|
||||||
var userRate = _centralDbContext.Rates
|
var userRate = _centralDbContext.Rates
|
||||||
.Where(r => r.UserId == user.Id)
|
.Where(r => r.UserId == user.Id)
|
||||||
.OrderByDescending(r => r.LastUpdated)
|
.OrderByDescending(r => r.LastUpdated)
|
||||||
@ -2436,7 +2377,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
logoImage = System.IO.File.ReadAllBytes(logoPath);
|
logoImage = System.IO.File.ReadAllBytes(logoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get flexi hour if exists
|
|
||||||
var flexiHour = _centralDbContext.Hrusersetting
|
var flexiHour = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.FlexiHour)
|
.Include(us => us.FlexiHour)
|
||||||
.FirstOrDefault(us => us.UserId == userId)?.FlexiHour?.FlexiHour;
|
.FirstOrDefault(us => us.UserId == userId)?.FlexiHour?.FlexiHour;
|
||||||
@ -2461,7 +2401,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
return NotFound("User not found.");
|
return NotFound("User not found.");
|
||||||
|
|
||||||
// Get the user's rate
|
|
||||||
var userRate = _centralDbContext.Rates
|
var userRate = _centralDbContext.Rates
|
||||||
.Where(r => r.UserId == user.Id)
|
.Where(r => r.UserId == user.Id)
|
||||||
.OrderByDescending(r => r.LastUpdated)
|
.OrderByDescending(r => r.LastUpdated)
|
||||||
@ -2476,7 +2415,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
? otRecords.First().OtDate
|
? otRecords.First().OtDate
|
||||||
: DateTime.Now;
|
: DateTime.Now;
|
||||||
|
|
||||||
var currentLoggedInUserId = GetCurrentLoggedInUserId(); // Assuming this is defined elsewhere in your API controller
|
var currentLoggedInUserId = GetCurrentLoggedInUserId();
|
||||||
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.Approvalflow)
|
.Include(us => us.Approvalflow)
|
||||||
@ -2491,13 +2430,11 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
string? flexiHour = userSetting?.FlexiHour?.FlexiHour;
|
string? flexiHour = userSetting?.FlexiHour?.FlexiHour;
|
||||||
|
|
||||||
// Pass _env to the OvertimeExcel constructor
|
|
||||||
var excelGenerator = new OvertimeExcel(_centralDbContext, _env);
|
var excelGenerator = new OvertimeExcel(_centralDbContext, _env);
|
||||||
|
|
||||||
var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg");
|
var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg");
|
||||||
byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null;
|
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);
|
var stream = excelGenerator.GenerateOvertimeExcel(otRecords, user, userRate, selectedMonth, isHoU, flexiHour, logoImage, isSimplifiedExport: false, otStatus);
|
||||||
|
|
||||||
string fileName = $"OvertimeReport_{user.FullName}_{DateTime.Now:yyyyMMdd}.xlsx";
|
string fileName = $"OvertimeReport_{user.FullName}_{DateTime.Now:yyyyMMdd}.xlsx";
|
||||||
@ -2516,20 +2453,17 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
if (user == null)
|
if (user == null)
|
||||||
return NotFound("User not found.");
|
return NotFound("User not found.");
|
||||||
|
|
||||||
// Get the user's rate
|
|
||||||
var userRate = _centralDbContext.Rates
|
var userRate = _centralDbContext.Rates
|
||||||
.Where(r => r.UserId == user.Id)
|
.Where(r => r.UserId == user.Id)
|
||||||
.OrderByDescending(r => r.LastUpdated)
|
.OrderByDescending(r => r.LastUpdated)
|
||||||
.FirstOrDefault()?.RateValue ?? 0m;
|
.FirstOrDefault()?.RateValue ?? 0m;
|
||||||
|
|
||||||
// Get the user's flexi hour setting
|
|
||||||
var userSetting = _centralDbContext.Hrusersetting
|
var userSetting = _centralDbContext.Hrusersetting
|
||||||
.Include(us => us.FlexiHour)
|
.Include(us => us.FlexiHour)
|
||||||
.FirstOrDefault(us => us.UserId == userId);
|
.FirstOrDefault(us => us.UserId == userId);
|
||||||
|
|
||||||
string flexiHour = userSetting?.FlexiHour?.FlexiHour;
|
string flexiHour = userSetting?.FlexiHour?.FlexiHour;
|
||||||
|
|
||||||
// Get records for the selected month/year
|
|
||||||
var startDate = new DateTime(year, month, 1);
|
var startDate = new DateTime(year, month, 1);
|
||||||
var endDate = startDate.AddMonths(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)
|
.Where(o => o.UserId == userId && o.OtDate >= startDate && o.OtDate < endDate)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Check if user is admin/PSTWAIR to show station column (logic remains same)
|
bool isAdminUser = IsAdmin(user.Id);
|
||||||
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.
|
|
||||||
|
|
||||||
// Corrected line: Pass _env to the OvertimeExcel constructor
|
|
||||||
var excelGenerator = new OvertimeExcel(_centralDbContext, _env);
|
var excelGenerator = new OvertimeExcel(_centralDbContext, _env);
|
||||||
|
|
||||||
// Get logo
|
|
||||||
var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg");
|
var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg");
|
||||||
byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null;
|
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(
|
var stream = excelGenerator.GenerateOvertimeExcel(
|
||||||
otRecords,
|
otRecords,
|
||||||
user,
|
user,
|
||||||
userRate,
|
userRate,
|
||||||
startDate,
|
startDate,
|
||||||
isHoU: false, // Set to false for regular user report
|
isHoU: false,
|
||||||
flexiHour: flexiHour,
|
flexiHour: flexiHour,
|
||||||
logoImage: logoImage,
|
logoImage: logoImage,
|
||||||
isSimplifiedExport: true // Set to true for the simplified report from OtRecords
|
isSimplifiedExport: true
|
||||||
);
|
);
|
||||||
|
|
||||||
string fileName = $"OvertimeReport_{user.FullName}_{month}_{year}.xlsx";
|
string fileName = $"OvertimeReport_{user.FullName}_{month}_{year}.xlsx";
|
||||||
|
|||||||
@ -40,6 +40,7 @@ internal class Program
|
|||||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"))
|
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"))
|
||||||
.WriteTo.Console()
|
.WriteTo.Console()
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
// Set default session to 30 minutes
|
// Set default session to 30 minutes
|
||||||
builder.Services.AddSession(options =>
|
builder.Services.AddSession(options =>
|
||||||
{
|
{
|
||||||
@ -54,6 +55,7 @@ internal class Program
|
|||||||
mysqlOptions => mysqlOptions.CommandTimeout(120)
|
mysqlOptions => mysqlOptions.CommandTimeout(120)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
//builder.Services.AddDbContext<InventoryDBContext>(options =>
|
//builder.Services.AddDbContext<InventoryDBContext>(options =>
|
||||||
//{
|
//{
|
||||||
// options.UseMySql(inventoryConnectionString, new MySqlServerVersion(new Version(8, 0, 39)),
|
// options.UseMySql(inventoryConnectionString, new MySqlServerVersion(new Version(8, 0, 39)),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user