feat: add shift management functionality

This commit is contained in:
2026-08-26 18:53:15 +03:30
parent 2d96978c38
commit aeef95339d
34 changed files with 1953 additions and 15 deletions
@@ -0,0 +1,145 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Shift;
use App\Http\Resources\Admin\ShiftResource;
use App\Http\Requests\Admin\ShiftRequest;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Inertia\Inertia;
class ShiftController extends Controller
{
use AuthorizesRequests;
public function index(Request $request)
{
$this->authorize('shifts.index');
$lists = Shift::with(['doctor'])
->when(isset($request->search) , function ($query) use ($request){
$query->where(function ($q) use ($request) {
$q->whereHas('doctor', function ($doctorQuery) use ($request) {
$doctorQuery->where('fullname', 'LIKE', "%{$request->search}%");
})->orWhere('id', $request->search);
});
})
->when(isset($request->day_of_week) && $request->day_of_week != 'همه روزها', function ($query) use ($request) {
$query->whereIn('day_of_week' , (array) $request->day_of_week);
})
->when(isset($request->status) && $request->status != 'همه وضعیت ها', function ($query) use ($request) {
$query->where('is_active' , $request->status == 'فعال');
})
->when(isset($request->occupancy) && $request->occupancy != 'همه شیفت ها', function ($query) use ($request) {
$request->occupancy == 'آزاد'
? $query->whereNull('user_id')
: $query->whereNotNull('user_id');
})
->latest()
->paginate(20)
->withQueryString()
->onEachSide(1);
$shifts = ShiftResource::collection($lists);
return Inertia::admin('Shifts/index', [
'shifts' => $shifts,
'search' => $request->search,
'day_of_week' => $request->day_of_week,
'status' => $request->status,
'occupancy' => $request->occupancy,
'permissions_edit' => auth()->user()->can('shifts.edit') ? false : true,
'permissions_delete' => auth()->user()->can('shifts.delete') ? false : true,
]);
}
public function create()
{
$this->authorize('shifts.create');
return Inertia::admin('Shifts/create_and_edit');
}
public function store(ShiftRequest $request)
{
$this->authorize('shifts.create');
Shift::create([
'user_id' => $request->user_id,
'day_of_week' => $request->day_of_week,
'date' => $request->date,
'start_time' => $request->start_time,
'end_time' => $request->end_time,
'is_active' => $request->is_active,
'created_by' => auth()->id(),
]);
session()->flash('success', 'شیفت با موفقیت ثبت شد');
return response(null , 200);
}
public function show(Shift $shift)
{
//
}
public function edit(Shift $shift)
{
$this->authorize('shifts.edit');
return Inertia::admin('Shifts/create_and_edit', [
'shift' => ShiftResource::make($shift->load('doctor'))->resolve(),
'is_edit' => true,
]);
}
public function update(ShiftRequest $request, Shift $shift)
{
$this->authorize('shifts.edit');
$shift->update([
'user_id' => $request->user_id,
'day_of_week' => $request->day_of_week,
'date' => $request->date,
'start_time' => $request->start_time,
'end_time' => $request->end_time,
'is_active' => $request->is_active,
]);
session()->flash('success', 'شیفت با موفقیت بروزرسانی شد');
return response(null , 200);
}
public function destroy(Shift $shift)
{
$this->authorize('shifts.delete');
$shift->delete();
session()->flash('success', 'شیفت با موفقیت حذف شد');
return response(null , 200);
}
public function calendarData()
{
$this->authorize('shifts.index');
$lists = Shift::with('doctor')->get();
return response()->json([
'success' => true,
'data' => ShiftResource::collection($lists),
]);
}
public function bulkDelete(Request $request)
{
$this->authorize('shifts.delete');
Shift::destroy($request->ids);
session()->flash('success', 'شیفت های مورد نظر با موفقیت حذف شدند.');
return response(null , 200);
}
}
@@ -2,14 +2,77 @@
namespace App\Http\Controllers\Client\Doctor;
use App\Enums\ShiftDayEnum;
use App\Enums\UserEnum;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Resources\Client\ShiftResource;
use App\Models\Shift;
use App\Services\Shift\ShiftScheduleService;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
class ShiftController extends Controller
{
protected ShiftScheduleService $shiftScheduleService;
public function __construct(ShiftScheduleService $shiftScheduleService)
{
$this->shiftScheduleService = $shiftScheduleService;
}
public function index()
{
return Inertia::client('Dashboard/Shifts/Index');
$this->ensureDoctor();
$myShifts = Shift::where('user_id', Auth::id())->get();
$openShifts = Shift::active()
->open()
->where(function ($query) {
$query->whereNull('date')
->orWhereDate('date', '>=', now()->toDateString());
})
->get();
return Inertia::client('Dashboard/Shifts/Index', [
'myShifts' => ShiftResource::collection($this->sortSchedule($myShifts)),
'openShifts' => ShiftResource::collection($this->sortSchedule($openShifts)),
]);
}
public function update(Shift $shift)
{
$this->ensureDoctor();
$this->shiftScheduleService->claim($shift->id, Auth::user());
return response(null, 200);
}
public function destroy(Shift $shift)
{
$this->ensureDoctor();
$this->shiftScheduleService->release($shift, Auth::user());
return response(null, 200);
}
protected function ensureDoctor(): void
{
abort_unless(Auth::user()->level === UserEnum::Doctor->value, 403);
}
protected function sortSchedule($shifts)
{
$dayOrder = array_flip(ShiftDayEnum::values());
return $shifts
->sortBy([
fn ($a, $b) => strcmp($a->date?->toDateString() ?? '', $b->date?->toDateString() ?? ''),
fn ($a, $b) => ($dayOrder[$a->day_of_week] ?? 0) <=> ($dayOrder[$b->day_of_week] ?? 0),
fn ($a, $b) => strcmp($a->start_time, $b->start_time),
])
->values();
}
}
@@ -45,7 +45,7 @@ class FastRequestController extends Controller
);
return response()->json([
'document' => new FastRequestResource($document->load('pet')),
'document' => new FastRequestResource($document->refresh()->load('pet')),
]);
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace App\Http\Requests\Admin;
use App\Enums\ShiftDayEnum;
use App\Enums\UserEnum;
use App\Services\Shift\ShiftScheduleService;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;
class ShiftRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'user_id' => ['nullable', Rule::exists('users', 'id')->where('level', UserEnum::Doctor->value)],
'day_of_week' => ['nullable', 'required_without:date', 'string', Rule::in(ShiftDayEnum::values())],
'date' => ['nullable', 'required_without:day_of_week', 'date'],
'start_time' => ['required', 'date_format:H:i'],
'end_time' => ['required', 'date_format:H:i', 'different:start_time'],
'is_active' => ['required', 'boolean'],
];
}
public function prepareForValidation()
{
if ($this->filled('date')) {
$this->merge(['day_of_week' => null]);
}
}
public function withValidator(Validator $validator): void
{
$validator->after(function (Validator $validator) {
if ($validator->errors()->isNotEmpty()) {
return;
}
$scheduleService = app(ShiftScheduleService::class);
$ignoreId = $this->route('shift')?->id;
if ($scheduleService->hasConflict($this->validated(), $ignoreId)) {
$validator->errors()->add('start_time', 'این شیفت با شیفت دیگری از این پزشک تداخل دارد.');
}
});
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources\Admin;
use App\Enums\ShiftDayEnum;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ShiftResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'user_id' => $this->user_id,
'doctor' => $this->relationLoaded('doctor') && $this->doctor ? $this->doctor->only(['id', 'fullname']) : null,
'day_of_week' => $this->day_of_week,
'day_index' => $this->day_of_week ? ShiftDayEnum::from($this->day_of_week)->carbonDay() : null,
'date' => $this->date ? verta($this->date)->format('%Y/%m/%d') : null,
'date_raw' => $this->date?->format('Y-m-d'),
'start_time' => substr($this->start_time, 0, 5),
'end_time' => substr($this->end_time, 0, 5),
'is_active' => $this->is_active,
'created_at' => verta($this->created_at)->format('%Y/%m/%d - H:i'),
];
}
}
@@ -2,6 +2,7 @@
namespace App\Http\Resources\Client;
use App\Enums\DocumentModalityEnum;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -16,6 +17,10 @@ class FastRequestResource extends JsonResource
'status' => $this->status,
'priority' => $this->priority,
'pet' => new PetArchiveResource($this->whenLoaded('pet')),
'invoice' => [
'number' => str_pad((string) $this->id, 5, '0', STR_PAD_LEFT),
'amount' => DocumentModalityEnum::from($this->modality)->price(),
],
];
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources\Client;
use App\Enums\ShiftDayEnum;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ShiftResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'day_of_week' => $this->day_of_week,
'day_index' => $this->day_of_week ? ShiftDayEnum::from($this->day_of_week)->carbonDay() : null,
'date' => $this->date ? verta($this->date)->format('%Y/%m/%d') : null,
'date_raw' => $this->date?->format('Y-m-d'),
'start_time' => substr($this->start_time, 0, 5),
'end_time' => substr($this->end_time, 0, 5),
'is_active' => $this->is_active,
];
}
}