Apply the worker capability filter to the manage-booking flow

The per-worker service allow-list was only enforced on the new-booking form.
On the guest manage-booking page a worker could still be offered for services
they do not perform:

- manage_booking() branched on $firstService, which was never assigned - the
  resolve loop above it used $resolvedService - so the guarded branch was dead
  and the worker list fell through to getActiveWorkers(), filtered by vertical
  only. Assigning it activates both the category filter and the capability
  check, and with it the $isOtherCategory checkbox disabling in the view.
- The page's own getAvailableWorkersByServiceCategorySlug() never sent
  service_ids, so re-picking services asked for a category-only worker list.
  It now mirrors the booking form, including the empty-result message.
- setWorker() clears any time already chosen: changing worker or services left
  a stale selection with the submit button still enabled.

manage_booking_process() did re-check capability server-side, but every
rejection there answered with raw JSON, which this non-AJAX form renders as an
unstyled blob in the guest's browser. All eight rejections, plus the
cancellation cutoff, now go through _booking_error(), which takes an optional
back URL so the guest returns to their own manage page rather than an empty
booking form. Adds the cutoff_passed message in all three languages and makes
the error title and button label reflect which flow failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYGSbK1erKv7VG1pvdPEjG
This commit is contained in:
Ubuntu
2026-08-24 17:06:08 +00:00
co-authored by Claude Opus 5
parent 13e1b1a672
commit ce7400578a
5 changed files with 103 additions and 34 deletions
+27 -23
View File
@@ -315,7 +315,7 @@ class Pages extends CI_Controller {
* form is a normal full-page POST, so whatever this outputs is what the * form is a normal full-page POST, so whatever this outputs is what the
* customer actually sees in their browser. * customer actually sees in their browser.
*/ */
private function _booking_error($errorCode, $statusCode = 409){ private function _booking_error($errorCode, $statusCode = 409, $backUrl = ''){
$this->load->helper('url'); $this->load->helper('url');
$lang = vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : ''); $lang = vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : '');
@@ -332,7 +332,10 @@ class Pages extends CI_Controller {
'selectedLang' => $lang, 'selectedLang' => $lang,
'subpage' => $vertical['slug'], 'subpage' => $vertical['slug'],
'vertical' => $vertical, 'vertical' => $vertical,
'bookingErrorCode' => $errorCode 'bookingErrorCode' => $errorCode,
// Manage-booking errors must return the guest to their own manage page,
// not to a fresh booking form. Empty = the new-booking default.
'bookingBackUrl' => $backUrl
); );
$this->output->set_status_header($statusCode); $this->output->set_status_header($statusCode);
@@ -854,10 +857,14 @@ class Pages extends CI_Controller {
// wrong vertical's branding. // wrong vertical's branding.
$serviceIds = unserialize($booking->service_ids); $serviceIds = unserialize($booking->service_ids);
$subpage = 'barber'; $subpage = 'barber';
$firstService = NULL;
if(is_array($serviceIds)){ if(is_array($serviceIds)){
foreach($serviceIds as $serviceIdItem){ foreach($serviceIds as $serviceIdItem){
$resolvedService = $this->Service_model->getServiceById($serviceIdItem); $resolvedService = $this->Service_model->getServiceById($serviceIdItem);
if($resolvedService && $resolvedService->service_type !== ''){ if($resolvedService && $resolvedService->service_type !== ''){
// Kept for the worker filter below: the category (and therefore the
// capability check) is derived from this same resolved service.
$firstService = $resolvedService;
$subpage = $resolvedService->service_type; $subpage = $resolvedService->service_type;
break; break;
} }
@@ -893,7 +900,7 @@ class Pages extends CI_Controller {
// Filter workers to those qualified for the booking's service category, // Filter workers to those qualified for the booking's service category,
// so the guest can't switch to a worker who doesn't perform these services. // so the guest can't switch to a worker who doesn't perform these services.
$categorySlug = ''; $categorySlug = '';
if(isset($firstService) && $firstService){ if($firstService){
$category = $this->Service_model->getServiceCategoryById($firstService->service_category_id); $category = $this->Service_model->getServiceCategoryById($firstService->service_category_id);
if($category){ if($category){
$categorySlug = $category->serv_cat_slug; $categorySlug = $category->serv_cat_slug;
@@ -928,11 +935,17 @@ class Pages extends CI_Controller {
return; return;
} }
// This form is a normal POST, not AJAX, so every rejection below is rendered
// as the shared booking error page. Returning raw JSON showed the guest an
// unstyled {"error":...} blob after they had filled the whole form in.
// Use the stored token, not the posted one: it is echoed straight into the
// error page's back link.
$manageBackUrl = SITEURL.vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : '').'/manage-booking/'.$booking->manage_token;
$now = new DateTime('now', new DateTimeZone('Europe/Oslo')); $now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo')); $appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){ if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('cutoff_passed', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Modification cutoff has passed.']));
return; return;
} }
@@ -963,8 +976,7 @@ class Pages extends CI_Controller {
if(!empty($selectedServiceArray) && $submittedWorker){ if(!empty($selectedServiceArray) && $submittedWorker){
$firstSelectedService = $this->Service_model->getServiceById($selectedServiceArray[0]); $firstSelectedService = $this->Service_model->getServiceById($selectedServiceArray[0]);
if($firstSelectedService && $firstSelectedService->service_category_id != $submittedWorker->service_category_id){ if($firstSelectedService && $firstSelectedService->service_category_id != $submittedWorker->service_category_id){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('category_mismatch', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Selected worker does not perform the chosen services.']));
return; return;
} }
} }
@@ -973,26 +985,22 @@ class Pages extends CI_Controller {
// category check proves only that the services belong to the worker's // category check proves only that the services belong to the worker's
// category, not that this worker performs them. // category, not that this worker performs them.
if(!$this->Service_model->workerCanPerformServices($newWorkerId, $selectedServiceArray)){ if(!$this->Service_model->workerCanPerformServices($newWorkerId, $selectedServiceArray)){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('service_not_offered', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Selected worker does not perform the chosen services.']));
return; return;
} }
// Schedule check // Schedule check
$schedule = $this->Service_model->getWorkerScheduleForDate($newWorkerId, $newBookingDate); $schedule = $this->Service_model->getWorkerScheduleForDate($newWorkerId, $newBookingDate);
if(!$schedule){ if(!$schedule){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('worker_unavailable', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Worker is not available on this day.']));
return; return;
} }
if($newStartTime < $schedule->start_time || $newStartTime >= $schedule->end_time){ if($newStartTime < $schedule->start_time || $newStartTime >= $schedule->end_time){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('outside_schedule', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Booking time is outside worker schedule.']));
return; return;
} }
if(!$this->Service_model->isWorkerAvailableThisWeek($newWorkerId, $newBookingDate)){ if(!$this->Service_model->isWorkerAvailableThisWeek($newWorkerId, $newBookingDate)){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('alternate_week', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Worker only works every second week.']));
return; return;
} }
@@ -1000,8 +1008,7 @@ class Pages extends CI_Controller {
$available = $this->Service_model->getAvailableTimes($newWorkerId, $newBookingDate, $servicelength, $booking->booking_id); $available = $this->Service_model->getAvailableTimes($newWorkerId, $newBookingDate, $servicelength, $booking->booking_id);
if(!is_array($available) || !in_array($newStartTime, $available)){ if(!is_array($available) || !in_array($newStartTime, $available)){
$this->output->set_status_header(409)->set_content_type('application/json') $this->_booking_error('slot_taken', 409, $manageBackUrl);
->set_output(json_encode(['error' => 'Conflict. Timeslot is taken or does not fit the service.']));
return; return;
} }
@@ -1010,8 +1017,7 @@ class Pages extends CI_Controller {
$maxDate = (clone $today)->modify('+3 months'); $maxDate = (clone $today)->modify('+3 months');
$selectedDate = new DateTime($newBookingDate); $selectedDate = new DateTime($newBookingDate);
if($selectedDate > $maxDate){ if($selectedDate > $maxDate){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('too_far', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Bookings can only be made up to 3 months in advance.']));
return; return;
} }
@@ -1023,8 +1029,7 @@ class Pages extends CI_Controller {
$bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('i')).'M')); $bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('i')).'M'));
$closingTime = new DateTime('18:00'); $closingTime = new DateTime('18:00');
if($bookingEnd > $closingTime){ if($bookingEnd > $closingTime){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('after_hours', 403, $manageBackUrl);
->set_output(json_encode(['error' => 'Selected time exceeds business hours.']));
return; return;
} }
$finishTime = $bookingEnd->format('H:i:s'); $finishTime = $bookingEnd->format('H:i:s');
@@ -1186,8 +1191,7 @@ class Pages extends CI_Controller {
$now = new DateTime('now', new DateTimeZone('Europe/Oslo')); $now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo')); $appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){ if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
$this->output->set_status_header(403)->set_content_type('application/json') $this->_booking_error('cutoff_passed', 403, SITEURL.vertical_lang($lang).'/manage-booking/'.$booking->manage_token);
->set_output(json_encode(['error' => 'Cancellation cutoff has passed.']));
return; return;
} }
@@ -10,6 +10,7 @@
'service_not_offered' => 'The selected staff member does not perform all of the chosen treatments. Please pick another staff member or change the treatments.', 'service_not_offered' => 'The selected staff member does not perform all of the chosen treatments. Please pick another staff member or change the treatments.',
'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.', 'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.',
'invalid_email' => 'That e-mail address does not look valid. Please check it - your confirmation and the link to manage your booking are sent there.', 'invalid_email' => 'That e-mail address does not look valid. Please check it - your confirmation and the link to manage your booking are sent there.',
'cutoff_passed' => 'Your booking is within 24 hours and can no longer be changed or cancelled. Please contact the salon.',
'no_service' => 'No service was selected. Please choose at least one service.', 'no_service' => 'No service was selected. Please choose at least one service.',
'default' => 'Something went wrong with your booking. Please try again.', 'default' => 'Something went wrong with your booking. Please try again.',
); );
@@ -19,12 +20,24 @@
: $bookingErrorMessages['default']; : $bookingErrorMessages['default'];
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber'; $backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
$backUrl = SITEURL.'en/booking/'.$backSubpage;
// _booking_error() passes a back URL when the error came from the
// manage-booking flow; that guest must return to their own booking page
// rather than being dropped into an empty new-booking form.
$isManageError = isset($bookingBackUrl) && $bookingBackUrl !== '';
$backUrl = $isManageError ? $bookingBackUrl : SITEURL.'en/booking/'.$backSubpage;
$backLabel = $isManageError ? 'Back to your booking' : 'Back to booking';
if(isset($bookingErrorCode) && $bookingErrorCode === 'slot_taken'){
$errorTitle = 'This time is no longer available';
} else {
$errorTitle = $isManageError ? 'Your booking could not be changed' : 'Your booking could not be completed';
}
?> ?>
<div class="bookingContainer" style="min-height:500px;"> <div class="bookingContainer" style="min-height:500px;">
<div class="bookingThankYouTitle">This time is no longer available</div> <div class="bookingThankYouTitle"><?php echo $errorTitle;?></div>
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div> <div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
<div style="text-align:center; margin-top:30px;"> <div style="text-align:center; margin-top:30px;">
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Back to booking</a> <a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;"><?php echo $backLabel;?></a>
</div> </div>
</div> </div>
@@ -10,6 +10,7 @@
'service_not_offered' => 'A kiválasztott kolléga nem végzi el az összes kiválasztott szolgáltatást. Kérjük, válasszon másik kollégát, vagy módosítsa a szolgáltatásokat.', 'service_not_offered' => 'A kiválasztott kolléga nem végzi el az összes kiválasztott szolgáltatást. Kérjük, válasszon másik kollégát, vagy módosítsa a szolgáltatásokat.',
'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.', 'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.',
'invalid_email' => 'Az e-mail cím nem tűnik érvényesnek. Kérjük, ellenőrizze - a visszaigazolást és a foglalás kezelésére szolgáló linket erre a címre küldjük.', 'invalid_email' => 'Az e-mail cím nem tűnik érvényesnek. Kérjük, ellenőrizze - a visszaigazolást és a foglalás kezelésére szolgáló linket erre a címre küldjük.',
'cutoff_passed' => 'A foglalás 24 órán belül van, ezért már nem módosítható és nem törölhető. Kérjük, vedd fel a kapcsolatot a szalonnal.',
'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.', 'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.',
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.', 'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
); );
@@ -19,12 +20,24 @@
: $bookingErrorMessages['default']; : $bookingErrorMessages['default'];
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber'; $backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
$backUrl = SITEURL.'hu/booking/'.$backSubpage;
// _booking_error() passes a back URL when the error came from the
// manage-booking flow; that guest must return to their own booking page
// rather than being dropped into an empty new-booking form.
$isManageError = isset($bookingBackUrl) && $bookingBackUrl !== '';
$backUrl = $isManageError ? $bookingBackUrl : SITEURL.'hu/booking/'.$backSubpage;
$backLabel = $isManageError ? 'Vissza a foglalásodhoz' : 'Vissza a foglaláshoz';
if(isset($bookingErrorCode) && $bookingErrorCode === 'slot_taken'){
$errorTitle = 'Ez az időpont már nem foglalható';
} else {
$errorTitle = $isManageError ? 'A foglalást nem sikerült módosítani' : 'A foglalást nem sikerült véglegesíteni';
}
?> ?>
<div class="bookingContainer" style="min-height:500px;"> <div class="bookingContainer" style="min-height:500px;">
<div class="bookingThankYouTitle">Ez az időpont már nem foglalható</div> <div class="bookingThankYouTitle"><?php echo $errorTitle;?></div>
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div> <div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
<div style="text-align:center; margin-top:30px;"> <div style="text-align:center; margin-top:30px;">
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Vissza a foglaláshoz</a> <a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;"><?php echo $backLabel;?></a>
</div> </div>
</div> </div>
@@ -10,6 +10,7 @@
'service_not_offered' => 'Den valgte medarbeideren utfører ikke alle de valgte behandlingene. Vennligst velg en annen medarbeider eller endre behandlingene.', 'service_not_offered' => 'Den valgte medarbeideren utfører ikke alle de valgte behandlingene. Vennligst velg en annen medarbeider eller endre behandlingene.',
'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.', 'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.',
'invalid_email' => 'E-postadressen ser ikke ut til å være gyldig. Vennligst kontroller den - bekreftelsen og lenken for å endre bestillingen sendes dit.', 'invalid_email' => 'E-postadressen ser ikke ut til å være gyldig. Vennligst kontroller den - bekreftelsen og lenken for å endre bestillingen sendes dit.',
'cutoff_passed' => 'Bestillingen er innen 24 timer og kan ikke lenger endres eller avbestilles. Vennligst ta kontakt med salongen.',
'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.', 'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.',
'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.', 'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.',
); );
@@ -19,12 +20,24 @@
: $bookingErrorMessages['default']; : $bookingErrorMessages['default'];
$backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber'; $backSubpage = isset($subpage) && $subpage != '' ? $subpage : 'barber';
$backUrl = SITEURL.'no/booking/'.$backSubpage;
// _booking_error() passes a back URL when the error came from the
// manage-booking flow; that guest must return to their own booking page
// rather than being dropped into an empty new-booking form.
$isManageError = isset($bookingBackUrl) && $bookingBackUrl !== '';
$backUrl = $isManageError ? $bookingBackUrl : SITEURL.'no/booking/'.$backSubpage;
$backLabel = $isManageError ? 'Tilbake til bestillingen din' : 'Tilbake til bestilling';
if(isset($bookingErrorCode) && $bookingErrorCode === 'slot_taken'){
$errorTitle = 'Tidspunktet er ikke lenger tilgjengelig';
} else {
$errorTitle = $isManageError ? 'Bestillingen kunne ikke endres' : 'Bestillingen kunne ikke fullføres';
}
?> ?>
<div class="bookingContainer" style="min-height:500px;"> <div class="bookingContainer" style="min-height:500px;">
<div class="bookingThankYouTitle">Tidspunktet er ikke lenger tilgjengelig</div> <div class="bookingThankYouTitle"><?php echo $errorTitle;?></div>
<div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div> <div class="bookingThankYouMessageTitle"><?php echo $shownError;?></div>
<div style="text-align:center; margin-top:30px;"> <div style="text-align:center; margin-top:30px;">
<a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;">Tilbake til bestilling</a> <a href="<?php echo $backUrl;?>" style="display:inline-block; padding:12px 28px; background:#000; color:#fff; text-decoration:none; border-radius:5px; font-size:16px;"><?php echo $backLabel;?></a>
</div> </div>
</div> </div>
+28 -2
View File
@@ -30,6 +30,7 @@
'back' => 'Tilbake', 'back' => 'Tilbake',
'send' => 'Bekreft endring', 'send' => 'Bekreft endring',
'no_times' => 'Ingen ledige tider denne dagen!', 'no_times' => 'Ingen ledige tider denne dagen!',
'no_workers' => 'Ingen medarbeidere utfører alle de valgte tjenestene.',
'expired_msg' => 'Denne bestillingen er allerede passert og kan ikke endres.', 'expired_msg' => 'Denne bestillingen er allerede passert og kan ikke endres.',
'cutoff_msg' => 'Bestillingen er innen 24 timer og kan ikke endres eller avbestilles.', 'cutoff_msg' => 'Bestillingen er innen 24 timer og kan ikke endres eller avbestilles.',
), ),
@@ -53,6 +54,7 @@
'back' => 'Vissza', 'back' => 'Vissza',
'send' => 'Módosítás megerősítése', 'send' => 'Módosítás megerősítése',
'no_times' => 'Ezen a napon nincs szabad időpont!', 'no_times' => 'Ezen a napon nincs szabad időpont!',
'no_workers' => 'Egyik kolléga sem végzi el az összes kiválasztott szolgáltatást.',
'expired_msg' => 'Ez a foglalás már elmúlt, nem módosítható.', 'expired_msg' => 'Ez a foglalás már elmúlt, nem módosítható.',
'cutoff_msg' => 'A foglalás 24 órán belül van, nem módosítható vagy törölhető.', 'cutoff_msg' => 'A foglalás 24 órán belül van, nem módosítható vagy törölhető.',
), ),
@@ -76,6 +78,7 @@
'back' => 'Back', 'back' => 'Back',
'send' => 'Confirm changes', 'send' => 'Confirm changes',
'no_times' => 'There is no available time on this day!', 'no_times' => 'There is no available time on this day!',
'no_workers' => 'No staff member performs all of the selected services.',
'expired_msg' => 'This booking has already passed and cannot be modified.', 'expired_msg' => 'This booking has already passed and cannot be modified.',
'cutoff_msg' => 'Your booking is within 24 hours and can no longer be changed or cancelled.', 'cutoff_msg' => 'Your booking is within 24 hours and can no longer be changed or cancelled.',
), ),
@@ -246,6 +249,7 @@
onsubmit="return confirm('<?php echo addslashes($l['cancel_confirm']); ?>');"> onsubmit="return confirm('<?php echo addslashes($l['cancel_confirm']); ?>');">
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>"> <input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
<input type="hidden" name="lang" value="<?php echo htmlspecialchars($lang); ?>"> <input type="hidden" name="lang" value="<?php echo htmlspecialchars($lang); ?>">
<input type="hidden" name="subpage" value="<?php echo htmlspecialchars($subpage); ?>">
<button type="submit" class="manage-cancel-btn"><?php echo $l['cancel_btn']; ?></button> <button type="submit" class="manage-cancel-btn"><?php echo $l['cancel_btn']; ?></button>
</form> </form>
</div> </div>
@@ -445,6 +449,7 @@
<script> <script>
var noTimesMsg = '<?php echo addslashes($l['no_times']); ?>'; var noTimesMsg = '<?php echo addslashes($l['no_times']); ?>';
var noWorkersMsg = '<?php echo addslashes($l['no_workers']); ?>';
$(document).ready(function(){ $(document).ready(function(){
recalcServices(); recalcServices();
@@ -543,16 +548,30 @@
} }
function getAvailableWorkersByServiceCategorySlug(categorySlug){ function getAvailableWorkersByServiceCategorySlug(categorySlug){
// Same rule as the new-booking form: only workers who can perform EVERY
// ticked service may be offered. The checkbox id IS the service_id.
var selectedServiceIds = $('.service:checked').map(function(){
return this.id;
}).get();
$.ajax({ $.ajax({
url: '<?php echo base_url(); ?>ajax', url: '<?php echo base_url(); ?>ajax',
type: 'POST', type: 'POST',
data: { action: 'getAvailableWorkersByServiceCategory', serv_cat_slug: categorySlug }, data: {
action: 'getAvailableWorkersByServiceCategory',
serv_cat_slug: categorySlug,
service_ids: selectedServiceIds
},
dataType: 'json', dataType: 'json',
}).done(function(result){ }).done(function(result){
$('#workerTable').html(result.workerListShow);
if(result.workers && result.workers.length > 0){ if(result.workers && result.workers.length > 0){
$('#workerTable').html(result.workerListShow);
setWorker(result.workers[0].worker_id); setWorker(result.workers[0].worker_id);
} }
else{
$('#workerTable').html('<div class="noWorkerMessage">' + noWorkersMsg + '</div>');
setWorker('');
}
}); });
} }
@@ -645,6 +664,13 @@
function setWorker(worker_id){ function setWorker(worker_id){
$('#worker_id').val(worker_id); $('#worker_id').val(worker_id);
$('.serviceBookingWorkerContainer').hide().html(''); $('.serviceBookingWorkerContainer').hide().html('');
// The worker (or the service set that produced this list) has changed, so
// a time picked for the previous one is stale. Clearing it also disables
// the submit button via the #booking_time change handler.
$('#booking_time').val('').trigger('change');
$('#serviceBookingTime').html('').hide();
$('.availableBookingTime').removeClass('selectedTime');
} }
</script> </script>