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;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@@ -10,13 +10,12 @@ use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
@@ -27,7 +26,7 @@ class User extends Authenticatable
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'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 = [
|
||||
'email_verified_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Проверяет, является ли пользователь админом.
|
||||
*/
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
return $this->is_admin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,16 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
//
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
// Middleware
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
})->create();
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
$exceptions->render(function (Throwable $e, Request $request) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'message' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
});
|
||||
})
|
||||
->create();
|
||||
@@ -7,8 +7,9 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-dompdf": "^3.1",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/sanctum": "^4.2",
|
||||
"laravel/tinker": "^2.10.1"
|
||||
},
|
||||
"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
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default authentication "guard" and password
|
||||
| reset "broker" for your application. You may change these values
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -25,13 +25,13 @@ return [
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| 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
|
||||
| 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',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'sanctum',
|
||||
'provider' => 'users',
|
||||
'hash' => false,
|
||||
],
|
||||
|
||||
'admin' => [ // ← Этот guard нужен для админов
|
||||
'driver' => 'sanctum',
|
||||
'provider' => 'admins',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -47,12 +58,12 @@ return [
|
||||
| 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
|
||||
| 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
|
||||
| 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.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
@@ -62,13 +73,13 @@ return [
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
'model' => App\Models\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
'admins' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\Admin::class,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -76,18 +87,14 @@ return [
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options specify the behavior of Laravel's password
|
||||
| reset functionality, including the table utilized for token storage
|
||||
| and the user provider that is invoked to actually retrieve users.
|
||||
| You may specify multiple password reset configurations if you have more
|
||||
| than one user table or model in the application and you want to have
|
||||
| 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
|
||||
| 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' => [
|
||||
@@ -112,4 +119,4 @@ return [
|
||||
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
];
|
||||
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
|
||||
{
|
||||
Schema::create('table_room_types', function (Blueprint $table) {
|
||||
Schema::create('hotels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('address');
|
||||
$table->text('description')->nullable();
|
||||
$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
|
||||
{
|
||||
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
|
||||
{
|
||||
Schema::create('table_room_availability', function (Blueprint $table) {
|
||||
Schema::create('room_availability', function (Blueprint $table) {
|
||||
$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->unique(['room_type_id', 'date']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +28,6 @@ return new class extends Migration
|
||||
*/
|
||||
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
|
||||
{
|
||||
Schema::create('table_hotels', function (Blueprint $table) {
|
||||
Schema::create('invoices', function (Blueprint $table) {
|
||||
$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->text('name');
|
||||
$table->text('description');
|
||||
$table->text('address');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +27,6 @@ return new class extends Migration
|
||||
*/
|
||||
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
|
||||
|
||||
use App\Http\Controllers\UsersController;
|
||||
use Hamcrest\Number\OrderingComparison;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\AdminAuthController;
|
||||
use App\Http\Controllers\OrdersController;
|
||||
|
||||
Route::get(uri: '/user', action: function (Request $request): mixed {
|
||||
return $request->user();
|
||||
})->middleware(middleware: 'auth:sanctum');
|
||||
|
||||
Route::get(uri: '/orders', action: [OrdersController::class, 'index']);
|
||||
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( 'orders', [OrdersController::class, 'index']);
|
||||
Route::post('orders', [OrdersController::class, 'create']);
|
||||
|
||||
Route::get( 'room_types', [RoomTypesController::class, 'index']);
|
||||
//Route::get( 'availabilite', [HotelsController::class, 'index']);
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::post('/bookings/{id}/invoice', [InvoiceController::class, 'generate']);
|
||||
});
|
||||
|
||||
|
||||
Route::post('users', [UsersController::class, 'create']);
|
||||
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();
|
||||
});
|
||||
|
||||
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::middleware('auth:sanctum')->group(function () {
|
||||
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::middleware('auth:sanctum')->group(function () {
|
||||
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::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