Initial commit
This commit is contained in:
commit
c909d539bc
7178 files changed
+756624
No files matched your search
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Student;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Slot;
|
||||
use Carbon\Carbon;
|
||||
use App\Services\AvailabilityService;
|
||||
|
||||
class BookingController extends Controller
|
||||
{
|
||||
public function create(Request $request)
|
||||
{
|
||||
// Expect: /student/bookings/create?date=YYYY-MM-DD&slot_id=NN
|
||||
$data = $request->validate([
|
||||
'date' => ['required','date_format:Y-m-d'],
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
]);
|
||||
|
||||
$slot = Slot::findOrFail($data['slot_id']);
|
||||
|
||||
// Prettify labels 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('student.bookings.create', [
|
||||
'slot' => $slot,
|
||||
'date' => $data['date'],
|
||||
'prettyDate' => $prettyDate,
|
||||
'prettyTime' => $prettyTime,
|
||||
'weekdayName' => $weekdayName,
|
||||
]);
|
||||
}
|
||||
|
||||
public function createSeries(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Ensure these use's exist at the top of the file:
|
||||
// use App\Models\Slot;
|
||||
// use Carbon\Carbon;
|
||||
|
||||
// Expect: /student/bookings/series/create?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']);
|
||||
|
||||
// Prettify 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;
|
||||
|
||||
return view('student.bookings.series', [
|
||||
'slot' => $slot,
|
||||
'date' => $data['date'],
|
||||
'prettyDate' => $prettyDate,
|
||||
'prettyTime' => $prettyTime,
|
||||
'weekdayName' => $weekdayName,
|
||||
'weeks' => $weeks,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Ensure these use's exist at the top of the file:
|
||||
// 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'],
|
||||
'message' => ['nullable','string'],
|
||||
]);
|
||||
|
||||
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
|
||||
|
||||
// Enforce > 2 hours’ notice (exactly 120 mins = NOT allowed)
|
||||
$now = \Carbon\Carbon::now('Europe/London');
|
||||
$sessionStart = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
|
||||
$minutesUntil = $now->diffInMinutes($sessionStart, false);
|
||||
if ($minutesUntil <= 120) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'date' => 'Bookings require more than 2 hours’ notice.',
|
||||
]);
|
||||
}
|
||||
|
||||
// Pull student name from the logged-in user
|
||||
$user = $request->user();
|
||||
$studentName = $user?->name ?? 'Student';
|
||||
|
||||
try {
|
||||
\App\Models\Booking::create([
|
||||
'slot_id' => $slot->id,
|
||||
'date' => $data['date'],
|
||||
'student_name' => $studentName,
|
||||
'booked_from_page' => 'student:booking',
|
||||
'message' => $data['message'] ?? null,
|
||||
'status' => 'booked',
|
||||
]);
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
// Unique (slot_id, date) hit — slot already taken
|
||||
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'slot_id' => 'Sorry, that slot was just taken. Please choose another.',
|
||||
]);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('student.calendar')
|
||||
->with('success', 'Your booking is confirmed.');
|
||||
}
|
||||
|
||||
public function storeSeries(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Validates: slot, start date, and weeks count
|
||||
$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');
|
||||
$user = $request->user();
|
||||
$student = $user?->name ?? 'Student';
|
||||
|
||||
$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');
|
||||
|
||||
// Enforce > 2 hours’ notice (exactly 120 mins = NOT allowed)
|
||||
$minutesUntil = $now->diffInMinutes($sessionStart, false);
|
||||
if ($minutesUntil <= 120) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
\App\Models\Booking::create([
|
||||
'slot_id' => $slot->id,
|
||||
'date' => $date,
|
||||
'student_name' => $student,
|
||||
'booked_from_page' => 'student:block-series',
|
||||
'message' => null,
|
||||
'status' => 'booked',
|
||||
]);
|
||||
$created++;
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
// Skip duplicates (unique (slot_id, date))
|
||||
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('student.calendar')
|
||||
->with('success', "Series created: {$created} added, {$skipped} skipped.");
|
||||
}
|
||||
|
||||
public function cancelConfirm(\App\Models\Booking $booking)
|
||||
{
|
||||
// Ensure these use's exist at the top:
|
||||
// use App\Models\Booking;
|
||||
// use Carbon\Carbon;
|
||||
|
||||
$user = auth()->user();
|
||||
$studentName = $user?->name ?? '';
|
||||
|
||||
// Authorize: must be this student's booking and still "booked"
|
||||
if ($booking->student_name !== $studentName || $booking->status !== 'booked') {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
// Need the slot's time to compute the session start
|
||||
$booking->load('slot');
|
||||
|
||||
$now = \Carbon\Carbon::now('Europe/London');
|
||||
$sessionStart = \Carbon\Carbon::createFromFormat(
|
||||
'Y-m-d H:i:s',
|
||||
$booking->date.' '.$booking->slot->time,
|
||||
'Europe/London'
|
||||
);
|
||||
|
||||
$minutesUntil = $now->diffInMinutes($sessionStart, false);
|
||||
|
||||
// If already started or in the past, disallow cancellation
|
||||
if ($minutesUntil <= 0) {
|
||||
return redirect()
|
||||
->route('student.bookings.index')
|
||||
->withErrors(['booking' => 'This session has started or is in the past and can’t be cancelled.']);
|
||||
}
|
||||
|
||||
// Refund policy band (for display only; payments added later)
|
||||
// ≥ 24h => 100%; < 24h => 50%
|
||||
$refundPercent = ($minutesUntil >= 24 * 60) ? 100 : 50;
|
||||
|
||||
// Prettified labels so Blade stays Carbon-free
|
||||
$prettyDate = $sessionStart->format('D d M Y');
|
||||
$prettyTime = $sessionStart->format('H:i');
|
||||
$weekdayName = $sessionStart->format('l');
|
||||
|
||||
return view('student.bookings.cancel', [
|
||||
'booking' => $booking,
|
||||
'prettyDate' => $prettyDate,
|
||||
'prettyTime' => $prettyTime,
|
||||
'weekdayName' => $weekdayName,
|
||||
'refundPercent' => $refundPercent,
|
||||
'minutesUntil' => $minutesUntil,
|
||||
]);
|
||||
}
|
||||
|
||||
public function cancelStore(\App\Models\Booking $booking)
|
||||
{
|
||||
// Ensure these use's exist at the top:
|
||||
// use App\Models\Booking;
|
||||
// use Carbon\Carbon;
|
||||
|
||||
$user = auth()->user();
|
||||
$studentName = $user?->name ?? '';
|
||||
|
||||
// Authorize: must be this student's booking and still "booked"
|
||||
if ($booking->student_name !== $studentName || $booking->status !== 'booked') {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
// Need slot time to compute the session start
|
||||
$booking->load('slot');
|
||||
|
||||
$now = \Carbon\Carbon::now('Europe/London');
|
||||
$sessionStart = \Carbon\Carbon::createFromFormat(
|
||||
'Y-m-d H:i:s',
|
||||
$booking->date.' '.$booking->slot->time,
|
||||
'Europe/London'
|
||||
);
|
||||
|
||||
$minutesUntil = $now->diffInMinutes($sessionStart, false);
|
||||
|
||||
// If already started or in the past, disallow cancellation
|
||||
if ($minutesUntil <= 0) {
|
||||
return redirect()
|
||||
->route('student.bookings.index')
|
||||
->withErrors(['booking' => 'This session has started or is in the past and can’t be cancelled.']);
|
||||
}
|
||||
|
||||
// Refund band (for info only; payments to be integrated later)
|
||||
$refundPercent = ($minutesUntil >= 24 * 60) ? 100 : 50;
|
||||
|
||||
// For now, delete the booking to free the slot.
|
||||
// (Later we can switch to a "cancelled" status or soft deletes if you want a history.)
|
||||
$booking->delete();
|
||||
|
||||
return redirect()
|
||||
->route('student.bookings.index')
|
||||
->with('success', "Booking cancelled. Refund: {$refundPercent}%.");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user