Fix lunch-break guard being skipped on same-day bookings

getAvailableTimes() overloaded $dayStartTime: it starts as the worker's
real shift start, but for same-day requests it is overwritten with the
earliest bookable slot (next 15-min block + 1 hour). The lunch-break
setup further down still read it as the shift start, so $shiftSeconds
measured the *remaining* day instead of the whole shift. Once that fell
under 6 hours, $lunchRequired went false and the guard was skipped
entirely -- every free slot was offered, including ones that leave no
room for a lunch break.

Observed on prod on 2026-08-21: at 12:17 the cutoff pushed the start to
13:30, so Kateryna's 10:00-18:00 shift measured 4.5h and a 13:30-15:30
booking was accepted even though it consumed the last possible lunch
slot. booking_process() re-validates through the same function, so the
server-side check agreed.

Capture the real shift start in $shiftStartTime before the cutoff runs,
and use it for both $shiftSeconds and the computeLunchBreak() call. The
latter also fixes a second symptom of the same overload: the lunch
window was being clamped to the cutoff rather than to the actual shift.

Verified end-to-end against pages/ajax on test: with the bug a slot that
destroys the last lunch break is offered, with the fix it is withheld.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZv7QGLyRYwdD8NyimYbQQ
This commit is contained in:
Ubuntu
2026-08-21 11:14:18 +00:00
co-authored by Claude Opus 5
parent 1a364f21b3
commit 4c823f92ca
+6 -2
View File
@@ -622,6 +622,10 @@ class Service_model extends CI_Model {
$finishTime = new DateTime($selectedDate . ' ' . $schedule->end_time, new DateTimeZone('Europe/Oslo')); $finishTime = new DateTime($selectedDate . ' ' . $schedule->end_time, new DateTimeZone('Europe/Oslo'));
} }
// A műszak valódi kezdete - $dayStartTime-ot a mai napra alább felülírjuk a legkorábban
// foglalható időponttal, az ebédszünet számítása viszont a tényleges műszakkal dolgozik.
$shiftStartTime = clone $dayStartTime;
// --- ÚJ: ma foglalva csak (következő 15 perces blokk + 1 óra) UTÁN legyen időpont --- // --- ÚJ: ma foglalva csak (következő 15 perces blokk + 1 óra) UTÁN legyen időpont ---
if ($selectedDate == date('Y-m-d')) { if ($selectedDate == date('Y-m-d')) {
// használd a helyi (Oslo) időt, ne az esetleges UTC defaultot // használd a helyi (Oslo) időt, ne az esetleges UTC defaultot
@@ -648,7 +652,7 @@ class Service_model extends CI_Model {
// --- Lunch break setup --- // --- Lunch break setup ---
// Pre-fetch all bookings for the day so we can do hypothetical checks per slot // Pre-fetch all bookings for the day so we can do hypothetical checks per slot
$workerRow = $this->db->query("SELECT lunch_window_start, lunch_window_end, lunch_preferred_time FROM workers WHERE worker_id = ? LIMIT 1", array($worker_id))->row(); $workerRow = $this->db->query("SELECT lunch_window_start, lunch_window_end, lunch_preferred_time FROM workers WHERE worker_id = ? LIMIT 1", array($worker_id))->row();
$shiftSeconds = $finishTime->getTimestamp() - $dayStartTime->getTimestamp(); $shiftSeconds = $finishTime->getTimestamp() - $shiftStartTime->getTimestamp();
$lunchRequired = $workerRow && $workerRow->lunch_window_start && $workerRow->lunch_window_end && $shiftSeconds >= 6 * 3600; $lunchRequired = $workerRow && $workerRow->lunch_window_start && $workerRow->lunch_window_end && $shiftSeconds >= 6 * 3600;
$allBookings = []; $allBookings = [];
@@ -705,7 +709,7 @@ class Service_model extends CI_Model {
]]); ]]);
$canStillBreak = $this->computeLunchBreak( $canStillBreak = $this->computeLunchBreak(
$workerRow->lunch_window_start, $workerRow->lunch_window_end, $workerRow->lunch_window_start, $workerRow->lunch_window_end,
$selectedDate, $hypo, $finishTime, $dayStartTime, $selectedDate, $hypo, $finishTime, $shiftStartTime,
$workerRow->lunch_preferred_time $workerRow->lunch_preferred_time
) !== null; ) !== null;
if (!$canStillBreak) continue; // booking this slot would eliminate the only break opportunity if (!$canStillBreak) continue; // booking this slot would eliminate the only break opportunity