feat: add shift management functionality
This commit is contained in:
@@ -11,4 +11,15 @@ enum DocumentModalityEnum: string
|
||||
case Sonography = 'sonography';
|
||||
case Echo = 'echo';
|
||||
case Radiology = 'radiology';
|
||||
|
||||
public function price(): int
|
||||
{
|
||||
return match ($this) {
|
||||
self::CtScan => 1000000,
|
||||
self::Mri => 1200000,
|
||||
self::Sonography => 500000,
|
||||
self::Echo => 700000,
|
||||
self::Radiology => 400000,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
enum ShiftDayEnum: string
|
||||
{
|
||||
use EnumHasOptions;
|
||||
|
||||
case Saturday = 'شنبه';
|
||||
case Sunday = 'یکشنبه';
|
||||
case Monday = 'دوشنبه';
|
||||
case Tuesday = 'سهشنبه';
|
||||
case Wednesday = 'چهارشنبه';
|
||||
case Thursday = 'پنجشنبه';
|
||||
case Friday = 'جمعه';
|
||||
|
||||
public function carbonDay(): int
|
||||
{
|
||||
return match ($this) {
|
||||
self::Saturday => Carbon::SATURDAY,
|
||||
self::Sunday => Carbon::SUNDAY,
|
||||
self::Monday => Carbon::MONDAY,
|
||||
self::Tuesday => Carbon::TUESDAY,
|
||||
self::Wednesday => Carbon::WEDNESDAY,
|
||||
self::Thursday => Carbon::THURSDAY,
|
||||
self::Friday => Carbon::FRIDAY,
|
||||
};
|
||||
}
|
||||
|
||||
public static function fromDate(Carbon $date): self
|
||||
{
|
||||
return match ($date->dayOfWeek) {
|
||||
Carbon::SATURDAY => self::Saturday,
|
||||
Carbon::SUNDAY => self::Sunday,
|
||||
Carbon::MONDAY => self::Monday,
|
||||
Carbon::TUESDAY => self::Tuesday,
|
||||
Carbon::WEDNESDAY => self::Wednesday,
|
||||
Carbon::THURSDAY => self::Thursday,
|
||||
Carbon::FRIDAY => self::Friday,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Shift extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'day_of_week',
|
||||
'date',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'is_active',
|
||||
'user_id',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'date',
|
||||
'is_active' => 'boolean',
|
||||
'user_id' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
];
|
||||
|
||||
public function doctor()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeOpen($query)
|
||||
{
|
||||
return $query->whereNull('user_id');
|
||||
}
|
||||
}
|
||||
@@ -71,4 +71,9 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasMany(Pet::class, 'owner_id');
|
||||
}
|
||||
|
||||
public function shifts()
|
||||
{
|
||||
return $this->hasMany(Shift::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,19 @@ use App\Models\Document;
|
||||
use App\Models\Pet;
|
||||
use App\Models\User;
|
||||
use App\Services\FileStorageService;
|
||||
use App\Services\Shift\ShiftScheduleService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FastRequestService
|
||||
{
|
||||
protected FileStorageService $fileStorageService;
|
||||
|
||||
public function __construct(FileStorageService $fileStorageService)
|
||||
protected ShiftScheduleService $shiftScheduleService;
|
||||
|
||||
public function __construct(FileStorageService $fileStorageService, ShiftScheduleService $shiftScheduleService)
|
||||
{
|
||||
$this->fileStorageService = $fileStorageService;
|
||||
$this->shiftScheduleService = $shiftScheduleService;
|
||||
}
|
||||
|
||||
public function create(User $requester, array $data, array $historyFiles = [], array $scanFiles = []): Document
|
||||
@@ -38,9 +42,12 @@ class FastRequestService
|
||||
$this->attachFiles($document, $historyFiles, MediaCollectionEnum::History);
|
||||
$this->attachFiles($document, $scanFiles, MediaCollectionEnum::Primary);
|
||||
|
||||
if (!empty($data['doctor_id'])) {
|
||||
$doctorId = $data['doctor_id']
|
||||
?? $this->shiftScheduleService->autoSelectDoctor(now())?->id;
|
||||
|
||||
if (!empty($doctorId)) {
|
||||
$document->assignments()->create([
|
||||
'user_id' => $data['doctor_id'],
|
||||
'user_id' => $doctorId,
|
||||
'role' => 'رادیولوژیست',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Shift;
|
||||
|
||||
use App\Enums\DocumentStatusEnum;
|
||||
use App\Enums\ShiftDayEnum;
|
||||
use App\Enums\UserEnum;
|
||||
use App\Models\Shift;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ShiftScheduleService
|
||||
{
|
||||
public function shiftsCoveringMoment(Carbon $moment): Collection
|
||||
{
|
||||
$minutes = $moment->hour * 60 + $moment->minute;
|
||||
$today = ShiftDayEnum::fromDate($moment)->value;
|
||||
$yesterday = ShiftDayEnum::fromDate($moment->copy()->subDay())->value;
|
||||
|
||||
return Shift::active()
|
||||
->where(function ($query) use ($moment, $today, $yesterday) {
|
||||
$query->whereDate('date', $moment->toDateString())
|
||||
->orWhereDate('date', $moment->copy()->subDay()->toDateString())
|
||||
->orWhere(function ($subQuery) use ($today, $yesterday) {
|
||||
$subQuery->whereNull('date')->whereIn('day_of_week', [$today, $yesterday]);
|
||||
});
|
||||
})
|
||||
->get()
|
||||
->filter(function (Shift $shift) use ($moment, $minutes) {
|
||||
$start = $this->toMinutes($shift->start_time);
|
||||
$end = $this->toMinutes($shift->end_time);
|
||||
|
||||
if ($end > $start) {
|
||||
return $this->coversDate($shift, $moment) && $minutes >= $start && $minutes < $end;
|
||||
}
|
||||
|
||||
// overnight shift: covers [start, 24:00) on its day and [00:00, end) on the next day
|
||||
if ($this->coversDate($shift, $moment) && $minutes >= $start) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->coversDate($shift, $moment->copy()->subDay()) && $minutes < $end;
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
public function availableDoctorsAt(Carbon $moment): Collection
|
||||
{
|
||||
$doctorIds = $this->shiftsCoveringMoment($moment)->pluck('user_id')->filter()->unique();
|
||||
|
||||
if ($doctorIds->isEmpty()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return User::where('level', UserEnum::Doctor->value)
|
||||
->whereIn('id', $doctorIds)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function autoSelectDoctor(Carbon $moment): ?User
|
||||
{
|
||||
$doctorIds = $this->shiftsCoveringMoment($moment)->pluck('user_id')->filter()->unique();
|
||||
|
||||
if ($doctorIds->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return User::where('level', UserEnum::Doctor->value)
|
||||
->whereIn('id', $doctorIds)
|
||||
->withCount(['documents as open_documents_count' => function ($query) {
|
||||
$query->whereIn('status', [
|
||||
DocumentStatusEnum::Registered->value,
|
||||
DocumentStatusEnum::InProgress->value,
|
||||
]);
|
||||
}])
|
||||
->orderBy('open_documents_count')
|
||||
->orderBy('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function hasConflict(array $data, ?int $ignoreId = null): bool
|
||||
{
|
||||
if (empty($data['user_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$candidate = new Shift($data);
|
||||
|
||||
return Shift::active()
|
||||
->where('user_id', $data['user_id'])
|
||||
->when($ignoreId, fn ($query) => $query->where('id', '!=', $ignoreId))
|
||||
->get()
|
||||
->contains(fn (Shift $existing) => $this->overlaps($existing, $candidate));
|
||||
}
|
||||
|
||||
public function claim(int $shiftId, User $doctor): Shift
|
||||
{
|
||||
return DB::transaction(function () use ($shiftId, $doctor) {
|
||||
$shift = Shift::whereKey($shiftId)->lockForUpdate()->firstOrFail();
|
||||
|
||||
if (!$shift->is_active || $shift->user_id !== null) {
|
||||
throw ValidationException::withMessages([
|
||||
'shift' => 'این شیفت قابل انتخاب نیست.',
|
||||
]);
|
||||
}
|
||||
|
||||
$data = array_merge(
|
||||
$shift->only(['day_of_week', 'date', 'start_time', 'end_time']),
|
||||
['user_id' => $doctor->id]
|
||||
);
|
||||
|
||||
if ($this->hasConflict($data, $shift->id)) {
|
||||
throw ValidationException::withMessages([
|
||||
'shift' => 'این شیفت با برنامه فعلی شما تداخل دارد.',
|
||||
]);
|
||||
}
|
||||
|
||||
$shift->update(['user_id' => $doctor->id]);
|
||||
|
||||
return $shift;
|
||||
});
|
||||
}
|
||||
|
||||
public function release(Shift $shift, User $doctor): Shift
|
||||
{
|
||||
if ($shift->user_id !== $doctor->id) {
|
||||
throw ValidationException::withMessages([
|
||||
'shift' => 'شما مجاز به تغییر این شیفت نیستید.',
|
||||
]);
|
||||
}
|
||||
|
||||
$shift->update(['user_id' => null]);
|
||||
|
||||
return $shift;
|
||||
}
|
||||
|
||||
public function overlaps(Shift $first, Shift $second): bool
|
||||
{
|
||||
$byWeekday = !$first->date || !$second->date;
|
||||
|
||||
foreach ($this->daySegments($first, $byWeekday) as [$dayA, $startA, $endA]) {
|
||||
foreach ($this->daySegments($second, $byWeekday) as [$dayB, $startB, $endB]) {
|
||||
if ($dayA === $dayB && $startA < $endB && $startB < $endA) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function coversDate(Shift $shift, Carbon $date): bool
|
||||
{
|
||||
if ($shift->date) {
|
||||
return $shift->date->isSameDay($date);
|
||||
}
|
||||
|
||||
return $shift->day_of_week === ShiftDayEnum::fromDate($date)->value;
|
||||
}
|
||||
|
||||
protected function daySegments(Shift $shift, bool $byWeekday): array
|
||||
{
|
||||
$start = $this->toMinutes($shift->start_time);
|
||||
$end = $this->toMinutes($shift->end_time);
|
||||
|
||||
if ($shift->date) {
|
||||
$day = $byWeekday ? ShiftDayEnum::fromDate($shift->date)->value : $shift->date->toDateString();
|
||||
$nextDay = $byWeekday
|
||||
? ShiftDayEnum::fromDate($shift->date->copy()->addDay())->value
|
||||
: $shift->date->copy()->addDay()->toDateString();
|
||||
} else {
|
||||
$day = $shift->day_of_week;
|
||||
$nextDay = $this->nextDay($shift->day_of_week);
|
||||
}
|
||||
|
||||
if ($end > $start) {
|
||||
return [[$day, $start, $end]];
|
||||
}
|
||||
|
||||
return [[$day, $start, 1440], [$nextDay, 0, $end]];
|
||||
}
|
||||
|
||||
protected function nextDay(string $day): string
|
||||
{
|
||||
$days = ShiftDayEnum::values();
|
||||
$index = array_search($day, $days);
|
||||
|
||||
return $days[($index + 1) % count($days)];
|
||||
}
|
||||
|
||||
protected function toMinutes(string $time): int
|
||||
{
|
||||
[$hours, $minutes] = explode(':', $time);
|
||||
|
||||
return ((int) $hours) * 60 + ((int) $minutes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user