feat: Enhance appointment management by decoupling online booking toggle for admin context
- Introduced management mode for appointment slots, allowing doctors, admins, and clinic managers to view and book slots regardless of the online booking status. - Updated SlotCalculatorService to accept a management context parameter, bypassing online booking restrictions. - Modified appointment-related endpoints to handle management context and ensure proper authorization checks. - Added tests to verify that management users can access slots even when online booking is disabled, while public users are still restricted. - Improved documentation for API endpoints to reflect new management parameters and behaviors.
This commit is contained in:
@@ -32,9 +32,9 @@ class SlotCalculatorService
|
||||
*
|
||||
* @return array[] [{start, end, start_time, end_time, location_id}]
|
||||
*/
|
||||
public function getAvailableSlots(Doctor $doctor, string $date, ?Clinic $clinic = null): array
|
||||
public function getAvailableSlots(Doctor $doctor, string $date, ?Clinic $clinic = null, bool $forManagement = false): array
|
||||
{
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic);
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic, $forManagement);
|
||||
if (empty($sessions)) return [];
|
||||
$flat = array_merge(...array_map(fn($s) => $s['slots'], $sessions));
|
||||
return $this->filterBookedSlots($doctor, $flat);
|
||||
@@ -42,11 +42,14 @@ class SlotCalculatorService
|
||||
|
||||
/**
|
||||
* آدرس (location_id) متناظر با اسلاتِ شروعشده در تاریخ مشخص. اگر پیدا نشد null.
|
||||
*
|
||||
* برای ثبتِ نوبت از پنل ($forManagement=true) نباید خاموشبودنِ نوبتدهی آنلاین
|
||||
* باعث گمشدنِ location شود؛ وگرنه نوبتِ دستی بدون آدرس ثبت میشد.
|
||||
*/
|
||||
public function resolveSlotLocationId(Doctor $doctor, int $slotStart, ?Clinic $clinic = null): ?int
|
||||
public function resolveSlotLocationId(Doctor $doctor, int $slotStart, ?Clinic $clinic = null, bool $forManagement = false): ?int
|
||||
{
|
||||
$date = date('Y-m-d', $slotStart);
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic);
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic, $forManagement);
|
||||
foreach ($sessions as $session) {
|
||||
foreach (($session['slots'] ?? []) as $slot) {
|
||||
if ((int) ($slot['start'] ?? 0) === $slotStart) {
|
||||
@@ -64,9 +67,9 @@ class SlotCalculatorService
|
||||
*
|
||||
* @return array[] [{start_time, end_time, slots: [{start, end, start_time, end_time, location_id, is_available}]}]
|
||||
*/
|
||||
public function getAllSlotsWithAvailability(Doctor $doctor, string $date, ?Clinic $clinic = null): array
|
||||
public function getAllSlotsWithAvailability(Doctor $doctor, string $date, ?Clinic $clinic = null, bool $forManagement = false): array
|
||||
{
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic);
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic, $forManagement);
|
||||
$now = time();
|
||||
return array_map(fn(array $session) => [
|
||||
'start_time' => $session['start_time'],
|
||||
@@ -82,9 +85,9 @@ class SlotCalculatorService
|
||||
* Whether a doctor has at least one slot on the given date.
|
||||
* Lightweight check for the month-availability endpoint.
|
||||
*/
|
||||
public function hasAnyAvailability(Doctor $doctor, string $date, ?Clinic $clinic = null): bool
|
||||
public function hasAnyAvailability(Doctor $doctor, string $date, ?Clinic $clinic = null, bool $forManagement = false): bool
|
||||
{
|
||||
return !empty($this->buildAllSessions($doctor, $date, $clinic));
|
||||
return !empty($this->buildAllSessions($doctor, $date, $clinic, $forManagement));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +102,7 @@ class SlotCalculatorService
|
||||
*
|
||||
* @return array<array{start:int,end:int,start_time:string,end_time:string,location_id:?int}>
|
||||
*/
|
||||
public function getServiceStartTimes(Doctor $doctor, string $date, int $durationMinutes, ?Clinic $clinic = null): array
|
||||
public function getServiceStartTimes(Doctor $doctor, string $date, int $durationMinutes, ?Clinic $clinic = null, bool $forManagement = false): array
|
||||
{
|
||||
if ($durationMinutes <= 0) return [];
|
||||
|
||||
@@ -107,7 +110,7 @@ class SlotCalculatorService
|
||||
$durSec = $durationMinutes * 60;
|
||||
$needSec = $durSec + $buffer * 60; // فضای لازم شامل بافر
|
||||
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic); // window/holiday/override/booking-window رعایت میشود
|
||||
$sessions = $this->buildAllSessions($doctor, $date, $clinic, $forManagement); // window/holiday/override/booking-window رعایت میشود
|
||||
if (empty($sessions)) return [];
|
||||
|
||||
$dayStart = (int) strtotime($date . ' 00:00:00');
|
||||
@@ -148,9 +151,9 @@ class SlotCalculatorService
|
||||
* بدون شیفت و خارجبودن از بازهٔ نوبتدهی چهار چیز متفاوتاند و کاربر باید
|
||||
* بداند کدامیک رخ داده تا بداند چه کاری باید بکند.
|
||||
*/
|
||||
public function explainEmptyDay(Doctor $doctor, string $date, ?Clinic $clinic = null): ?string
|
||||
public function explainEmptyDay(Doctor $doctor, string $date, ?Clinic $clinic = null, bool $forManagement = false): ?string
|
||||
{
|
||||
if ($this->buildAllSessions($doctor, $date, $clinic) !== []) {
|
||||
if ($this->buildAllSessions($doctor, $date, $clinic, $forManagement) !== []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -165,7 +168,7 @@ class SlotCalculatorService
|
||||
return self::EMPTY_HOLIDAY;
|
||||
}
|
||||
|
||||
if (!$this->isWithinBookingWindow($doctor, $dayStart, $clinic)) {
|
||||
if (!$this->isWithinBookingWindow($doctor, $dayStart, $clinic, $forManagement)) {
|
||||
return self::EMPTY_OUTSIDE_WINDOW;
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ class SlotCalculatorService
|
||||
* و نوبتهای اشغال یکبار برای کل بازه واکشی میشوند و بقیه در حافظه محاسبه
|
||||
* میشود: ۴ کوئری ثابت بهجای رشدِ خطی با تعداد روز و اسلات.
|
||||
*/
|
||||
public function findNextAvailableStart(Doctor $doctor, ?Clinic $clinic = null, int $daysAhead = 30): ?int
|
||||
public function findNextAvailableStart(Doctor $doctor, ?Clinic $clinic = null, int $daysAhead = 30, bool $forManagement = false): ?int
|
||||
{
|
||||
$schedule = $this->scheduleRepo->findByDoctorAndClinic($doctor, $clinic);
|
||||
if ($schedule === null) {
|
||||
@@ -187,7 +190,7 @@ class SlotCalculatorService
|
||||
}
|
||||
|
||||
$meta = $schedule->getMeta();
|
||||
if (!($meta['online_booking_enabled'] ?? true)) {
|
||||
if (!$forManagement && !($meta['online_booking_enabled'] ?? true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -285,14 +288,22 @@ class SlotCalculatorService
|
||||
/**
|
||||
* Booking is allowed only when online booking is enabled and the date is
|
||||
* today..(today + window). Past dates are always rejected.
|
||||
*
|
||||
* مدیریت پنل ($forManagement=true): خاموشبودنِ نوبتدهی آنلاین و سقفِ بازهٔ
|
||||
* مجاز رزرو (advance window) فقط قواعد رزرو عمومی از سایتاند و نباید جلوی
|
||||
* نمایش/ثبتِ نوبت توسط پزشک/منشی/ادمین را بگیرند. تاریخِ گذشته همچنان رد میشود.
|
||||
*/
|
||||
private function isWithinBookingWindow(Doctor $doctor, int $dayStart, ?Clinic $clinic): bool
|
||||
private function isWithinBookingWindow(Doctor $doctor, int $dayStart, ?Clinic $clinic, bool $forManagement = false): bool
|
||||
{
|
||||
$todayStart = (int) strtotime('today 00:00:00');
|
||||
if ($dayStart < $todayStart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($forManagement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$meta = $this->getBookingMeta($doctor, $clinic);
|
||||
if (!($meta['online_booking_enabled'] ?? true)) {
|
||||
return false;
|
||||
@@ -316,13 +327,14 @@ class SlotCalculatorService
|
||||
*
|
||||
* @return array[] [{start_time: string, end_time: string, slots: array[]}]
|
||||
*/
|
||||
private function buildAllSessions(Doctor $doctor, string $date, ?Clinic $clinic = null): array
|
||||
private function buildAllSessions(Doctor $doctor, string $date, ?Clinic $clinic = null, bool $forManagement = false): array
|
||||
{
|
||||
$dayStart = (int) strtotime($date . ' 00:00:00');
|
||||
$dayEnd = $dayStart + 86400;
|
||||
|
||||
// 0. Online booking disabled or date outside the booking window
|
||||
if (!$this->isWithinBookingWindow($doctor, $dayStart, $clinic)) {
|
||||
// (در کانتکست مدیریت این دو نادیده گرفته میشوند — رجوع به isWithinBookingWindow)
|
||||
if (!$this->isWithinBookingWindow($doctor, $dayStart, $clinic, $forManagement)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user