">
 

How I Approach Appointment Slot Optimization in Laravel When Services Have Different Durations

Iniciado por joomlamz, Hoje at 06:25

Respostas: 1   |   Visualizações: 1

Tópico anterior - Tópico seguinte

0 Membros e 1 Visitante estão a ver este tópico.

Saudações, comunidade do **webmastersmz.com**! Como especialista em tecnologia, analisei o excelente tópico *"How I Approach Appointment Slot Optimization in Laravel When Services Have Different Durations"* (Como abordo a optimização de horários de atendimento no Laravel quando os serviços têm durações diferentes).

Este é um desafio clássico e complexo no desenvolvimento backend, especialmente em sistemas de agendamento (como barbearias, clínicas ou consultorias) onde a gestão eficiente do tempo dita a rentabilidade do negócio.

Abaixo, destaco os pontos técnicos principais discutidos no tópico:

1. **Abordagem Algorítmica vs. Brute Force:** O autor enfatiza que gerar intervalos de tempo ("slots") de forma estática falha assim que introduzimos serviços com durações variáveis (ex: um corte de 30 minutos versus uma coloração de 2 horas). A solução passa por calcular os intervalos de forma dinâmica com base na disponibilidade do profissional e na duração específica do serviço seleccionado.
2. **Manipulação de Datas com Carbon:** No ecossistema Laravel, o uso eficiente do pacote `Carbon` é crucial para verificar sobreposições de horários (*overlapping*). O tópico demonstra como mapear os horários de início e fim, garantindo que um novo agendamento não colida com compromissos já existentes na base de dados.
3. **Indexação e Performance na Base de Dados:** Consultar rácios de disponibilidade pode tornar-se pesado à medida que a tabela de agendamentos cresce. O autor sugere uma estratégia de indexação adequada nas colunas de data/hora (`start_time` e `end_time`) nas migrações do Eloquent, o que reduz drasticamente o tempo de resposta das queries SQL.
4. **Separação de Responsabilidades (Service Classes):** Para manter o código limpo e testável (seguindo as boas práticas do Laravel), a lógica pesada de cálculo de slots não deve ficar nos Controllers. O uso de **Service Classes** ou **Actions** dedicadas para a optimização da agenda é o caminho ideal para a manutenção a longo prazo.

**Vamos abrir o debate!**
Como é que vocês têm lidado com esta questão nos vossos projectos em Moçambique? Já utilizaram pacotes prontos do ecosistema PHP/Laravel para agendamentos ou preferiram construir uma lógica personalizada do zero para atender a regras de negócio específicas? Deixem as vossas opiniões e experiências aqui nos comentários do fórum!

---

Para garantir que os vossos projectos e fóruns rodam sem falhas, convido-vos a conhecer as soluções de alojamento de alta performance da AplicHost em https://aplichost.com.

How I Approach Appointment Slot Optimization in Laravel When Services Have Different Durations



Tópico: How I Approach Appointment Slot Optimization in Laravel When Services Have Different Durations
Categoria: Tutoriais | Programação & Tecnologia
Idioma Principal: Português (Conteúdo de Tecnologia)

Descrição do Conteúdo / Informações:
-------------------------------------------------------------------------
When building an appointment scheduling system, one of the problems that looks simple at first is:

«"Find the available time slots for a doctor."»

But the problem becomes much more interesting when different services have different durations.

For example:

• Consultation → 15 minutes

• Therapy → 45 minutes

• Follow-up → 20 minutes

• Procedure → 60 minutes

Now imagine a doctor is available from 9:00 AM to 1:00 PM, and several appointments have already been booked.

How do we find the remaining slots efficiently?

In this article, I'll explain the approach I use when designing this type of scheduling logic in Laravel.

The Basic Problem

Let's say a doctor works:

09:00 AM → 01:00 PM

And the following appointments already exist:

09:30 → 09:45  Consultation

10:15 → 11:00  Therapy

11:30 → 11:50  Follow-up

Now a patient wants to book a 45-minute therapy session.

