fix: review #19 — booking overlap check (FOR UPDATE), wait estimate excludes in-service
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s

This commit is contained in:
Jonathan Miller
2026-06-21 17:55:39 -05:00
parent 43a2f45666
commit 38d43cfd70
2 changed files with 45 additions and 11 deletions
@@ -82,18 +82,52 @@ class BookingHelper
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$booking = (object) [
'client_contact_id' => $clientContactId,
'stylist_id' => $stylistId,
'service_id' => $serviceId,
'start_time' => $datetime,
'status' => 'confirmed',
'created_at' => Factory::getDate()->toSql(),
];
// Get service duration for overlap check
$db->setQuery($db->getQuery(true)->select('duration_minutes')->from('#__mokosuitebeauty_services')->where('id = ' . (int) $serviceId));
$duration = (int) $db->loadResult();
$db->insertObject('#__mokosuitebeauty_bookings', $booking, 'id');
if ($duration <= 0) {
return (object) ['success' => false, 'error' => 'Invalid service'];
}
return (object) ['success' => true, 'booking_id' => (int) $booking->id];
$db->transactionStart();
try {
// Check for overlapping bookings with row lock
$db->setQuery(
'SELECT COUNT(*) FROM #__mokosuitebeauty_bookings b'
. ' JOIN #__mokosuitebeauty_services s ON s.id = b.service_id'
. ' WHERE b.stylist_id = ' . (int) $stylistId
. ' AND b.status NOT IN (' . $db->quote('cancelled') . ',' . $db->quote('no_show') . ')'
. ' AND b.start_time < DATE_ADD(' . $db->quote($datetime) . ', INTERVAL ' . $duration . ' MINUTE)'
. ' AND DATE_ADD(b.start_time, INTERVAL s.duration_minutes MINUTE) > ' . $db->quote($datetime)
. ' FOR UPDATE'
);
$conflicts = (int) $db->loadResult();
if ($conflicts > 0) {
$db->transactionRollback();
return (object) ['success' => false, 'error' => 'Time slot is no longer available'];
}
$booking = (object) [
'client_contact_id' => $clientContactId,
'stylist_id' => $stylistId,
'service_id' => $serviceId,
'start_time' => $datetime,
'status' => 'confirmed',
'created_at' => Factory::getDate()->toSql(),
];
$db->insertObject('#__mokosuitebeauty_bookings', $booking, 'id');
$db->transactionCommit();
return (object) ['success' => true, 'booking_id' => (int) $booking->id];
} catch (\Throwable $e) {
$db->transactionRollback();
return (object) ['success' => false, 'error' => 'Booking failed'];
}
}
/**
@@ -77,7 +77,7 @@ class WalkInHelper
->select('COALESCE(SUM(s.duration_minutes), 0)')
->from($db->quoteName('#__mokosuitebeauty_walkins', 'w'))
->join('LEFT', $db->quoteName('#__mokosuitebeauty_services', 's') . ' ON s.id = w.service_id')
->where($db->quoteName('w.status') . ' IN (' . $db->quote('waiting') . ',' . $db->quote('in_service') . ')'));
->where($db->quoteName('w.status') . ' = ' . $db->quote('waiting')));
$totalMinutes = (int) $db->loadResult();