Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Benjamest's avatar

Generate a room booking recurrence and then find empty rooms for each booking

I'm creating a booking system in Laravel 5.6 whereby I have a form which takes the booking details and the recurrence requirement, usually weekly bookings for 2 years+ (100+ recurrences), and then I want it to return each date with a list of rooms available. So far in the application I can generate the recurrences and can find empty rooms for a single booking, but I want an efficient way of querying multiple bookings for available rooms without having to do a single query for each recurrence. Is this possible?

The room availability query:

$AvailableRooms = Derpartment::where('id', $DepartmentID)->with(['Rooms' => function($Rooms) use ($StartTime, $EndTime)
    {
        $Rooms->whereDoesntHave('Clinics', function ($Clinics) use ($StartTime, $EndTime) {
            $Clinics->where(function($q) use ($StartTime, $EndTime)
            {
                $q->where( function($r) use ($StartTime, $EndTime)
                {
                    // Req Start before or equal to booked start & Req End is before booked End time
                    $r->where('StartTime', '>=', $StartTime);
                    $r->where('StartTime', '<', $EndTime);
                });
                $q->where( function($r)
                {
                    $r->where('ClinicStatus', '=', 1);
                    $r->orWhere('ClinicStatus', '=', 2);
                    $r->orWhere('ClinicStatus', '=', 4);
                });
            });
            $Clinics->orWhere(function($q) use ($StartTime, $EndTime)
            {
                $q->where( function($r) use ($StartTime, $EndTime)
                {
                    // Req Start is before booked end & Req End is after booked end
                    $r->where('EndTime', '>', $StartTime);
                    $r->where('EndTime', '<=', $EndTime);
                });
                $q->where( function($r)
                {
                    $r->where('ClinicStatus', '=', 1);
                    $r->orWhere('ClinicStatus', '=', 2);
                    $r->orWhere('ClinicStatus', '=', 4);
                });
            });
            $Clinics->orWhere(function($q) use ($StartTime, $EndTime)
            {
                $q->where( function($r) use ($StartTime, $EndTime)
                {
                    // Req Start equal or greater than Booked Start & Req End is
                    $r->where('StartTime', '<=', $StartTime);
                    $r->where('EndTime', ">=", $EndTime);
                });
                $q->where( function($r)
                {
                    $r->where('ClinicStatus', '=', 1);
                    $r->orWhere('ClinicStatus', '=', 2);
                    $r->orWhere('ClinicStatus', '=', 4);
                });
            });
        });
    }])->get();

ClinicStatus is how I determine whether the clinic should be included or not (e.g. other booking in database is cancelled)

The recurrence generator is done using SimShaun's Recurr from which I get an array of dates.

0 likes
6 replies
Benjamest's avatar

Thanks jlrdw. I've read far-and-wide on this one, even generic SQL resources. I guess if if I went more theoretical with the question what I'm trying to ask is can I query similar to the above on multiple date ranges in one query and get a list of rooms, instead of one query per date range.

jlrdw's avatar

A post laravel a while back went pretty deep into how to do bookings, searching is hard I know. I regret not saving that link. I think the one I remember was airline booking.

You could also search Github for things like this and get ideas from the code.

And I think @Snapey was involved in one of the previous post.

Benjamest's avatar

Thanks again, I appreciate your contribution. I had not thought of trying to find examples through an airline booking system which should help widen my searching. Should I find a suitable answer I will ensure to copy it here.

Benjamest's avatar

Ok, so I've been racking my brains quite a bit and what I've come up with is:

  1. Generate the recurrences
  2. Query the database for all rooms in the department
  3. Create an array for each date containing the rooms room
  4. Query the database for all bookings which fall in the datetime range (see below)
  5. For each booking's date & room flag the relevant field in step 3 as room unavailable.
  6. Output all rooms by booking with available/unavailable rooms flagged appropriately.

This works on the assumption that there isn't multiple recurrences on the same day (there isn't). It will take it down to two queries instead of a single query for each booking. Any comments welcome.

P.s. how do people name the different levels of an eloquent query? In this one I've gone $query -> $q -> oh shit, now what... alphabet? $r -> $s

        Clinic::where(function ($query) use ($Dates)
        {
            $query->where(function($q) use ($Dates)
            {
                $q->where( function($r) use ($Dates)
                {
                    $i=0;
                    foreach($Dates as $Date)
                    {
                        if($i == 0)
                        {
                            $r->where( function($s) use ($Date)
                            {
                                $s->where('StartTime', '>=', $Date->getStart()->format('Y-m-d H:i'));
                                $s->where('StartTime', '<', $Date->getEnd()->format('Y-m-d H:i'));
                            });
                            $i++;
                        } else {
                            $r->orWhere( function($s) use ($Date)
                            {
                                $s->where('StartTime', '>=', $Date->getStart()->format('Y-m-d H:i'));
                                $s->where('StartTime', '<', $Date->getEnd()->format('Y-m-d H:i'));
                            });
                            $i++;
                        }
                    }
                });
                $q->where( function($r)
                {
                    $r->where('ClinicStatus', '=', 1);
                    $r->orWhere('ClinicStatus', '=', 2);
                    $r->orWhere('ClinicStatus', '=', 4);
                });
            });
            $query->orWhere(function($q) use ($Dates)
            {
                $q->where( function($r) use ($Dates)
                {
                    $i=0;
                    foreach($Dates as $Date)
                    {
                        if($i == 0)
                        {
                            $r->where( function($s) use ($Date)
                            {
                                $s->where('EndTime', '>', $Date->getStart()->format('Y-m-d H:i'));
                                $s->where('EndTime', '<=', $Date->getEnd()->format('Y-m-d H:i'));
                            });
                            $i++;
                        } else {
                            $r->orWhere( function($s) use ($Date)
                            {
                                $s->where('EndTime', '>', $Date->getStart()->format('Y-m-d H:i'));
                                $s->where('EndTime', '<=', $Date->getEnd()->format('Y-m-d H:i'));
                            });
                            $i++;
                        }
                    }
                });
                $q->where( function($r)
                {
                    $r->where('ClinicStatus', '=', 1);
                    $r->orWhere('ClinicStatus', '=', 2);
                    $r->orWhere('ClinicStatus', '=', 4);
                });
            });
            $query->orWhere(function($q) use ($Dates)
            {
                $q->where( function($r) use ($Dates)
                {
                    $i=0;
                    foreach($Dates as $Date)
                    {
                        if($i == 0)
                        {
                            $r->where( function($s) use ($Date)
                            {
                                $s->where('StartTime', '<=', $Date->getStart()->format('Y-m-d H:i'));
                                $s->where('EndTime', ">=", $Date->getEnd()->format('Y-m-d H:i'));
                            });
                            $i++;
                        } else {
                            $r->orWhere( function($s) use ($Date)
                            {
                                $s->where('StartTime', '<=', $Date->getStart()->format('Y-m-d H:i'));
                                $s->where('EndTime', ">=", $Date->getEnd()->format('Y-m-d H:i'));
                            });
                            $i++;
                        }
                    }
                });
                $q->where( function($r)
                {
                    $r->where('ClinicStatus', '=', 1);
                    $r->orWhere('ClinicStatus', '=', 2);
                    $r->orWhere('ClinicStatus', '=', 4);
                });
            });
        })->whereHas('Room.Outpatient', function($q) use($OutpatientID) {
            $q->where('id', "=", $OutpatientID);
        })->get();

Please or to participate in this conversation.