Simply checking whether "10:00 AM" is available isn't enough.

We need to determine whether there is a continuous 45-minute window where the appointment can fit.

That distinction is extremely important.

Don't Think in Terms of Fixed Slots

One common approach is to generate slots like:

09:00

09:15

09:30

09:45

10:00

...

This works for systems where every appointment has the same duration.

But it becomes problematic when services have different durations.

Instead, I prefer thinking about the doctor's schedule as a continuous timeline.

For example:

Doctor Schedule

09:00 ───────────────────────────── 13:00

Booked:

09:30 ─ 09:45

10:15 ───────── 11:00

11:30 ───── 11:50

The gaps between appointments become the important part.

Step 1: Get the Doctor's Working Window

First, determine the doctor's availability.

For example:

$doctorStart = Carbon::parse('09:00');

$doctorEnd   = Carbon::parse('13:00');

This represents the complete scheduling window.

The appointment duration comes from the selected service:

$serviceDuration = 45; // minutes

Step 2: Retrieve Existing Appointments

The next step is to retrieve all appointments that can affect the requested schedule.

For example:

$appointments = Appointment::query()

->where('doctor_id', $doctorId)

->whereDate('appointment_date', $date)

->whereIn('status', ['confirmed', 'pending'])

->orderBy('start_time')

->get();

The important thing here is that we should consider all relevant appointments, not just the first or next appointment.

If multiple appointments already exist, each one can reduce the available scheduling window.

Step 3: Find the Gaps

Suppose our working hours are:

09:00 → 13:00

And appointments are:

09:30 → 09:45

10:15 → 11:00

11:30 → 11:50

The available gaps are:

09:00 → 09:30   = 30 minutes

09:45 → 10:15   = 30 minutes

11:00 → 11:30   = 30 minutes

11:50 → 13:00   = 70 minutes

Now suppose the requested service requires 45 minutes.

Only this window can accommodate it:

11:50 → 12:35

We could also return additional possible start times inside the 70-minute window:

11:50 → 12:35

12:05 → 12:50

Depending on the business rules, we might allow 5, 10, or 15-minute increments.

Step 4: Use Interval Overlap Detection

The most important part of the implementation is detecting whether a proposed appointment overlaps an existing appointment.

Conceptually, two intervals overlap when:

New Start < Existing End

AND

New End > Existing Start

In Laravel, this logic can be expressed as:

$hasConflict = Appointment::query()

->where('doctor_id', $doctorId)

->whereDate('appointment_date', $date)

->where('start_time', '<', $newEnd)

->where('end_time', '>', $newStart)

->exists();

This is much safer than checking only whether the proposed start time already exists.

For example, this appointment:

10:00 → 10:45

must conflict with:

10:30 → 11:00

even though "10:00" isn't an existing start time.

Step 5: Generate Candidate Start Times

Once we know the free gaps, we can generate candidate start times.

For example:

$increment = 15; // minutes

For a free window:

11:50 → 13:00

and a service duration of 45 minutes:

11:50 → 12:35

12:05 → 12:50

The algorithm should stop generating candidates once:

candidateStart + serviceDuration > gapEnd

This prevents invalid slots from being returned.

A Simple Laravel Implementation

A simplified version could look like this:

$availableSlots = [];

$current = $doctorStart->copy();

foreach ($appointments as $appointment) {

$appointmentStart = Carbon::parse($appointment->start_time);
$appointmentEnd   = Carbon::parse($appointment->end_time);

// Generate slots before the appointment
while (
$current->copy()->addMinutes($serviceDuration)->lte($appointmentStart)
) {
$availableSlots[] = [
'start' => $current->format('H:i'),
'end'   => $current->copy()
->addMinutes($serviceDuration)
->format('H:i'),
];

$current->addMinutes($increment);
}

// Move current pointer beyond the booked appointment
if ($current->lt($appointmentEnd)) {
$current = $appointmentEnd->copy();
}

}

// Handle the remaining time after the last appointment

