coursework tasks 1 to 10
This commit is contained in:
169
app/Http/Controllers/AvailabilitiesController.php
Normal file
169
app/Http/Controllers/AvailabilitiesController.php
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\EmployeeAvailability;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AvailabilitiesController extends Controller
|
||||||
|
{
|
||||||
|
// GET api/admin/availabilities?employee_id=5&date=2025-06-15
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$query = EmployeeAvailability::query();
|
||||||
|
|
||||||
|
if ($request->employee_id) {
|
||||||
|
$query->where('employee_id', $request->employee_id);
|
||||||
|
}
|
||||||
|
if ($request->date) {
|
||||||
|
$query->where('date', $request->date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$availabilities = $query->get();
|
||||||
|
return response()->json($availabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/admin/availabilities - создать один слот
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'employee_id' => 'required|exists:users,id',
|
||||||
|
'date' => 'required|date',
|
||||||
|
'starttime' => 'required',
|
||||||
|
'endtime' => 'required|after:starttime',
|
||||||
|
'isavailable' => 'boolean'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$availability = EmployeeAvailability::create($request->all());
|
||||||
|
return response()->json($availability, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/admin/availabilities/bulk - создать несколько слотов
|
||||||
|
public function bulkStore(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'employee_id' => 'required|exists:users,id',
|
||||||
|
'date' => 'required|date',
|
||||||
|
'intervals' => 'required|array|min:1'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$availabilities = [];
|
||||||
|
foreach ($request->intervals as $interval) {
|
||||||
|
$availability = EmployeeAvailability::create([
|
||||||
|
'employee_id' => $request->employee_id,
|
||||||
|
'date' => $request->date,
|
||||||
|
'starttime' => $interval['start'],
|
||||||
|
'endtime' => $interval['end'],
|
||||||
|
'isavailable' => true
|
||||||
|
]);
|
||||||
|
$availabilities[] = $availability;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($availabilities, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE api/admin/availabilities/{id} - удалить слот (брони остаются!)
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$availability = EmployeeAvailability::findOrFail($id);
|
||||||
|
$availability->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Слот удален из расписания (брони сохранены)']);
|
||||||
|
}
|
||||||
|
public function publicAvailability(Request $request)
|
||||||
|
{
|
||||||
|
$serviceId = $request->query('service_id');
|
||||||
|
$date = $request->query('date');
|
||||||
|
|
||||||
|
if (!$serviceId || !$date) {
|
||||||
|
return response()->json(['error' => 'service_id и date обязательны'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Найти услугу и получить длительность
|
||||||
|
$service = \App\Models\Services::find($serviceId);
|
||||||
|
if (!$service) {
|
||||||
|
return response()->json(['error' => 'Услуга не найдена'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$durationMinutes = $service->durationminutes;
|
||||||
|
|
||||||
|
// Найти сотрудников с расписанием на эту дату
|
||||||
|
$availabilities = \App\Models\EmployeeAvailability::where('date', $date)
|
||||||
|
->where('isavailable', true)
|
||||||
|
->with('employee') // связь с User
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$freeSlots = [];
|
||||||
|
|
||||||
|
foreach ($availabilities as $availability) {
|
||||||
|
$employeeId = $availability->employee_id;
|
||||||
|
|
||||||
|
// Найти занятые слоты этого сотрудника
|
||||||
|
$bookings = \App\Models\Booking::where('employee_id', $employeeId)
|
||||||
|
->where('bookingdate', $date)
|
||||||
|
->where('status', '!=', 'cancelled')
|
||||||
|
->pluck('starttime', 'endtime');
|
||||||
|
|
||||||
|
// Генерировать возможные слоты с учетом duration
|
||||||
|
$start = new \DateTime($availability->starttime);
|
||||||
|
$end = new \DateTime($availability->endtime);
|
||||||
|
|
||||||
|
$current = clone $start;
|
||||||
|
while ($current < $end) {
|
||||||
|
$slotEnd = clone $current;
|
||||||
|
$slotEnd->modify("+{$durationMinutes} minutes");
|
||||||
|
|
||||||
|
// Проверить пересечение с бронями
|
||||||
|
$isFree = true;
|
||||||
|
foreach ($bookings as $bookingStart => $bookingEnd) {
|
||||||
|
$bookingStartTime = new \DateTime($bookingStart);
|
||||||
|
$bookingEndTime = new \DateTime($bookingEnd);
|
||||||
|
|
||||||
|
if ($current < $bookingEndTime && $slotEnd > $bookingStartTime) {
|
||||||
|
$isFree = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isFree && $slotEnd <= $end) {
|
||||||
|
$freeSlots[] = [
|
||||||
|
'employee_id' => $employeeId,
|
||||||
|
'start' => $current->format('H:i'),
|
||||||
|
'end' => $slotEnd->format('H:i')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$current->modify('+30 minutes'); // шаг 30 мин
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($freeSlots);
|
||||||
|
}
|
||||||
|
public function cancel(Request $request, $id)
|
||||||
|
{
|
||||||
|
$booking = Booking::findOrFail($id);
|
||||||
|
|
||||||
|
// Проверка: только автор брони может отменить
|
||||||
|
if ($booking->client_id != auth()->id()) {
|
||||||
|
return response()->json(['error' => 'Можете отменить только свою бронь'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка: нельзя отменить уже отмененную/выполненную
|
||||||
|
if ($booking->status != 'confirmed') {
|
||||||
|
return response()->json(['error' => 'Можно отменить только подтвержденные брони'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновить статус
|
||||||
|
$booking->update([
|
||||||
|
'status' => 'cancelled',
|
||||||
|
'cancelledby' => 'client',
|
||||||
|
'cancelreason' => $request->reason ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Бронь отменена',
|
||||||
|
'booking' => $booking
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
108
app/Http/Controllers/BookingsController.php
Normal file
108
app/Http/Controllers/BookingsController.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Booking;
|
||||||
|
use App\Models\Services;
|
||||||
|
use App\Models\EmployeeAvailability;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class BookingsController extends Controller
|
||||||
|
{
|
||||||
|
// POST api/bookings - создание брони (ТОЛЬКО клиенты)
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'service_id' => 'required|exists:services,id',
|
||||||
|
'employee_id' => 'required|exists:users,id',
|
||||||
|
'date' => 'required|date',
|
||||||
|
'starttime' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$clientId = auth()->id();
|
||||||
|
if (!$clientId) {
|
||||||
|
return response()->json(['error' => 'Авторизация обязательна'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить активную услугу
|
||||||
|
$service = Services::where('id', $request->service_id)
|
||||||
|
->where('isactive', true)
|
||||||
|
->first();
|
||||||
|
if (!$service) {
|
||||||
|
return response()->json(['error' => 'Услуга неактивна или не найдена'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$durationMinutes = $service->durationminutes;
|
||||||
|
$endtime = date('H:i:s', strtotime($request->starttime . " +{$durationMinutes} minutes"));
|
||||||
|
|
||||||
|
// Проверить доступность сотрудника
|
||||||
|
$availability = EmployeeAvailability::where('employee_id', $request->employee_id)
|
||||||
|
->where('date', $request->date)
|
||||||
|
->where('starttime', '<=', $request->starttime)
|
||||||
|
->where('endtime', '>=', $endtime)
|
||||||
|
->where('isavailable', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$availability) {
|
||||||
|
return response()->json(['error' => 'Сотрудник недоступен в это время'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить уникальность слота
|
||||||
|
$bookingExists = Booking::where('employee_id', $request->employee_id)
|
||||||
|
->where('bookingdate', $request->date)
|
||||||
|
->where('starttime', $request->starttime)
|
||||||
|
->whereIn('status', ['confirmed', 'completed'])
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($bookingExists) {
|
||||||
|
return response()->json(['error' => 'Слот уже забронирован'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создать бронь
|
||||||
|
$bookingNumber = 'CL-' . date('Y') . '-' . str_pad(Booking::count() + 1, 4, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
$booking = Booking::create([
|
||||||
|
'bookingnumber' => $bookingNumber,
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'employee_id' => $request->employee_id,
|
||||||
|
'service_id' => $request->service_id,
|
||||||
|
'bookingdate' => $request->date,
|
||||||
|
'starttime' => $request->starttime,
|
||||||
|
'endtime' => $endtime,
|
||||||
|
'status' => 'confirmed'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'booking' => $booking,
|
||||||
|
'message' => 'Бронирование создано №' . $bookingNumber
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/bookings/{id}/cancel - отмена клиентом
|
||||||
|
public function cancel(Request $request, $id)
|
||||||
|
{
|
||||||
|
$booking = Booking::findOrFail($id);
|
||||||
|
|
||||||
|
// Только автор брони может отменить
|
||||||
|
if ($booking->client_id != auth()->id()) {
|
||||||
|
return response()->json(['error' => 'Можете отменить только свою бронь'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Только confirmed брони
|
||||||
|
if ($booking->status != 'confirmed') {
|
||||||
|
return response()->json(['error' => 'Можно отменить только подтвержденные'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking->update([
|
||||||
|
'status' => 'cancelled',
|
||||||
|
'cancelledby' => 'client',
|
||||||
|
'cancelreason' => $request->reason ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Бронь отменена',
|
||||||
|
'booking' => $booking
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,78 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Services;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ServicesController extends Controller
|
class ServicesController extends Controller
|
||||||
{
|
{
|
||||||
//
|
// GET api/admin/services - список активных услуг
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$services = Services::where('isactive', true)->get();
|
||||||
|
return response()->json($services);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/admin/services - создать услугу
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'description' => 'required|string',
|
||||||
|
'durationminutes' => 'required|integer|min:1|max:500',
|
||||||
|
'price' => 'required|numeric|min:0'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$service = Services::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'durationminutes' => $request->durationminutes,
|
||||||
|
'price' => $request->price,
|
||||||
|
'isactive' => true // по умолчанию активна
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($service, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT api/admin/services/{id} - обновить услугу
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$service = Services::findOrFail($id);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'description' => 'required|string',
|
||||||
|
'durationminutes' => 'required|integer|min:1|max:500',
|
||||||
|
'price' => 'required|numeric|min:0'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$service->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'durationminutes' => $request->durationminutes,
|
||||||
|
'price' => $request->price,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE api/admin/services/{id} - только если нет активных броней
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$service = Services::findOrFail($id);
|
||||||
|
|
||||||
|
// ПРОВЕРКА: нельзя удалить услугу с активными бронями
|
||||||
|
$activeBookings = \App\Models\Booking::where('service_id', $id)
|
||||||
|
->where('status', '!=', 'cancelled')
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($activeBookings) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Нельзя удалить услугу с активными бронями'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->delete();
|
||||||
|
return response()->json(['message' => 'Услуга удалена']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,32 +5,30 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use SebastianBergmann\CodeCoverage\Report\Xml\Project;
|
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
public function create(Request $request)
|
// /api/register - ТОЛЬКО клиенты (role = client)
|
||||||
{
|
public function register(Request $request)
|
||||||
$user = new User();
|
{
|
||||||
$name = $request->get(key:'name');
|
$request->validate([
|
||||||
$password = Hash::make($request->get(key:'password'));
|
'name' => 'required|string|max:255',
|
||||||
$email = $request->get(key: 'email');
|
'email' => 'required|email|unique:users',
|
||||||
|
'password' => 'required|min:6'
|
||||||
|
]);
|
||||||
|
|
||||||
$user->name = $name;
|
$user = User::create([
|
||||||
$user->email = $email;
|
'name' => $request->name,
|
||||||
$user->password = $password;
|
'email' => $request->email,
|
||||||
$user->save();
|
'password' => Hash::make($request->password),
|
||||||
|
'role' => 'client' // ТОЛЬКО клиенты
|
||||||
|
]);
|
||||||
|
|
||||||
dispatch(function() use ($user) {
|
$token = $user->createToken('client-token')->plainTextToken;
|
||||||
$project= new Project();
|
|
||||||
$project->title = 'default project';
|
|
||||||
$project->description = 'test';
|
|
||||||
$project->creator_user_id = $user->id;
|
|
||||||
$project->save();
|
|
||||||
|
|
||||||
});
|
return response()->json([
|
||||||
|
'user' => $user,
|
||||||
return ['toker' => $user->createToken('frotend')];
|
'token' => $token
|
||||||
|
], 201);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
app/Http/Middleware/CheckRole.php
Normal file
16
app/Http/Middleware/CheckRole.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CheckRole
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next, $role)
|
||||||
|
{
|
||||||
|
if (!auth()->check() || !auth()->user()->isEmployeeOrAdmin()) {
|
||||||
|
return response()->json(['error' => 'Доступ запрещен'], 403);
|
||||||
|
}
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
app/Models/Booking.php
Normal file
23
app/Models/Booking.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Models;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
// бронирование ĸлиентов
|
||||||
|
class Booking extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'bookings';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'bookingnumber',
|
||||||
|
'client_id',
|
||||||
|
'employee_id',
|
||||||
|
'service_id',
|
||||||
|
'bookingdate',
|
||||||
|
'starttime',
|
||||||
|
'endtime',
|
||||||
|
'status',
|
||||||
|
'cancelledby',
|
||||||
|
'cancelreason'
|
||||||
|
];
|
||||||
|
}
|
||||||
10
app/Models/EmployeeAvailability.php
Normal file
10
app/Models/EmployeeAvailability.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Models;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
// расписание сотрудниĸов
|
||||||
|
class EmployeeAvailability extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
protected $table = 'employee_availabilities';
|
||||||
|
protected $fillable = ['employee_id','date','starttime','endtime','isavailable'];
|
||||||
|
}
|
||||||
@@ -2,9 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class Services extends Model
|
class Services extends Model // услуги ĸлининга
|
||||||
{
|
{
|
||||||
//
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'durationminutes',
|
||||||
|
'price',
|
||||||
|
'isactive',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Простая связь с bookings, если нужно
|
||||||
|
public function bookings()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Booking::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,43 +2,27 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// 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;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
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.
|
|
||||||
*
|
|
||||||
* @var list<string>
|
|
||||||
*/
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'role',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that should be hidden for serialization.
|
|
||||||
*
|
|
||||||
* @var list<string>
|
|
||||||
*/
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the attributes that should be cast.
|
|
||||||
*
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -46,4 +30,16 @@ class User extends Authenticatable
|
|||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяет админ или сотрудник
|
||||||
|
public function isEmployeeOrAdmin()
|
||||||
|
{
|
||||||
|
return $this->role == 'employee' || $this->role == 'admin';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для запросов - все сотрудники и админы
|
||||||
|
public static function scopeEmployeeOrAdmin($query)
|
||||||
|
{
|
||||||
|
return $query->whereIn('role', ['employee', 'admin']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ 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->alias([
|
||||||
})
|
'role:employee' => \App\Http\Middleware\CheckRole::class,
|
||||||
|
]);
|
||||||
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
})->create();
|
})->create();
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?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('employee_availabilities', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('employee_id');
|
||||||
|
$table->date('date');
|
||||||
|
$table->time('starttime');
|
||||||
|
$table->time('endtime');
|
||||||
|
$table->boolean('isavailable')->default(true);
|
||||||
|
$table->timestamps(); // автоматическое создание created_at и updated_at
|
||||||
|
|
||||||
|
$table->foreign('employee_id')->references('id')->on('users');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down() {
|
||||||
|
Schema::dropIfExists('employee_availabilities');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?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('bookings', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('bookingnumber');
|
||||||
|
$table->unsignedBigInteger('client_id');
|
||||||
|
$table->unsignedBigInteger('employee_id');
|
||||||
|
$table->unsignedBigInteger('service_id');
|
||||||
|
$table->date('bookingdate');
|
||||||
|
$table->time('starttime');
|
||||||
|
$table->time('endtime');
|
||||||
|
$table->enum('status', ['confirmed', 'cancelled', 'completed'])->default('confirmed');
|
||||||
|
$table->enum('cancelledby', ['client', 'admin'])->nullable();
|
||||||
|
$table->text('cancelreason')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Внешние ключи
|
||||||
|
$table->foreign('client_id')->references('id')->on('users');
|
||||||
|
$table->foreign('employee_id')->references('id')->on('users');
|
||||||
|
$table->foreign('service_id')->references('id')->on('services');
|
||||||
|
|
||||||
|
// УНИКАЛЬНЫЙ ИНДЕКС
|
||||||
|
$table->unique(['employee_id', 'bookingdate', 'starttime'], 'unique_booking_slot');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down() {
|
||||||
|
Schema::dropIfExists('bookings');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,16 +1,45 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
|
use App\Http\Controllers\ServicesController;
|
||||||
|
use App\Http\Controllers\BookingsController;
|
||||||
|
use App\Http\Controllers\AvailabilitiesController;
|
||||||
|
use App\Http\Controllers\CategoriesController;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Http\Controllers\CategoriesController;
|
|
||||||
|
|
||||||
|
|
||||||
Route::get('/user', function (Request $request) {
|
Route::get('/user', function (Request $request) {
|
||||||
return $request->user();
|
return $request->user();
|
||||||
})->middleware('auth:sanctum');
|
})->middleware('auth:sanctum');
|
||||||
|
|
||||||
|
// РЕГИСТРАЦИЯ ТОЛЬКО КЛИЕНТОВ (публичный)
|
||||||
|
Route::post('/register', [UserController::class, 'register']);
|
||||||
|
|
||||||
|
// Существующие роуты categories
|
||||||
Route::get('/categories', [CategoriesController::class, 'index'])->middleware('auth:sanctum');
|
Route::get('/categories', [CategoriesController::class, 'index'])->middleware('auth:sanctum');
|
||||||
Route::get('/categories/{id}', [CategoriesController::class, 'show']);
|
Route::get('/categories/{id}', [CategoriesController::class, 'show']);
|
||||||
Route::post( '/categories', [CategoriesController::class, 'create']);
|
Route::post('/categories', [CategoriesController::class, 'create']);
|
||||||
Route::post( '/users', [UserController::class, 'create']);
|
|
||||||
|
// ПУБЛИЧНЫЙ API доступности (без авторизации)
|
||||||
|
Route::get('/availability', [AvailabilitiesController::class, 'publicAvailability']);
|
||||||
|
|
||||||
|
// КЛИЕНТСКИЕ РОУТЫ БРОНИРОВАНИЙ (auth:sanctum)
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('/bookings', [BookingsController::class, 'store']); // Пункт 9
|
||||||
|
Route::post('/bookings/{id}/cancel', [BookingsController::class, 'cancel']); // Пункт 10
|
||||||
|
});
|
||||||
|
|
||||||
|
// АДМИН РОУТЫ - ТОЛЬКО employee/admin (role:employee)
|
||||||
|
Route::middleware(['auth:sanctum', 'role:employee'])->prefix('admin')->group(function () {
|
||||||
|
// CRUD услуги
|
||||||
|
Route::get('/services', [ServicesController::class, 'index']);
|
||||||
|
Route::post('/services', [ServicesController::class, 'store']);
|
||||||
|
Route::put('/services/{id}', [ServicesController::class, 'update']);
|
||||||
|
Route::delete('/services/{id}', [ServicesController::class, 'destroy']);
|
||||||
|
|
||||||
|
// CRUD расписание
|
||||||
|
Route::get('/availabilities', [AvailabilitiesController::class, 'index']);
|
||||||
|
Route::post('/availabilities', [AvailabilitiesController::class, 'store']);
|
||||||
|
Route::post('/availabilities/bulk', [AvailabilitiesController::class, 'bulkStore']);
|
||||||
|
Route::delete('/availabilities/{id}', [AvailabilitiesController::class, 'destroy']);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user