+
+ Valid After Office Hours ranges: 00:00 - {{ flexiTimes.start }} or {{ flexiTimes.end }} - 00:00
+
+
@@ -163,9 +175,12 @@
userState: null,
publicHolidays: [],
userDepartmentId: null, // The department ID from the current user's profile
- isUserAdmin: false,
- departmentName: "",
+ isUserAdmin: false,
+ departmentName: "",
areUserSettingsComplete: false,
+ // Define these constants in data for access via `this`
+ minAllowedFromMidnightTo: "16:30", // 4:30 PM
+ maxAllowedFromMidnightTo: "23:30", // 11:30 PM
breakOptions: Array.from({ length: 15 }, (_, i) => {
const totalMinutes = i * 30;
const hours = Math.floor(totalMinutes / 60);
@@ -197,11 +212,24 @@
if (this.selectedMarineStation) {
return parseInt(this.selectedMarineStation);
}
- return null;
+ return null;
},
requiresStation() {
return this.showAirDropdown || this.showMarineDropdown;
- }
+ },
+ isOfficeHoursDisabled() {
+ return this.detectedDayType === "Weekday";
+ },
+ flexiTimes() {
+ if (this.userFlexiHour === 'N/A') {
+ return { start: null, end: null };
+ }
+ const parts = this.userFlexiHour.split(' - ');
+ return {
+ start: parts[0],
+ end: parts[1]
+ };
+ },
},
watch: {
airStationList: {
@@ -255,6 +283,15 @@
if (selectElement.length && selectElement.val() !== newVal) {
selectElement.val(newVal).trigger('change.select2');
}
+ },
+ detectedDayType(newVal) {
+ // When the day type changes, clear office hours if it becomes a weekday
+ if (newVal === "Weekday") {
+ this.officeFrom = "";
+ this.officeTo = "";
+ this.officeBreak = 0;
+ }
+ this.calculateOTAndBreak(); // Recalculate OT and break when day type changes
}
},
async mounted() {
@@ -299,9 +336,9 @@
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin");
- this.userDepartmentId = this.currentUser?.department?.departmentId;
+ this.userDepartmentId = this.currentUser?.department?.departmentId;
- this.isUserAdmin = isSuperAdmin || isSystemAdmin;
+ this.isUserAdmin = isSuperAdmin || isSystemAdmin;
if (!this.isUserAdmin) {
if (this.userDepartmentId === 2) {
@@ -375,7 +412,7 @@
const roundedMinutes = remainder < 15 ? totalMinutes - remainder : totalMinutes + (30 - remainder);
- const adjustedHour = Math.floor(roundedMinutes / 60) % 24;
+ const adjustedHour = Math.floor(roundedMinutes / 60) % 24;
const adjustedMinute = roundedMinutes % 60;
return `${adjustedHour.toString().padStart(2, '0')}:${adjustedMinute.toString().padStart(2, '0')}`;
@@ -442,8 +479,43 @@
},
handleDateChange() {
this.updateDayType();
+ // Clear office hour fields if it's a weekday
+ if (this.isOfficeHoursDisabled) {
+ this.officeFrom = "";
+ this.officeTo = "";
+ this.officeBreak = 0;
+ }
this.calculateOTAndBreak();
},
+ // Moved and updated validateTimeRangeForSubmission to be a method
+ validateTimeRangeForSubmission(fromTime, toTime, label) {
+ if (!fromTime || !toTime) return true;
+
+ const start = this.parseTime(fromTime);
+ const end = this.parseTime(toTime);
+
+ // Use 'this' to access data properties
+ const minAllowedFromMinutesForMidnightTo = this.parseTime(this.minAllowedFromMidnightTo).hours * 60 + this.parseTime(this.minAllowedFromMidnightTo).minutes;
+ const maxAllowedFromMidnightTo = this.parseTime(this.maxAllowedFromMidnightTo).hours * 60 + this.parseTime(this.maxAllowedFromMidnightTo).minutes;
+
+ const startMinutes = start.hours * 60 + start.minutes;
+ const endMinutes = end.hours * 60 + end.minutes;
+
+ if (end.hours === 0 && end.minutes === 0) { // If 'To' is 00:00 (midnight)
+ if (fromTime === "00:00") {
+ alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`);
+ return false;
+ }
+ if (startMinutes < minAllowedFromMinutesForMidnightTo || startMinutes > maxAllowedFromMidnightTo) {
+ alert(`Invalid ${label} Time: If 'To' is 12:00 am (00:00), 'From' must start between end of your flexi hour to 11:30 pm on the same day.`);
+ return false;
+ }
+ } else if (endMinutes <= startMinutes) {
+ alert(`Invalid ${label} Time: 'To' time must be later than 'From' time for durations within the same day.`);
+ return false;
+ }
+ return true;
+ },
async addOvertime() {
if (!this.areUserSettingsComplete) {
alert("Cannot save overtime: Your essential user settings are incomplete. Please contact IT or HR.");
@@ -485,38 +557,11 @@
return;
}
- // Validate time ranges according to the new rule: TO 00:00 only if FROM is 4:30 PM - 11:30 PM
- const validateTimeRangeForSubmission = (fromTime, toTime, label) => {
- if (!fromTime || !toTime) return true;
-
- const start = this.parseTime(fromTime);
- const end = this.parseTime(toTime);
-
- const minAllowedFromMinutesForMidnightTo = 16 * 60 + 30; // 4:30 PM
- const maxAllowedFromMinutesForMidnightTo = 23 * 60 + 30; // 11:30 PM
- const startMinutes = start.hours * 60 + start.minutes;
-
- if (end.hours === 0 && end.minutes === 0) { // If 'To' is 00:00 (midnight)
- if (fromTime === "00:00") {
- alert(`Invalid ${label} Time: 'From' and 'To' cannot both be 00:00 (midnight).`);
- 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) {
- 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;
- }
- } else if (end.hours * 60 + end.minutes <= start.hours * 60 + start.minutes) {
- alert(`Invalid ${label} Time: 'To' time must be later than 'From' time for durations within the same day.`);
- return false;
- }
- return true;
- };
-
- if (hasOfficeHours && !validateTimeRangeForSubmission(this.officeFrom, this.officeTo, 'Office Hour')) {
+ // Now call the shared validation method correctly using 'this'
+ if (hasOfficeHours && !this.validateTimeRangeForSubmission(this.officeFrom, this.officeTo, 'Office Hour')) {
return;
}
- if (hasAfterHours && !validateTimeRangeForSubmission(this.afterFrom, this.afterTo, 'After Office Hour')) {
+ if (hasAfterHours && !this.validateTimeRangeForSubmission(this.afterFrom, this.afterTo, 'After Office Hour')) {
return;
}
@@ -528,7 +573,7 @@
afterFrom: this.afterFrom ? this.formatTime(this.afterFrom) : null,
afterTo: this.afterTo ? this.formatTime(this.afterTo) : null,
afterBreak: this.afterBreak || null,
- stationId: stationIdToSubmit,
+ stationId: stationIdToSubmit,
otDescription: this.otDescription.trim().split(/\s+/).slice(0, 50).join(' '),
otDays: this.detectedDayType,
userId: this.userId,
@@ -545,8 +590,8 @@
});
if (!response.ok) {
- const errorText = await response.text();
- throw new Error(`HTTP error! status: ${response.status} - ${errorText}`);
+ const errorMessage = await response.text();
+ throw new Error(errorMessage);
}
const result = await response.json();
@@ -560,8 +605,8 @@
}
} catch (error) {
- console.error("Error adding overtime:", error);
- alert(`Failed to save overtime. Error: ${error.message}`);
+ // Display the specific error message from the backend.
+ alert(`${error.message}`);
}
},
@@ -610,20 +655,19 @@
this.afterFrom = "";
this.afterTo = "";
this.afterBreak = 0;
- this.selectedAirStation = "";
- this.selectedMarineStation = "";
+ this.selectedAirStation = "";
+ this.selectedMarineStation = "";
const airSelect = $('#airStationDropdown');
if (airSelect.length && airSelect.data('select2')) {
- airSelect.val('').trigger('change.select2');
+ airSelect.val('').trigger('change.select2');
}
const marineSelect = $('#marineStationDropdown');
if (marineSelect.length && marineSelect.data('select2')) {
- marineSelect.val('').trigger('change.select2');
+ marineSelect.val('').trigger('change.select2');
}
this.otDescription = "";
- this.userFlexiHour = "";
this.detectedDayType = "";
this.totalOTHours = "0 hr 0 min";
this.totalBreakHours = "0 hr 0 min";
diff --git a/Controllers/API/OvertimeAPI.cs b/Controllers/API/OvertimeAPI.cs
index 12c7166..a1c5899 100644
--- a/Controllers/API/OvertimeAPI.cs
+++ b/Controllers/API/OvertimeAPI.cs
@@ -1,32 +1,33 @@
using Azure.Core;
+using DocumentFormat.OpenXml.InkML;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
+using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
using Mono.TextTemplating;
using Newtonsoft.Json;
using PSTW_CentralSystem.Areas.OTcalculate.Models;
+using PSTW_CentralSystem.Areas.OTcalculate.Services;
using PSTW_CentralSystem.Controllers.API;
using PSTW_CentralSystem.Controllers.API.Inventory;
using PSTW_CentralSystem.DBContext;
using PSTW_CentralSystem.Models;
-using System.ComponentModel.Design;
-using System.Data;
-using System;
-using System.Threading.Tasks;
-using System.Diagnostics;
-using System.Reflection;
-using static System.Collections.Specialized.BitVector32;
-using System.Security.Claims;
-using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
-using Microsoft.AspNetCore.Mvc.Rendering;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
-using PSTW_CentralSystem.Areas.OTcalculate.Services;
-using Microsoft.AspNetCore.Hosting;
-using DocumentFormat.OpenXml.InkML;
+using RestSharp.Extensions;
+using System;
+using System.ComponentModel.Design;
+using System.Data;
+using System.Diagnostics;
+using System.Reflection;
+using System.Security.Claims;
+using System.Threading.Tasks;
using static PSTW_CentralSystem.Areas.OTcalculate.Models.OtRegisterModel;
+using static System.Collections.Specialized.BitVector32;
namespace PSTW_CentralSystem.Controllers.API
@@ -863,7 +864,7 @@ namespace PSTW_CentralSystem.Controllers.API
#endregion
- #region OtRegister
+ #region Ot Register
[HttpGet("GetStationsByDepartment")]
public async Task GetStationsByDepartment([FromQuery] int? departmentId)
{
@@ -924,11 +925,11 @@ namespace PSTW_CentralSystem.Controllers.API
int? userDepartmentId = userWithDepartment?.Department?.DepartmentId;
bool stationRequired = false;
- if (userDepartmentId == 2 || userDepartmentId == 3)
+ if (userDepartmentId == 2 || userDepartmentId == 3)
{
stationRequired = true;
}
- else if (isSuperAdmin || isSystemAdmin)
+ else if (isSuperAdmin || isSystemAdmin)
{
stationRequired = true;
}
@@ -953,11 +954,11 @@ namespace PSTW_CentralSystem.Controllers.API
}
TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0);
- TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0);
+ TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0);
if (officeFrom.HasValue && officeTo.HasValue)
{
- if (officeTo == TimeSpan.Zero)
+ if (officeTo == TimeSpan.Zero)
{
if (officeFrom == TimeSpan.Zero)
{
@@ -966,10 +967,10 @@ namespace PSTW_CentralSystem.Controllers.API
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 end of your flexi hour to 11:30 pm on the same day.");
}
}
- else if (officeTo <= officeFrom)
+ else if (officeTo <= officeFrom)
{
return BadRequest("Invalid Office Hour Time: 'To' time must be later than 'From' time (same day only).");
}
@@ -977,7 +978,7 @@ namespace PSTW_CentralSystem.Controllers.API
if (afterFrom.HasValue && afterTo.HasValue)
{
- if (afterTo == TimeSpan.Zero)
+ if (afterTo == TimeSpan.Zero)
{
if (afterFrom == TimeSpan.Zero)
{
@@ -986,10 +987,10 @@ namespace PSTW_CentralSystem.Controllers.API
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 end of your flexi hour to 11:30 pm on the same day.");
}
}
- else if (afterTo <= afterFrom)
+ else if (afterTo <= afterFrom)
{
return BadRequest("Invalid After Office Hour Time: 'To' time must be later than 'From' time (same day only).");
}
@@ -1000,15 +1001,77 @@ namespace PSTW_CentralSystem.Controllers.API
return BadRequest("Please enter either Office Hours or After Office Hours.");
}
+ // Convert the request date to a DateOnly for comparison
+ DateOnly requestDate = DateOnly.FromDateTime(request.OtDate);
+
+ // Fetch existing overtime records for the same user and date
+ var existingOvertimeRecords = await _centralDbContext.Otregisters
+ .Where(o => o.UserId == request.UserId && DateOnly.FromDateTime(o.OtDate) == requestDate)
+ .ToListAsync();
+
+ // Check for overlaps with existing records
+ foreach (var existingRecord in existingOvertimeRecords)
+ {
+ // Convert existing record times to actual TimeSpan objects
+ var existingOfficeFrom = existingRecord.OfficeFrom;
+ var existingOfficeTo = existingRecord.OfficeTo;
+ var existingAfterFrom = existingRecord.AfterFrom;
+ var existingAfterTo = existingRecord.AfterTo;
+
+ // Function to check for overlap between two time ranges
+ Func CheckOverlap =
+ (newStart, newEnd, existingStart, existingEnd) =>
+ {
+ if (!newStart.HasValue || !newEnd.HasValue || !existingStart.HasValue || !existingEnd.HasValue)
+ {
+ return false; // No overlap if either range is incomplete
+ }
+
+ // Handle midnight (00:00) as end of day (24:00) for comparison purposes
+ TimeSpan adjustedNewEnd = (newEnd == TimeSpan.Zero && newStart != TimeSpan.Zero) ? TimeSpan.FromHours(24) : newEnd.Value;
+ TimeSpan adjustedExistingEnd = (existingEnd == TimeSpan.Zero && existingStart != TimeSpan.Zero) ? TimeSpan.FromHours(24) : existingEnd.Value;
+
+
+ // Check for overlap:
+ // New range starts before existing range ends AND
+ // New range ends after existing range starts
+ return (newStart.Value < adjustedExistingEnd && adjustedNewEnd > existingStart.Value);
+ };
+
+ // Check for overlap between new Office Hours and existing Office Hours
+ if (CheckOverlap(officeFrom, officeTo, existingOfficeFrom, existingOfficeTo))
+ {
+ return BadRequest("Your Office Hours entry overlaps with another record on this date. Kindly adjust your time.");
+ }
+
+ // Check for overlap between new After Office Hours and existing After Office Hours
+ if (CheckOverlap(afterFrom, afterTo, existingAfterFrom, existingAfterTo))
+ {
+ return BadRequest("Your Office Hours entry overlaps with another record on this date. Kindly adjust your time.");
+ }
+
+ // Check for overlap between new Office Hours and existing After Office Hours
+ if (CheckOverlap(officeFrom, officeTo, existingAfterFrom, existingAfterTo))
+ {
+ return BadRequest("Your Office Hours entry overlaps with another record on this date. Kindly adjust your time.");
+ }
+
+ // Check for overlap between new After Office Hours and existing Office Hours
+ if (CheckOverlap(afterFrom, afterTo, existingOfficeFrom, existingOfficeTo))
+ {
+ return BadRequest("Your Office Hours entry overlaps with another record on this date. Kindly adjust your time.");
+ }
+ }
+
var newRecord = new OtRegisterModel
{
OtDate = request.OtDate,
OfficeFrom = officeFrom,
OfficeTo = officeTo,
- OfficeBreak = request.OfficeBreak,
+ OfficeBreak = request.OfficeBreak,
AfterFrom = afterFrom,
AfterTo = afterTo,
- AfterBreak = request.AfterBreak,
+ AfterBreak = request.AfterBreak,
StationId = request.StationId,
OtDescription = request.OtDescription,
OtDays = request.OtDays,
@@ -1393,6 +1456,11 @@ namespace PSTW_CentralSystem.Controllers.API
return BadRequest(new { message = timeValidationError });
}
+ if (HasOverlappingRecords(model, model.UserId))
+ {
+ return BadRequest(new { message = "Your Office Hours entry overlaps with another record on this date. Kindly adjust your time." });
+ }
+
var existing = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == model.OvertimeId);
if (existing == null)
{
@@ -1442,7 +1510,7 @@ namespace PSTW_CentralSystem.Controllers.API
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 end of your flexi hour to 11:30 pm on the same day.";
}
}
else if (officeTo <= officeFrom)
@@ -1462,7 +1530,7 @@ namespace PSTW_CentralSystem.Controllers.API
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 end of your flexi hour to 11:30 pm on the same day.";
}
}
else if (afterTo <= afterFrom)
@@ -1478,6 +1546,82 @@ namespace PSTW_CentralSystem.Controllers.API
return null;
}
+
+ private bool HasOverlappingRecords(OtRegisterUpdateDto model, int userId)
+ {
+ // Retrieve all other overtime records for the same user and date,
+ // excluding the current record being updated.
+ var existingRecords = _centralDbContext.Otregisters
+ .Where(o => o.UserId == userId && o.OtDate.Date == model.OtDate.Date && o.OvertimeId != model.OvertimeId)
+ .ToList();
+
+ // The logic to check for overlap between two time ranges A and B is:
+ // A_start < B_end AND B_start < A_end
+ // This handles all scenarios, including one range being entirely inside another.
+
+ // Check for overlap with the Office Hours block
+ if (!string.IsNullOrEmpty(model.OfficeFrom) && !string.IsNullOrEmpty(model.OfficeTo))
+ {
+ var newOfficeStart = TimeSpan.Parse(model.OfficeFrom);
+ var newOfficeEnd = TimeSpan.Parse(model.OfficeTo);
+ foreach (var record in existingRecords)
+ {
+ TimeSpan? existingOfficeStart = record.OfficeFrom;
+ TimeSpan? existingOfficeEnd = record.OfficeTo;
+ TimeSpan? existingAfterStart = record.AfterFrom;
+ TimeSpan? existingAfterEnd = record.AfterTo;
+
+ // Check against existing Office Hours
+ if (existingOfficeStart.HasValue && existingOfficeEnd.HasValue)
+ {
+ if (newOfficeStart < existingOfficeEnd.Value && existingOfficeStart.Value < newOfficeEnd)
+ {
+ return true;
+ }
+ }
+ // Check against existing After Office Hours
+ if (existingAfterStart.HasValue && existingAfterEnd.HasValue)
+ {
+ if (newOfficeStart < existingAfterEnd.Value && existingAfterStart.Value < newOfficeEnd)
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ // Check for overlap with the After Office Hours block
+ if (!string.IsNullOrEmpty(model.AfterFrom) && !string.IsNullOrEmpty(model.AfterTo))
+ {
+ var newAfterStart = TimeSpan.Parse(model.AfterFrom);
+ var newAfterEnd = TimeSpan.Parse(model.AfterTo);
+ foreach (var record in existingRecords)
+ {
+ TimeSpan? existingOfficeStart = record.OfficeFrom;
+ TimeSpan? existingOfficeEnd = record.OfficeTo;
+ TimeSpan? existingAfterStart = record.AfterFrom;
+ TimeSpan? existingAfterEnd = record.AfterTo;
+
+ // Check against existing Office Hours
+ if (existingOfficeStart.HasValue && existingOfficeEnd.HasValue)
+ {
+ if (newAfterStart < existingOfficeEnd.Value && existingOfficeStart.Value < newAfterEnd)
+ {
+ return true;
+ }
+ }
+ // Check against existing After Office Hours
+ if (existingAfterStart.HasValue && existingAfterEnd.HasValue)
+ {
+ if (newAfterStart < existingAfterEnd.Value && existingAfterStart.Value < newAfterEnd)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
#endregion
#region OT Status
@@ -1993,12 +2137,21 @@ namespace PSTW_CentralSystem.Controllers.API
return NotFound(new { message = "Overtime record not found." });
}
+ // **NEW LOGIC: Check for overlapping entries**
+ if (IsOverlapping(updatedRecordDto, existingRecord.UserId, updatedRecordDto.OvertimeId))
+ {
+ return BadRequest(new { message = "The new overtime time range overlaps with an existing entry for the same date." });
+ }
+
+ // ... (rest of your existing logic for finding otStatus, permissions, and logging)
+ // The previous code for validation will be moved to a helper method or retained if it's still needed
var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == updatedRecordDto.StatusId);
if (otStatus == null)
{
return NotFound("OT status not found.");
}
+ // ... (rest of the code for checking permissions and logging)
var currentLoggedInUserId = GetCurrentLoggedInUserId();
string approverRole = GetApproverRole(currentLoggedInUserId, otStatus.StatusId);
@@ -2029,6 +2182,7 @@ namespace PSTW_CentralSystem.Controllers.API
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
+ // Update the existing record with the new data
existingRecord.OtDate = updatedRecordDto.OtDate;
existingRecord.OfficeFrom = ParseTimeStringToTimeSpan(updatedRecordDto.OfficeFrom);
existingRecord.OfficeTo = ParseTimeStringToTimeSpan(updatedRecordDto.OfficeTo);
@@ -2075,6 +2229,78 @@ namespace PSTW_CentralSystem.Controllers.API
return Ok(new { message = "Overtime record updated successfully and changes logged." });
}
+ // **NEW HELPER METHOD: IsOverlapping**
+ private bool IsOverlapping(OtRegisterEditDto updatedRecord, int userId, int currentRecordId)
+ {
+ // Filter existing records for the same date, excluding the one being edited
+ var existingRecords = _centralDbContext.Otregisters
+ .Where(o => o.UserId == userId && o.OtDate.Date == updatedRecord.OtDate.Date && o.OvertimeId != currentRecordId)
+ .ToList();
+
+ // Check for overlaps with the new time ranges
+ TimeSpan? newOfficeFrom = ParseTimeStringToTimeSpan(updatedRecord.OfficeFrom);
+ TimeSpan? newOfficeTo = ParseTimeStringToTimeSpan(updatedRecord.OfficeTo);
+ TimeSpan? newAfterFrom = ParseTimeStringToTimeSpan(updatedRecord.AfterFrom);
+ TimeSpan? newAfterTo = ParseTimeStringToTimeSpan(updatedRecord.AfterTo);
+
+ foreach (var existing in existingRecords)
+ {
+ // Check if the new office hours overlap with any existing time range
+ if (newOfficeFrom.HasValue && newOfficeTo.HasValue)
+ {
+ if (CheckOverlapBetween(newOfficeFrom.Value, newOfficeTo.Value, existing.OfficeFrom, existing.OfficeTo) ||
+ CheckOverlapBetween(newOfficeFrom.Value, newOfficeTo.Value, existing.AfterFrom, existing.AfterTo))
+ {
+ return true;
+ }
+ }
+
+ // Check if the new after office hours overlap with any existing time range
+ if (newAfterFrom.HasValue && newAfterTo.HasValue)
+ {
+ if (CheckOverlapBetween(newAfterFrom.Value, newAfterTo.Value, existing.OfficeFrom, existing.OfficeTo) ||
+ CheckOverlapBetween(newAfterFrom.Value, newAfterTo.Value, existing.AfterFrom, existing.AfterTo))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ // **NEW HELPER METHOD: CheckOverlapBetween**
+ private bool CheckOverlapBetween(TimeSpan start1, TimeSpan end1, TimeSpan? start2, TimeSpan? end2)
+ {
+ // If either of the second time range's values is missing, there can be no overlap.
+ if (!start2.HasValue || !end2.HasValue)
+ {
+ return false;
+ }
+
+ // Create local, non-nullable variables to work with.
+ TimeSpan s1 = start1;
+ TimeSpan e1 = end1;
+ TimeSpan s2 = start2.Value;
+ TimeSpan e2 = end2.Value;
+
+ // Handle overnight cases for both ranges by adding 24 hours to the end time
+ // if it's on or before the start time.
+ // TimeSpan.FromHours(24) is a cleaner way to express this.
+ if (e1 <= s1)
+ {
+ e1 = e1.Add(TimeSpan.FromHours(24));
+ }
+ if (e2 <= s2)
+ {
+ e2 = e2.Add(TimeSpan.FromHours(24));
+ }
+
+ // Overlap exists if the start of one range is before the end of the other,
+ // AND the start of the other is before the end of the first.
+ return s1 < e2 && s2 < e1;
+ }
+
[HttpDelete("DeleteOvertimeInOtReview/{id}")]
public IActionResult DeleteOvertimeInOtReview(int id)
{