while (

$current->copy()->addMinutes($serviceDuration)->lte($doctorEnd)

) {

$availableSlots[] = [

'start' => $current->format('H:i'),

'end'   => $current->copy()

->addMinutes($serviceDuration)

->format('H:i'),

];

$current->addMinutes($increment);

}

This is intentionally simplified. In a production system, I would separate the slot calculation from the controller and put the scheduling logic into a dedicated service class.

For example:

AppointmentController



AppointmentAvailabilityService



Availability calculation



Appointment Repository / Model

This keeps the controller thin and makes the scheduling algorithm easier to test.

Why a Dedicated Service Class Matters

Scheduling logic tends to grow quickly.

Initially, you may only have:

Doctor availability

+

Booked appointments

Later, you may need:

Doctor breaks

+

Week offs

+

Holiday

+

Service duration

+

Buffer time

+

Room availability

+

Multiple doctors

+

Multiple locations

+

Appointment status

+

Cancellation

If all of this logic lives inside a controller, it becomes difficult to maintain.

A dedicated service makes the architecture much easier to evolve.

For example:

class AppointmentAvailabilityService

{

public function getAvailableSlots(

int $doctorId,

Carbon $date,

int $serviceDuration

): array {

// availability calculation

}

}

Now the controller only needs to ask:

$slots = $availabilityService->getAvailableSlots(

$doctorId,

$date,

$serviceDuration

);

Don't Forget Appointment Status

Another important consideration is appointment status.

For example:

confirmed

pending

cancelled

completed

no_show

A cancelled appointment normally shouldn't block a slot.

So your query should explicitly define which statuses consume availability.

For example:

->whereIn('status', [

'confirmed',

'pending',

])

Don't simply retrieve every appointment and assume every record blocks the schedule.

Database Indexing

Scheduling systems can generate a large number of availability queries.

If you frequently search appointments by:

doctor_id

appointment_date

start_time

then an appropriate composite index can make a significant difference.

For example:

$table->index([

'doctor_id',

'appointment_date',

'start_time',

]);

The exact indexes should depend on your actual query patterns and database workload.

Don't blindly add indexes everywhere.

Think About Race Conditions

There is another problem that is easy to miss.

Suppose two patients request the same slot at almost exactly the same time.

Both requests could see:

11:50 → 12:35

as available.

Both then try to book it.

This is no longer just a slot-calculation problem.

It becomes a concurrency problem.

The final booking operation should therefore re-check availability inside a transaction or use an appropriate locking/constraint strategy.

Calculating availability and actually reserving the slot should be treated as two different operations.

The Architecture I Prefer

For a larger Laravel scheduling application, I'd structure it roughly like this:

Controller





Availability Service



├── Doctor Schedule

├── Service Duration

├── Existing Appointments

├── Breaks / Holidays

└── Business Rules





Available Slots

Then the booking flow becomes:

Request Slot



Calculate Availability



User Selects Slot



Re-check Availability



Transaction



Create Appointment

This separation makes the system easier to reason about and significantly reduces the chance of inconsistent bookings.

Final Thoughts

Appointment scheduling is not really a "generate some time slots" problem.

It is an interval management and constraint-solving problem.

Once you start thinking in terms of:

• Continuous availability windows

• Variable service durations

• Interval overlap

• Multiple existing appointments

• Business constraints

• Concurrency

the problem becomes much easier to design correctly.

Laravel provides everything needed to build this kind of system, but the important part is keeping the scheduling algorithm separate from your controllers and database models.

For me, the biggest lesson is:

«Don't generate slots first and try to validate them later. Model the available time first, then generate only the slots that can actually fit.»

I write more about Laravel, backend architecture, system design, and real-world application development on my portfolio: https://ajkumar.in

If you're building an appointment scheduling system with Laravel, I'd be interested to hear how you're handling variable-duration services and overlapping appointments.


Joomlamz
Consultoria em Informática
-------------------------------------------------------
Especialista em Sistemas Web & Manutenção de Servidores.
A desenvolver o novo AplPortal com suporte a PHP 8.
Precisa de ajuda profissional? Contacte-me.

Tags: