$this->nightlyRate()/100,'uploadedPhotos'=>LodgePhoto::latest()->get()]); } public function availability(Request $request) { $v=$request->validate(['check_in'=>'required|date|after_or_equal:today','check_out'=>'required|date|after:check_in','guests'=>'required|integer|min:1|max:'.config('lodge.max_guests',6)]); $start=CarbonImmutable::parse($v['check_in']);$end=CarbonImmutable::parse($v['check_out']);$nights=$start->diffInDays($end); if($nights>30) throw ValidationException::withMessages(['check_out'=>'Maximum stay is 30 nights.']); $available=!LodgeBooking::whereIn('status',['pending','confirmed'])->where('check_in','<',$v['check_out'])->where('check_out','>',$v['check_in'])->exists(); return view('lodge.availability',compact('v','available','nights')+['total'=>$nights*$this->nightlyRate()/100]); } public function book(Request $request) { $v=$request->validate(['check_in'=>'required|date|after_or_equal:today','check_out'=>'required|date|after:check_in','guests'=>'required|integer|min:1|max:'.config('lodge.max_guests',6),'guest_name'=>'required|string|max:120','guest_email'=>'required|email|max:255','guest_phone'=>'nullable|string|max:40','notes'=>'nullable|string|max:2000']); $nights=CarbonImmutable::parse($v['check_in'])->diffInDays(CarbonImmutable::parse($v['check_out'])); if($nights>30) throw ValidationException::withMessages(['check_out'=>'Maximum stay is 30 nights.']); // Serialized booking creation protects against concurrent requests on supported database engines. $booking=DB::transaction(function()use($v,$nights){ DB::table('lodge_booking_locks')->where('id',1)->lockForUpdate()->first(); if(LodgeBooking::whereIn('status',['pending','confirmed'])->where('check_in','<',$v['check_out'])->where('check_out','>',$v['check_in'])->exists()) throw ValidationException::withMessages(['check_in'=>'These dates have just been booked. Please choose different dates.']); return LodgeBooking::create($v+['reference'=>'CR31-'.strtoupper(Str::random(9)),'nights'=>$nights,'total_pence'=>$nights*$this->nightlyRate(),'status'=>'pending']); },3); return redirect()->route('lodge.confirmation',['reference'=>$booking->reference]); } public function confirmation(string $reference) { $booking=LodgeBooking::where('reference',$reference)->firstOrFail();return view('lodge.confirmation',compact('booking')); } public function admin(Request $request) { abort_unless($request->user()?->email===config('lodge.admin_email') && filled(config('lodge.admin_email')),403);return view('lodge.admin',['bookings'=>LodgeBooking::latest()->paginate(30),'photos'=>LodgePhoto::latest()->get()]); } public function update(Request $request,LodgeBooking $booking) {abort_unless($request->user()?->email===config('lodge.admin_email') && filled(config('lodge.admin_email')),403);$v=$request->validate(['status'=>'required|in:pending,confirmed,cancelled']);if($v['status']!=='cancelled' && LodgeBooking::whereKeyNot($booking->id)->whereIn('status',['pending','confirmed'])->where('check_in','<',$booking->check_out)->where('check_out','>',$booking->check_in)->exists()) throw ValidationException::withMessages(['status'=>'Date conflict.']);$booking->update($v);return back()->with('success','Booking updated.'); } }