Hi,
I’m working on a Laravel project for managing physiotherapy treatments for uni. I need to prevent scheduling conflicts, so that the same room, physiotherapist, or patient cannot be booked for overlapping time slots on the same day.
This is a simplified version of my controller code for reference, but i don't understand why it doesn't work correctly:
class TreatmentController extends Controller
{
// ...existing index method...
public function store(StoreTrattamentiRequest $request)
{
Gate::authorize('create', Treatment::class);
DB::beginTransaction();
$user_id = UserService::getUser()['id'];
$request->merge(['user_id' => $user_id]);
try {
$price = floatval($request->input('prezzo'));
$paid = floatval($request->input('importo_pagato'));
// PAID IN FULL
if ($paid == $price) {
$status = TreatmentStatus::where('nome', 'Saldato')->firstOrFail();
$request->merge(['stato_trattamento_id' => $status->id, 'importo_da_pagare' => 0]);
}
// NOT PAID
if ($paid == 0) {
$status = TreatmentStatus::where('nome', 'Non saldato')->firstOrFail();
$request->merge(['stato_trattamento_id' => $status->id, 'importo_da_pagare' => $price]);
}
// DISCOUNTED OR PARTIALLY PAID
else {
$status_id = $request->input('stato_trattamento_id');
$status = TreatmentStatus::find($status_id);
$remaining = $status->nome === 'Sconto' ? 0 : ($price - $paid);
$request->merge(['importo_da_pagare' => $remaining]);
if (!$status_id) {
return response()->json([
'message' => 'For partial payments, please specify if this is a discount or partial payment.'
], 422);
}
}
$patientIds = $this->getPatients($request);
if ($this->conflictRoom($request)) {
return response()->json(['message' => 'The room is already booked for the selected time slot'], 409);
}
if ($this->conflictPhysiotherapist($request)) {
return response()->json(['message' => 'The physiotherapist is already booked for the selected time slot'], 409);
}
if ($this->conflictPatient($patientIds, $request)) {
return response()->json(['message' => 'The patient is already booked for the selected time slot'], 409);
}
if ($this->conflictTime($request)) {
return response()->json(['message' => 'End time cannot be earlier than start time'], 409);
}
// --- END CONCURRENCY ---
$treatment = Treatment::create($request->all());
$treatment->patients()->attach($patientIds);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
return response()->json(compact('treatment'));
}
private function getPatients(Request $request): array
{
$ids = [];
foreach ($request->input('pazienti') as $patient) {
$ids[] = $patient['id'];
}
return $ids;
}
private function checkConflict(Builder $query, Request $request, $excludeId = null): void
{
$start = $request->input('ora_inizio');
$end = $request->input('ora_fine');
$query->where(function ($subQuery) use ($start, $end) {
$subQuery->where('ora_inizio', '<', $end)
->where('ora_fine', '>', $start);
});
if ($excludeId) {
$query->where('id', '!=', $excludeId);
}
}
private function conflictRoom(Request $request, $excludeId = null): bool
{
$date = $request->input('data');
$roomId = $request->input('stanza_id');
$query = Treatment::where('data', $date)->where('stanza_id', $roomId);
$this->checkConflict($query, $request, $excludeId);
return $query->exists();
}
private function conflictPhysiotherapist(Request $request, $excludeId = null): bool
{
$date = $request->input('data');
$physioId = $request->input('fisioterapista_id');
$query = Treatment::where('data', $date)->where('fisioterapista_id', $physioId);
$this->checkConflict($query, $request, $excludeId);
return $query->exists();
}
private function conflictPatient(array $patients, Request $request, $excludeId = null): bool
{
$date = $request->input('data');
if ($patients != null) {
foreach ($patients as $patientId) {
$query = Treatment::where('data', $date)
->whereHas('patients', function ($subQuery) use ($patientId) {
$subQuery->where('paziente_id', $patientId);
});
$this->checkConflict($query, $request, $excludeId);
if ($query->exists()) {
return true;
}
}
}
return false;
}
private function conflictTime(Request $request): bool
{
$start = $request->input('ora_inizio');
$end = $request->input('ora_fine');
return $start >= $end;
}