tutoring/app/Http/Controllers/Admin/BookingController.php

247 lines
8.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Slot;
use Carbon\Carbon;
class BookingController extends Controller
{
public function create(Request $request)
{
// Validate query params: /admin/bookings/create?date=YYYY-MM-DD&slot_id=123
$data = $request->validate([
'date' => ['required','date_format:Y-m-d'],
'slot_id' => ['required','exists:slots,id'],
]);
$slot = Slot::findOrFail($data['slot_id']);
// Build nice labels (Europe/London) so Blade doesn't need Carbon
$when = Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
$prettyDate = $when->format('D d M Y');
$prettyTime = $when->format('H:i');
$weekdayName = $when->format('l');
return view('bookings.create', [
'slot' => $slot,
'date' => $data['date'],
'prettyDate' => $prettyDate,
'prettyTime' => $prettyTime,
'weekdayName' => $weekdayName,
]);
}
public function store(\Illuminate\Http\Request $request)
{
// Needed at top of file (ensure these use's exist):
// use App\Models\Slot;
// use App\Models\Booking;
// use Carbon\Carbon;
// use Illuminate\Validation\ValidationException;
// use Illuminate\Database\QueryException;
$data = $request->validate([
'slot_id' => ['required','exists:slots,id'],
'date' => ['required','date_format:Y-m-d'],
'student_name' => ['required','string','max:255'],
'booked_from_page' => ['nullable','string','max:255'],
'message' => ['nullable','string'],
]);
// Enforce > 2 hours' notice (exactly 120 mins = NOT allowed)
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
$sessionStart = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
$now = \Carbon\Carbon::now('Europe/London');
$minutesUntil = $now->diffInMinutes($sessionStart, false);
if ($minutesUntil <= 120) {
throw \Illuminate\Validation\ValidationException::withMessages([
'date' => 'Bookings require more than 2 hours notice.',
]);
}
// Persist booking; default status for admin-created bookings = 'booked'
$data['status'] = 'booked';
try {
\App\Models\Booking::create($data);
} catch (\Illuminate\Database\QueryException $e) {
// Handle unique (slot_id, date) violation gracefully
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
throw \Illuminate\Validation\ValidationException::withMessages([
'slot_id' => 'That slot has just been taken. Please choose another.',
]);
}
throw $e;
}
return redirect()
->route('admin.calendar')
->with('success', 'Booking created successfully.');
}
public function createBlock(\Illuminate\Http\Request $request)
{
// Needed at top of file:
// use App\Models\Slot;
// use Carbon\Carbon;
$data = $request->validate([
'date' => ['required','date_format:Y-m-d'],
'slot_id' => ['required','exists:slots,id'],
]);
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
// Prettify for the view (no Carbon in Blade)
$when = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
$prettyDate = $when->format('D d M Y');
$prettyTime = $when->format('H:i');
$weekdayName = $when->format('l');
return view('bookings.block', [
'slot' => $slot,
'date' => $data['date'],
'prettyDate' => $prettyDate,
'prettyTime' => $prettyTime,
'weekdayName' => $weekdayName,
]);
}
public function storeBlock(\Illuminate\Http\Request $request)
{
// Validation: block a specific slot on a specific date
$data = $request->validate([
'slot_id' => ['required','exists:slots,id'],
'date' => ['required','date_format:Y-m-d'],
'message' => ['nullable','string'], // optional reason/notes for the block
]);
// Create a "blocked" booking for that slot/date
try {
\App\Models\Booking::create([
'slot_id' => (int)$data['slot_id'],
'date' => $data['date'],
'student_name' => null, // blocks don't need a student
'booked_from_page' => 'admin:block', // provenance
'message' => $data['message'] ?? null,
'status' => 'blocked',
]);
} catch (\Illuminate\Database\QueryException $e) {
// Unique (slot_id, date) hit — something already exists for that slot/date
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
return back()
->withErrors(['slot_id' => 'That slot/date already has a booking or block.'])
->withInput();
}
throw $e;
}
return redirect()
->route('admin.calendar')
->with('success', 'Slot blocked successfully.');
}
public function createSeries(\Illuminate\Http\Request $request)
{
// Needed at top of file (ensure these exist):
// use App\Models\Slot;
// use Carbon\Carbon;
// Accept prefill from calendar click: ?date=YYYY-MM-DD&slot_id=NN&weeks=6
$data = $request->validate([
'date' => ['required','date_format:Y-m-d'],
'slot_id' => ['required','exists:slots,id'],
'weeks' => ['nullable','integer','min:1','max:52'],
]);
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
// Precompute nice labels (keep Blade Carbon-free)
$when = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
$prettyDate = $when->format('D d M Y');
$prettyTime = $when->format('H:i');
$weekdayName = $when->format('l');
$weeks = $data['weeks'] ?? 6; // sensible default
return view('bookings.series', [
'slot' => $slot,
'date' => $data['date'],
'prettyDate' => $prettyDate,
'prettyTime' => $prettyTime,
'weekdayName' => $weekdayName,
'weeks' => $weeks,
]);
}
public function storeSeries(\Illuminate\Http\Request $request)
{
// Needed at top of file:
// use App\Models\Slot;
// use App\Models\Booking;
// use Carbon\Carbon;
// use Illuminate\Database\QueryException;
$data = $request->validate([
'slot_id' => ['required','exists:slots,id'],
'date' => ['required','date_format:Y-m-d'], // start date
'weeks' => ['required','integer','min:1','max:52'],
]);
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
$now = \Carbon\Carbon::now('Europe/London');
$startDate = \Carbon\Carbon::createFromFormat('Y-m-d', $data['date'], 'Europe/London');
$created = 0;
$skipped = 0;
for ($i = 0; $i < (int)$data['weeks']; $i++) {
$date = $startDate->copy()->addWeeks($i)->toDateString();
$sessionStart = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$date} {$slot->time}", 'Europe/London');
// Skip past or ≤ 2 hours from now
$minutesUntil = $now->diffInMinutes($sessionStart, false);
if ($minutesUntil <= 120) {
$skipped++;
continue;
}
try {
\App\Models\Booking::create([
'slot_id' => $slot->id,
'date' => $date,
'student_name' => null, // series = no student name
'booked_from_page' => 'admin:block-series', // provenance
'message' => null,
'status' => 'booked', // shows as “Booked” on the calendar
]);
$created++;
} catch (\Illuminate\Database\QueryException $e) {
// Skip duplicates (unique (slot_id, date) might already exist as booked/closed)
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
$skipped++;
continue;
}
throw $e;
}
}
// Redirect back to the week containing the start date (nice UX)
$displayStart = $now->copy()->startOfWeek(\Carbon\Carbon::MONDAY);
$fridayCutoff = $displayStart->copy()->addDays(4)->setTime(15, 0);
if ($now->greaterThanOrEqualTo($fridayCutoff)) {
$displayStart->addWeek();
}
$weekOffset = $displayStart->diffInWeeks($startDate->copy()->startOfWeek(\Carbon\Carbon::MONDAY), false);
return redirect()
->route('admin.calendar', ['week' => $weekOffset ?: null])
->with('success', "Series created: {$created} added, {$skipped} skipped.");
}
}