migration
This commit is contained in:
39
app/Http/Controllers/AdminAuthController.php
Normal file
39
app/Http/Controllers/AdminAuthController.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Admin;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class AdminAuthController extends Controller
|
||||||
|
{
|
||||||
|
public function login(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'email' => 'required|email',
|
||||||
|
'password' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admin = Admin::where('email', $request->email)->first();
|
||||||
|
|
||||||
|
if (!$admin || !Hash::check($request->password, $admin->password)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'email' => ['The provided credentials are incorrect.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'token' => $admin->createToken('admin-token')->plainTextToken,
|
||||||
|
'admin' => $admin,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(Request $request)
|
||||||
|
{
|
||||||
|
$request->user()->currentAccessToken()->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Logged out successfully']);
|
||||||
|
}
|
||||||
|
}
|
||||||
129
app/Http/Controllers/BookingController.php
Normal file
129
app/Http/Controllers/BookingController.php
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Booking;
|
||||||
|
use App\Models\RoomType;
|
||||||
|
use App\Models\RoomAvailability;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class BookingController extends Controller
|
||||||
|
{
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'room_type_id' => 'required|exists:room_types,id',
|
||||||
|
'check_in' => 'required|date|after_or_equal:today',
|
||||||
|
'check_out' => 'required|date|after:check_in',
|
||||||
|
'guest_name' => 'required|string|max:255',
|
||||||
|
'guest_email' => 'required|email',
|
||||||
|
'guest_phone' => 'required|string',
|
||||||
|
'confirmation_type' => 'required|in:auto,manual',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$roomType = RoomType::findOrFail($validated['room_type_id']);
|
||||||
|
|
||||||
|
$dates = [];
|
||||||
|
$currentDate = new \DateTime($validated['check_in']);
|
||||||
|
$endDate = new \DateTime($validated['check_out']);
|
||||||
|
|
||||||
|
while ($currentDate < $endDate) {
|
||||||
|
$dates[] = $currentDate->format('Y-m-d');
|
||||||
|
$currentDate->modify('+1 day');
|
||||||
|
}
|
||||||
|
|
||||||
|
$unavailableDates = RoomAvailability::where('room_type_id', $roomType->id)
|
||||||
|
->whereIn('date', $dates)
|
||||||
|
->where('is_available', false)
|
||||||
|
->pluck('date')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (!empty($unavailableDates)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'check_in' => [
|
||||||
|
'The following dates are not available: ' . implode(', ', $unavailableDates)
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$booking = Booking::create([
|
||||||
|
'room_type_id' => $validated['room_type_id'],
|
||||||
|
'check_in' => $validated['check_in'],
|
||||||
|
'check_out' => $validated['check_out'],
|
||||||
|
'guest_name' => $validated['guest_name'],
|
||||||
|
'guest_email' => $validated['guest_email'],
|
||||||
|
'guest_phone' => $validated['guest_phone'],
|
||||||
|
'status' => $validated['confirmation_type'] === 'auto' ? 'confirmed' : 'pending',
|
||||||
|
'confirmed_at' => $validated['confirmation_type'] === 'auto' ? now() : null,
|
||||||
|
'created_by_user_id' => $request->user()->id, // ← ID админа
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json($booking, 201);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollback();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$query = \App\Models\Booking::with(['roomType', 'roomType.hotel']);
|
||||||
|
|
||||||
|
// Фильтр по статусу
|
||||||
|
if ($request->has('status')) {
|
||||||
|
$query->where('status', $request->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по отелю
|
||||||
|
if ($request->has('hotel_id')) {
|
||||||
|
$query->whereHas('roomType.hotel', function ($q) use ($request) {
|
||||||
|
$q->where('id', $request->hotel_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по дате заезда (от)
|
||||||
|
if ($request->has('from')) {
|
||||||
|
$query->where('check_in', '>=', $request->from);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
return response()->json($query->paginate(10));
|
||||||
|
}
|
||||||
|
public function confirm(Request $request, $id)
|
||||||
|
{
|
||||||
|
$booking = Booking::findOrFail($id);
|
||||||
|
|
||||||
|
if ($booking->status !== 'pending') {
|
||||||
|
return response()->json(['error' => 'Booking is not in pending status'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking->update([
|
||||||
|
'status' => 'confirmed',
|
||||||
|
'confirmed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($booking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(Request $request, $id)
|
||||||
|
{
|
||||||
|
$booking = Booking::findOrFail($id);
|
||||||
|
|
||||||
|
if ($booking->status === 'cancelled') {
|
||||||
|
return response()->json(['error' => 'Booking is already cancelled'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking->update([
|
||||||
|
'status' => 'cancelled',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($booking);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
app/Http/Controllers/HotelController.php
Normal file
62
app/Http/Controllers/HotelController.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Hotel;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class HotelController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return Hotel::all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'address' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$hotel = Hotel::create($validated);
|
||||||
|
|
||||||
|
return response()->json($hotel, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$hotel = Hotel::findOrFail($id);
|
||||||
|
|
||||||
|
return response()->json($hotel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$hotel = Hotel::findOrFail($id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'address' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$hotel->update($validated);
|
||||||
|
|
||||||
|
return response()->json($hotel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$hotel = Hotel::findOrFail($id);
|
||||||
|
|
||||||
|
|
||||||
|
//if ($hotel->bookings()->exists()) {
|
||||||
|
// return response()->json(['error' => 'Cannot delete hotel with bookings'], 400);
|
||||||
|
//}
|
||||||
|
|
||||||
|
$hotel->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Hotel deleted successfully']);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/Http/Controllers/InvoiceController.php
Normal file
81
app/Http/Controllers/InvoiceController.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Booking;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\RoomAvailability;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class InvoiceController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function generate(Request $request, $bookingId)
|
||||||
|
{
|
||||||
|
$booking = Booking::findOrFail($bookingId);
|
||||||
|
|
||||||
|
if ($booking->status === 'cancelled') {
|
||||||
|
return response()->json(['error' => 'Cannot generate invoice for cancelled booking'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalAmount = 0;
|
||||||
|
$currentDate = new \DateTime($booking->check_in);
|
||||||
|
$endDate = new \DateTime($booking->check_out);
|
||||||
|
|
||||||
|
while ($currentDate < $endDate) {
|
||||||
|
$date = $currentDate->format('Y-m-d');
|
||||||
|
|
||||||
|
$availability = RoomAvailability::where('room_type_id', $booking->room_type_id)
|
||||||
|
->where('date', $date)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$price = $availability && $availability->price_override !== null
|
||||||
|
? $availability->price_override
|
||||||
|
: $booking->roomType->base_price;
|
||||||
|
|
||||||
|
$totalAmount += $price;
|
||||||
|
|
||||||
|
$currentDate->modify('+1 day');
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$invoice = Invoice::create([
|
||||||
|
'booking_id' => $booking->id,
|
||||||
|
'total_amount' => $totalAmount,
|
||||||
|
'status' => 'pending',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$pdfPath = $this->generatePdf($invoice);
|
||||||
|
|
||||||
|
if ($pdfPath) {
|
||||||
|
$invoice->update(['pdf_path' => $pdfPath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json($invoice, 201);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollback();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function generatePdf($invoice)
|
||||||
|
{
|
||||||
|
|
||||||
|
$pdf = \PDF::loadView('invoices.show', compact('invoice'));
|
||||||
|
|
||||||
|
$fileName = "invoice_{$invoice->id}.pdf";
|
||||||
|
$path = "invoices/{$fileName}";
|
||||||
|
|
||||||
|
Storage::put($path, $pdf->output());
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
app/Http/Controllers/RoomAvailabilityController.php
Normal file
86
app/Http/Controllers/RoomAvailabilityController.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\RoomType;
|
||||||
|
use App\Models\RoomAvailability;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class RoomAvailabilityController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request, $roomTypeId)
|
||||||
|
{
|
||||||
|
$roomType = RoomType::findOrFail($roomTypeId);
|
||||||
|
|
||||||
|
$from = $request->query('from');
|
||||||
|
$to = $request->query('to');
|
||||||
|
|
||||||
|
if (!$from || !$to) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'from' => ['The from date is required.'],
|
||||||
|
'to' => ['The to date is required.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fromDate = \DateTime::createFromFormat('Y-m-d', $from);
|
||||||
|
$toDate = \DateTime::createFromFormat('Y-m-d', $to);
|
||||||
|
|
||||||
|
if (!$fromDate || !$toDate) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'from' => ['Invalid from date format. Use YYYY-MM-DD.'],
|
||||||
|
'to' => ['Invalid to date format. Use YYYY-MM-DD.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fromDate > $toDate) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'from' => ['From date must be before to date.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$availabilities = RoomAvailability::where('room_type_id', $roomTypeId)
|
||||||
|
->whereBetween('date', [$from, $to])
|
||||||
|
->orderBy('date')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return response()->json($availabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bulkUpdate(Request $request, $roomTypeId)
|
||||||
|
{
|
||||||
|
$roomType = RoomType::findOrFail($roomTypeId);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'data' => 'required|array',
|
||||||
|
'data.*.date' => 'required|date_format:Y-m-d|after_or_equal:today',
|
||||||
|
'data.*.is_available' => 'required|boolean',
|
||||||
|
'data.*.price_override' => 'nullable|numeric|min:0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($validated['data'] as $item) {
|
||||||
|
RoomAvailability::updateOrCreate(
|
||||||
|
[
|
||||||
|
'room_type_id' => $roomTypeId,
|
||||||
|
'date' => $item['date'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'is_available' => $item['is_available'],
|
||||||
|
'price_override' => $item['price_override'] ?? null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Availability updated successfully']);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollback();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
app/Http/Controllers/RoomTypeController.php
Normal file
75
app/Http/Controllers/RoomTypeController.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Hotel;
|
||||||
|
use App\Models\RoomType;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class RoomTypeController extends Controller
|
||||||
|
{
|
||||||
|
public function store(Request $request, $hotelId)
|
||||||
|
{
|
||||||
|
$hotel = Hotel::findOrFail($hotelId);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'capacity' => 'required|integer|min:1',
|
||||||
|
'base_price' => 'required|numeric|min:0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$roomType = $hotel->roomTypes()->create($validated);
|
||||||
|
|
||||||
|
return response()->json($roomType, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$roomType = RoomType::findOrFail($id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'capacity' => 'required|integer|min:1',
|
||||||
|
'base_price' => 'required|numeric|min:0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$roomType->update($validated);
|
||||||
|
|
||||||
|
return response()->json($roomType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$roomType = RoomType::findOrFail($id);
|
||||||
|
|
||||||
|
|
||||||
|
if ($roomType->bookings()->exists()) {
|
||||||
|
return response()->json(['error' => 'Cannot delete room type with active bookings'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($roomType->availabilities()->exists()) {
|
||||||
|
return response()->json(['error' => 'Cannot delete room type with availability records'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$roomType->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Room type deleted successfully']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hotel()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Hotel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bookings()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Booking::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function availabilities()
|
||||||
|
{
|
||||||
|
return $this->hasMany(RoomAvailability::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Models/Admin.php
Normal file
24
app/Models/Admin.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
|
class Admin extends Authenticatable
|
||||||
|
{
|
||||||
|
use HasApiTokens;
|
||||||
|
|
||||||
|
protected $guard = 'admin'; // Указываем guard для админов
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
}
|
||||||
33
app/Models/Booking.php
Normal file
33
app/Models/Booking.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Booking extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'room_type_id',
|
||||||
|
'check_in',
|
||||||
|
'check_out',
|
||||||
|
'guest_name',
|
||||||
|
'guest_email',
|
||||||
|
'guest_phone',
|
||||||
|
'status',
|
||||||
|
'confirmed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'check_in' => 'date',
|
||||||
|
'check_out' => 'date',
|
||||||
|
'confirmed_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function roomType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(RoomType::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Models/Hotel.php
Normal file
26
app/Models/Hotel.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Hotel extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'address',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function roomTypes()
|
||||||
|
{
|
||||||
|
return $this->hasMany(RoomType::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//public function bookings()
|
||||||
|
//{
|
||||||
|
// return $this->hasMany(Booking::class);
|
||||||
|
//}
|
||||||
|
}
|
||||||
25
app/Models/Invoice.php
Normal file
25
app/Models/Invoice.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Invoice extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'booking_id',
|
||||||
|
'total_amount',
|
||||||
|
'status',
|
||||||
|
'issued_at',
|
||||||
|
'due_date',
|
||||||
|
'pdf_path',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function booking()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(\App\Models\Booking::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/Models/RoomAvailability.php
Normal file
29
app/Models/RoomAvailability.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RoomAvailability extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'room_type_id',
|
||||||
|
'date',
|
||||||
|
'is_available',
|
||||||
|
'price_override',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'date' => 'date',
|
||||||
|
'is_available' => 'boolean',
|
||||||
|
'price_override' => 'decimal:2',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function roomType()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(RoomType::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Models/RoomType.php
Normal file
33
app/Models/RoomType.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RoomType extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'hotel_id',
|
||||||
|
'name',
|
||||||
|
'capacity',
|
||||||
|
'base_price',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function hotel()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Hotel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bookings()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Booking::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function availabilities()
|
||||||
|
{
|
||||||
|
return $this->hasMany(RoomAvailability::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
@@ -10,13 +10,12 @@ use Laravel\Sanctum\HasApiTokens;
|
|||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
|
||||||
use HasApiTokens, HasFactory, Notifiable;
|
use HasApiTokens, HasFactory, Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
@@ -27,7 +26,7 @@ class User extends Authenticatable
|
|||||||
/**
|
/**
|
||||||
* The attributes that should be hidden for serialization.
|
* The attributes that should be hidden for serialization.
|
||||||
*
|
*
|
||||||
* @var list<string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
'password',
|
||||||
@@ -35,15 +34,19 @@ class User extends Authenticatable
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the attributes that should be cast.
|
* The attributes that should be cast.
|
||||||
*
|
*
|
||||||
* @return array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
protected function casts(): array
|
protected $casts = [
|
||||||
{
|
|
||||||
return [
|
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, является ли пользователь админом.
|
||||||
|
*/
|
||||||
|
public function isAdmin(): bool
|
||||||
|
{
|
||||||
|
return $this->is_admin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,16 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
//
|
// Middleware
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
//
|
$exceptions->render(function (Throwable $e, Request $request) {
|
||||||
})->create();
|
if ($request->expectsJson()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->create();
|
||||||
@@ -7,8 +7,9 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
|
"barryvdh/laravel-dompdf": "^3.1",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.2",
|
||||||
"laravel/tinker": "^2.10.1"
|
"laravel/tinker": "^2.10.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|||||||
1561
composer.lock
generated
1561
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,15 +7,15 @@ return [
|
|||||||
| Authentication Defaults
|
| Authentication Defaults
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| This option defines the default authentication "guard" and password
|
| This option controls the default authentication "guard" and password
|
||||||
| reset "broker" for your application. You may change these values
|
| reset options for your application. You may change these defaults
|
||||||
| as required, but they're a perfect start for most applications.
|
| as required, but they're a perfect start for most applications.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'guard' => env('AUTH_GUARD', 'web'),
|
'guard' => 'web',
|
||||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
'passwords' => 'users',
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -25,13 +25,13 @@ return [
|
|||||||
|
|
|
|
||||||
| Next, you may define every authentication guard for your application.
|
| Next, you may define every authentication guard for your application.
|
||||||
| Of course, a great default configuration has been defined for you
|
| Of course, a great default configuration has been defined for you
|
||||||
| which utilizes session storage plus the Eloquent user provider.
|
| here which uses session storage and the Eloquent user provider.
|
||||||
|
|
|
|
||||||
| All authentication guards have a user provider, which defines how the
|
| All authentication drivers have a user provider. This defines how the
|
||||||
| users are actually retrieved out of your database or other storage
|
| users are actually retrieved out of your database or other storage
|
||||||
| system used by the application. Typically, Eloquent is utilized.
|
| mechanisms used by this application to persist your user's data.
|
||||||
|
|
|
|
||||||
| Supported: "session"
|
| Supported: "session", "token"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -40,6 +40,17 @@ return [
|
|||||||
'driver' => 'session',
|
'driver' => 'session',
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
'driver' => 'sanctum',
|
||||||
|
'provider' => 'users',
|
||||||
|
'hash' => false,
|
||||||
|
],
|
||||||
|
|
||||||
|
'admin' => [ // ← Этот guard нужен для админов
|
||||||
|
'driver' => 'sanctum',
|
||||||
|
'provider' => 'admins',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -47,12 +58,12 @@ return [
|
|||||||
| User Providers
|
| User Providers
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| All authentication guards have a user provider, which defines how the
|
| All authentication drivers have a user provider. This defines how the
|
||||||
| users are actually retrieved out of your database or other storage
|
| users are actually retrieved out of your database or other storage
|
||||||
| system used by the application. Typically, Eloquent is utilized.
|
| mechanisms used by this application to persist your user's data.
|
||||||
|
|
|
|
||||||
| If you have multiple user tables or models you may configure multiple
|
| If you have multiple user tables or models you may configure multiple
|
||||||
| providers to represent the model / table. These providers may then
|
| sources which represent each model / table. These sources may then
|
||||||
| be assigned to any extra authentication guards you have defined.
|
| be assigned to any extra authentication guards you have defined.
|
||||||
|
|
|
|
||||||
| Supported: "database", "eloquent"
|
| Supported: "database", "eloquent"
|
||||||
@@ -62,13 +73,13 @@ return [
|
|||||||
'providers' => [
|
'providers' => [
|
||||||
'users' => [
|
'users' => [
|
||||||
'driver' => 'eloquent',
|
'driver' => 'eloquent',
|
||||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
'model' => App\Models\User::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
// 'users' => [
|
'admins' => [
|
||||||
// 'driver' => 'database',
|
'driver' => 'eloquent',
|
||||||
// 'table' => 'users',
|
'model' => App\Models\Admin::class,
|
||||||
// ],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -76,18 +87,14 @@ return [
|
|||||||
| Resetting Passwords
|
| Resetting Passwords
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| These configuration options specify the behavior of Laravel's password
|
| You may specify multiple password reset configurations if you have more
|
||||||
| reset functionality, including the table utilized for token storage
|
| than one user table or model in the application and you want to have
|
||||||
| and the user provider that is invoked to actually retrieve users.
|
| separate password reset settings based on the specific user types.
|
||||||
|
|
|
|
||||||
| The expiry time is the number of minutes that each reset token will be
|
| The expire time is the number of minutes that each reset token will be
|
||||||
| considered valid. This security feature keeps tokens short-lived so
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
| they have less time to be guessed. You may change this as needed.
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
|
||||||
| The throttle setting is the number of seconds a user must wait before
|
|
||||||
| generating more password reset tokens. This prevents the user from
|
|
||||||
| quickly generating a very large amount of password reset tokens.
|
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'passwords' => [
|
'passwords' => [
|
||||||
|
|||||||
301
config/dompdf.php
Normal file
301
config/dompdf.php
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Set some default values. It is possible to add all defines that can be set
|
||||||
|
| in dompdf_config.inc.php. You can also override the entire config file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'show_warnings' => false, // Throw an Exception on warnings from dompdf
|
||||||
|
|
||||||
|
'public_path' => null, // Override the public path if needed
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dejavu Sans font is missing glyphs for converted entities, turn it off if you need to show € and £.
|
||||||
|
*/
|
||||||
|
'convert_entities' => true,
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
/**
|
||||||
|
* The location of the DOMPDF font directory
|
||||||
|
*
|
||||||
|
* The location of the directory where DOMPDF will store fonts and font metrics
|
||||||
|
* Note: This directory must exist and be writable by the webserver process.
|
||||||
|
* *Please note the trailing slash.*
|
||||||
|
*
|
||||||
|
* Notes regarding fonts:
|
||||||
|
* Additional .afm font metrics can be added by executing load_font.php from command line.
|
||||||
|
*
|
||||||
|
* Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must
|
||||||
|
* be embedded in the pdf file or the PDF may not display correctly. This can significantly
|
||||||
|
* increase file size unless font subsetting is enabled. Before embedding a font please
|
||||||
|
* review your rights under the font license.
|
||||||
|
*
|
||||||
|
* Any font specification in the source HTML is translated to the closest font available
|
||||||
|
* in the font directory.
|
||||||
|
*
|
||||||
|
* The pdf standard "Base 14 fonts" are:
|
||||||
|
* Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique,
|
||||||
|
* Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique,
|
||||||
|
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
|
||||||
|
* Symbol, ZapfDingbats.
|
||||||
|
*/
|
||||||
|
'font_dir' => storage_path('fonts'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the DOMPDF font cache directory
|
||||||
|
*
|
||||||
|
* This directory contains the cached font metrics for the fonts used by DOMPDF.
|
||||||
|
* This directory can be the same as DOMPDF_FONT_DIR
|
||||||
|
*
|
||||||
|
* Note: This directory must exist and be writable by the webserver process.
|
||||||
|
*/
|
||||||
|
'font_cache' => storage_path('fonts'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of a temporary directory.
|
||||||
|
*
|
||||||
|
* The directory specified must be writeable by the webserver process.
|
||||||
|
* The temporary directory is required to download remote images and when
|
||||||
|
* using the PDFLib back end.
|
||||||
|
*/
|
||||||
|
'temp_dir' => sys_get_temp_dir(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ==== IMPORTANT ====
|
||||||
|
*
|
||||||
|
* dompdf's "chroot": Prevents dompdf from accessing system files or other
|
||||||
|
* files on the webserver. All local files opened by dompdf must be in a
|
||||||
|
* subdirectory of this directory. DO NOT set it to '/' since this could
|
||||||
|
* allow an attacker to use dompdf to read any files on the server. This
|
||||||
|
* should be an absolute path.
|
||||||
|
* This is only checked on command line call by dompdf.php, but not by
|
||||||
|
* direct class use like:
|
||||||
|
* $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output();
|
||||||
|
*/
|
||||||
|
'chroot' => realpath(base_path()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol whitelist
|
||||||
|
*
|
||||||
|
* Protocols and PHP wrappers allowed in URIs, and the validation rules
|
||||||
|
* that determine if a resouce may be loaded. Full support is not guaranteed
|
||||||
|
* for the protocols/wrappers specified
|
||||||
|
* by this array.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
'allowed_protocols' => [
|
||||||
|
'data://' => ['rules' => []],
|
||||||
|
'file://' => ['rules' => []],
|
||||||
|
'http://' => ['rules' => []],
|
||||||
|
'https://' => ['rules' => []],
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operational artifact (log files, temporary files) path validation
|
||||||
|
*/
|
||||||
|
'artifactPathValidation' => null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
'log_output_file' => null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable font subsetting or not.
|
||||||
|
*/
|
||||||
|
'enable_font_subsetting' => false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PDF rendering backend to use
|
||||||
|
*
|
||||||
|
* Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and
|
||||||
|
* 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will
|
||||||
|
* fall back on CPDF. 'GD' renders PDFs to graphic files.
|
||||||
|
* {@link * Canvas_Factory} ultimately determines which rendering class to
|
||||||
|
* instantiate based on this setting.
|
||||||
|
*
|
||||||
|
* Both PDFLib & CPDF rendering backends provide sufficient rendering
|
||||||
|
* capabilities for dompdf, however additional features (e.g. object,
|
||||||
|
* image and font support, etc.) differ between backends. Please see
|
||||||
|
* {@link PDFLib_Adapter} for more information on the PDFLib backend
|
||||||
|
* and {@link CPDF_Adapter} and lib/class.pdf.php for more information
|
||||||
|
* on CPDF. Also see the documentation for each backend at the links
|
||||||
|
* below.
|
||||||
|
*
|
||||||
|
* The GD rendering backend is a little different than PDFLib and
|
||||||
|
* CPDF. Several features of CPDF and PDFLib are not supported or do
|
||||||
|
* not make any sense when creating image files. For example,
|
||||||
|
* multiple pages are not supported, nor are PDF 'objects'. Have a
|
||||||
|
* look at {@link GD_Adapter} for more information. GD support is
|
||||||
|
* experimental, so use it at your own risk.
|
||||||
|
*
|
||||||
|
* @link http://www.pdflib.com
|
||||||
|
* @link http://www.ros.co.nz/pdf
|
||||||
|
* @link http://www.php.net/image
|
||||||
|
*/
|
||||||
|
'pdf_backend' => 'CPDF',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* html target media view which should be rendered into pdf.
|
||||||
|
* List of types and parsing rules for future extensions:
|
||||||
|
* http://www.w3.org/TR/REC-html40/types.html
|
||||||
|
* screen, tty, tv, projection, handheld, print, braille, aural, all
|
||||||
|
* Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3.
|
||||||
|
* Note, even though the generated pdf file is intended for print output,
|
||||||
|
* the desired content might be different (e.g. screen or projection view of html file).
|
||||||
|
* Therefore allow specification of content here.
|
||||||
|
*/
|
||||||
|
'default_media_type' => 'screen',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default paper size.
|
||||||
|
*
|
||||||
|
* North America standard is "letter"; other countries generally "a4"
|
||||||
|
*
|
||||||
|
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
|
||||||
|
*/
|
||||||
|
'default_paper_size' => 'a4',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default paper orientation.
|
||||||
|
*
|
||||||
|
* The orientation of the page (portrait or landscape).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
'default_paper_orientation' => 'portrait',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default font family
|
||||||
|
*
|
||||||
|
* Used if no suitable fonts can be found. This must exist in the font folder.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
'default_font' => 'serif',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image DPI setting
|
||||||
|
*
|
||||||
|
* This setting determines the default DPI setting for images and fonts. The
|
||||||
|
* DPI may be overridden for inline images by explictly setting the
|
||||||
|
* image's width & height style attributes (i.e. if the image's native
|
||||||
|
* width is 600 pixels and you specify the image's width as 72 points,
|
||||||
|
* the image will have a DPI of 600 in the rendered PDF. The DPI of
|
||||||
|
* background images can not be overridden and is controlled entirely
|
||||||
|
* via this parameter.
|
||||||
|
*
|
||||||
|
* For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
|
||||||
|
* If a size in html is given as px (or without unit as image size),
|
||||||
|
* this tells the corresponding size in pt.
|
||||||
|
* This adjusts the relative sizes to be similar to the rendering of the
|
||||||
|
* html page in a reference browser.
|
||||||
|
*
|
||||||
|
* In pdf, always 1 pt = 1/72 inch
|
||||||
|
*
|
||||||
|
* Rendering resolution of various browsers in px per inch:
|
||||||
|
* Windows Firefox and Internet Explorer:
|
||||||
|
* SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:?
|
||||||
|
* Linux Firefox:
|
||||||
|
* about:config *resolution: Default:96
|
||||||
|
* (xorg screen dimension in mm and Desktop font dpi settings are ignored)
|
||||||
|
*
|
||||||
|
* Take care about extra font/image zoom factor of browser.
|
||||||
|
*
|
||||||
|
* In images, <img> size in pixel attribute, img css style, are overriding
|
||||||
|
* the real image dimension in px for rendering.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
'dpi' => 96,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable embedded PHP
|
||||||
|
*
|
||||||
|
* If this setting is set to true then DOMPDF will automatically evaluate embedded PHP contained
|
||||||
|
* within <script type="text/php"> ... </script> tags.
|
||||||
|
*
|
||||||
|
* ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages)
|
||||||
|
* is a security risk.
|
||||||
|
* Embedded scripts are run with the same level of system access available to dompdf.
|
||||||
|
* Set this option to false (recommended) if you wish to process untrusted documents.
|
||||||
|
* This setting may increase the risk of system exploit.
|
||||||
|
* Do not change this settings without understanding the consequences.
|
||||||
|
* Additional documentation is available on the dompdf wiki at:
|
||||||
|
* https://github.com/dompdf/dompdf/wiki
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
'enable_php' => false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rnable inline JavaScript
|
||||||
|
*
|
||||||
|
* If this setting is set to true then DOMPDF will automatically insert JavaScript code contained
|
||||||
|
* within <script type="text/javascript"> ... </script> tags as written into the PDF.
|
||||||
|
* NOTE: This is PDF-based JavaScript to be executed by the PDF viewer,
|
||||||
|
* not browser-based JavaScript executed by Dompdf.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
'enable_javascript' => true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable remote file access
|
||||||
|
*
|
||||||
|
* If this setting is set to true, DOMPDF will access remote sites for
|
||||||
|
* images and CSS files as required.
|
||||||
|
*
|
||||||
|
* ==== IMPORTANT ====
|
||||||
|
* This can be a security risk, in particular in combination with isPhpEnabled and
|
||||||
|
* allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
|
||||||
|
* This allows anonymous users to download legally doubtful internet content which on
|
||||||
|
* tracing back appears to being downloaded by your server, or allows malicious php code
|
||||||
|
* in remote html pages to be executed by your server with your account privileges.
|
||||||
|
*
|
||||||
|
* This setting may increase the risk of system exploit. Do not change
|
||||||
|
* this settings without understanding the consequences. Additional
|
||||||
|
* documentation is available on the dompdf wiki at:
|
||||||
|
* https://github.com/dompdf/dompdf/wiki
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
'enable_remote' => false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of allowed remote hosts
|
||||||
|
*
|
||||||
|
* Each value of the array must be a valid hostname.
|
||||||
|
*
|
||||||
|
* This will be used to filter which resources can be loaded in combination with
|
||||||
|
* isRemoteEnabled. If enable_remote is FALSE, then this will have no effect.
|
||||||
|
*
|
||||||
|
* Leave to NULL to allow any remote host.
|
||||||
|
*
|
||||||
|
* @var array|null
|
||||||
|
*/
|
||||||
|
'allowed_remote_hosts' => null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ratio applied to the fonts height to be more like browsers' line height
|
||||||
|
*/
|
||||||
|
'font_height_ratio' => 1.1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the HTML5 Lib parser
|
||||||
|
*
|
||||||
|
* @deprecated This feature is now always on in dompdf 2.x
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
'enable_html5_parser' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('users', function (Blueprint $table) {
|
|
||||||
$table->boolean('is_admin')->default(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('users', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('is_admin');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -11,12 +11,12 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('table_room_types', function (Blueprint $table) {
|
Schema::create('hotels', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('address');
|
||||||
|
$table->text('description')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->text('name');
|
|
||||||
$table->text('capacity');
|
|
||||||
$table->text('base_price');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +25,6 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('table_room_types');
|
Schema::dropIfExists('hotels');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
31
database/migrations/2025_12_30_222106_create_room_types.php
Normal file
31
database/migrations/2025_12_30_222106_create_room_types.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('room_types', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('hotel_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->string('name');
|
||||||
|
$table->integer('capacity');
|
||||||
|
$table->decimal('base_price', 10, 2);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('room_types');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -11,9 +11,15 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('table_room_availability', function (Blueprint $table) {
|
Schema::create('room_availability', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->foreignId('room_type_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->date('date');
|
||||||
|
$table->boolean('is_available')->default(true);
|
||||||
|
$table->decimal('price_override', 10, 2)->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['room_type_id', 'date']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,6 +28,6 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('table_room_availability');
|
Schema::dropIfExists('room_availability');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('bookings', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('booking_number')->nullable();
|
||||||
|
$table->foreignId('room_type_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->date('check_in');
|
||||||
|
$table->date('check_out');
|
||||||
|
$table->string('guest_name');
|
||||||
|
$table->string('guest_email')->nullable();
|
||||||
|
$table->string('guest_phone')->nullable();
|
||||||
|
$table->enum('status', ['pending', 'confirmed', 'cancelled', 'completed'])->default('pending');
|
||||||
|
$table->enum('confirmation_type', ['auto', 'manual'])->default('auto');
|
||||||
|
$table->unsignedBigInteger('created_by_user_id')->nullable();
|
||||||
|
$table->timestamp('confirmed_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('bookings');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -11,12 +11,14 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('table_hotels', function (Blueprint $table) {
|
Schema::create('invoices', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->foreignId('booking_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->decimal('amount', 10, 2);
|
||||||
|
$table->string('currency')->default('RUB');
|
||||||
|
$table->string('pdf_path')->nullable();
|
||||||
|
$table->timestamp('issued_at')->useCurrent();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->text('name');
|
|
||||||
$table->text('description');
|
|
||||||
$table->text('address');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +27,6 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('table_hotels');
|
Schema::dropIfExists('invoices');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateAdminsTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('admins', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
|
$table->string('password');
|
||||||
|
$table->rememberToken();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('admins');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('invoices', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('booking_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->decimal('total_amount', 10, 2);
|
||||||
|
$table->string('status')->default('pending');
|
||||||
|
$table->timestamp('issued_at')->useCurrent();
|
||||||
|
$table->timestamp('due_date')->nullable();
|
||||||
|
$table->string('pdf_path')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('invoices');
|
||||||
|
}
|
||||||
|
};
|
||||||
19
resources/views/invoices/template.blade.php
Normal file
19
resources/views/invoices/template.blade.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Счёт №{{ $invoice->id }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Счёт №{{ $invoice->id }}</h1>
|
||||||
|
<p>Дата выписки: {{ $invoice->issued_at->format('d.m.Y') }}</p>
|
||||||
|
<p>Срок оплаты: {{ $invoice->due_date->format('d.m.Y') }}</p>
|
||||||
|
|
||||||
|
<h2>Бронирование</h2>
|
||||||
|
<p>Гость: {{ $booking->guest_name }}</p>
|
||||||
|
<p>Тип номера: {{ $roomType->name }}</p>
|
||||||
|
<p>Даты: {{ $booking->check_in }} — {{ $booking->check_out }}</p>
|
||||||
|
|
||||||
|
<h2>Сумма</h2>
|
||||||
|
<p>{{ number_format($invoice->amount, 2, ',', ' ') }} ₽</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,24 +1,71 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\UsersController;
|
|
||||||
use Hamcrest\Number\OrderingComparison;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Http\Controllers\AdminAuthController;
|
||||||
use App\Http\Controllers\OrdersController;
|
use App\Http\Controllers\OrdersController;
|
||||||
|
use App\Http\Controllers\RoomTypesController;
|
||||||
|
use App\Http\Controllers\HotelController;
|
||||||
|
use App\Http\Controllers\RoomAvailabilityController;
|
||||||
|
use App\Http\Controllers\BookingController;
|
||||||
|
use App\Http\Controllers\InvoiceController;
|
||||||
|
|
||||||
Route::get(uri: '/user', action: function (Request $request): mixed {
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('/bookings/{id}/invoice', [InvoiceController::class, 'generate']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Route::post('/admin/login', [AdminAuthController::class, 'login']);
|
||||||
|
Route::post('/admin/logout', [AdminAuthController::class, 'logout'])->middleware('auth:sanctum');
|
||||||
|
|
||||||
|
Route::middleware('auth:admin')->prefix('admin')->group(function () {
|
||||||
|
Route::get('/dashboard', function () {
|
||||||
|
return response()->json(['message' => 'Welcome to admin dashboard']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::get('/user', function (\Illuminate\Http\Request $request) {
|
||||||
return $request->user();
|
return $request->user();
|
||||||
})->middleware(middleware: 'auth:sanctum');
|
});
|
||||||
|
|
||||||
Route::get(uri: '/orders', action: [OrdersController::class, 'index']);
|
Route::get('/orders', [OrdersController::class, 'index']);
|
||||||
|
Route::post('/orders', [OrdersController::class, 'create']);
|
||||||
|
|
||||||
|
Route::get('/room_types', [RoomTypesController::class, 'index']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('/hotels', [HotelController::class, 'store']);
|
||||||
|
Route::get('/hotels', [HotelController::class, 'index']);
|
||||||
|
Route::get('/hotels/{id}', [HotelController::class, 'show']);
|
||||||
|
Route::put('/hotels/{id}', [HotelController::class, 'update']);
|
||||||
|
Route::delete('/hotels/{id}', [HotelController::class, 'destroy']);
|
||||||
|
});
|
||||||
|
|
||||||
Route::get( 'orders', [OrdersController::class, 'index']);
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
Route::post('orders', [OrdersController::class, 'create']);
|
Route::post('/hotels/{hotelId}/room-types', [RoomTypeController::class, 'store']);
|
||||||
|
Route::put('/room-types/{id}', [RoomTypeController::class, 'update']);
|
||||||
|
Route::delete('/room-types/{id}', [RoomTypeController::class, 'destroy']);
|
||||||
|
});
|
||||||
|
|
||||||
Route::get( 'room_types', [RoomTypesController::class, 'index']);
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
//Route::get( 'availabilite', [HotelsController::class, 'index']);
|
Route::get('/room-types/{id}/availability', [RoomAvailabilityController::class, 'index']);
|
||||||
|
Route::post('/room-types/{id}/availability/bulk', [RoomAvailabilityController::class, 'bulkUpdate']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('/bookings', [BookingController::class, 'store']);
|
||||||
|
});
|
||||||
|
|
||||||
Route::post('users', [UsersController::class, 'create']);
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('/bookings/{id}/confirm', [BookingController::class, 'confirm']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('/bookings/{id}/cancel', [BookingController::class, 'cancel']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::get('/bookings', [BookingController::class, 'index']);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user