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:
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Appointment;
|
||||
|
||||
use App\Appointment\Entity\WeeklySchedule;
|
||||
use App\Appointment\Service\SlotCalculatorService;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* خاموشبودنِ نوبتدهی آنلاین فقط رزرو عمومی از سایت را متوقف میکند. کانتکست مدیریت
|
||||
* (پزشک/منشی/ادمین، $forManagement=true) باید مستقلاً اسلات ببیند و بتواند نوبت ثبت کند.
|
||||
* تاریخِ گذشته اما در هر دو حالت رد میشود.
|
||||
*/
|
||||
class OnlineBookingManagementTest extends ApiTestCase
|
||||
{
|
||||
/** @return array{0: Doctor, 1: string} */
|
||||
private function makeDoctorWithBookingDisabled(): array
|
||||
{
|
||||
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($owner, 'دکتر خاموش');
|
||||
$this->em->persist($doctor);
|
||||
|
||||
$date = date('Y-m-d', strtotime('tomorrow'));
|
||||
$dayKey = (string)(((int) date('w', strtotime($date)) + 1) % 7);
|
||||
|
||||
$schedule = new WeeklySchedule($doctor, [
|
||||
$dayKey => ['sessions' => [[
|
||||
'active' => true,
|
||||
'start_time' => '10:00',
|
||||
'end_time' => '12:00',
|
||||
'duration_per_patient' => 30,
|
||||
'location_id' => 1,
|
||||
]]],
|
||||
]);
|
||||
$schedule->setMeta(['online_booking_enabled' => false]);
|
||||
$this->em->persist($schedule);
|
||||
$this->em->flush();
|
||||
|
||||
return [$doctor, $date];
|
||||
}
|
||||
|
||||
public function testPublicSeesNoSlotsWhenBookingDisabled(): void
|
||||
{
|
||||
[$doctor, $date] = $this->makeDoctorWithBookingDisabled();
|
||||
$calc = static::getContainer()->get(SlotCalculatorService::class);
|
||||
|
||||
$this->assertSame([], $calc->getAllSlotsWithAvailability($doctor, $date));
|
||||
$this->assertSame([], $calc->getAvailableSlots($doctor, $date));
|
||||
$this->assertFalse($calc->hasAnyAvailability($doctor, $date));
|
||||
$this->assertSame(SlotCalculatorService::EMPTY_OUTSIDE_WINDOW, $calc->explainEmptyDay($doctor, $date));
|
||||
}
|
||||
|
||||
public function testManagementSeesSlotsWhenBookingDisabled(): void
|
||||
{
|
||||
[$doctor, $date] = $this->makeDoctorWithBookingDisabled();
|
||||
$calc = static::getContainer()->get(SlotCalculatorService::class);
|
||||
|
||||
$sessions = $calc->getAllSlotsWithAvailability($doctor, $date, null, true);
|
||||
$this->assertNotSame([], $sessions);
|
||||
$this->assertNotSame([], $calc->getAvailableSlots($doctor, $date, null, true));
|
||||
$this->assertTrue($calc->hasAnyAvailability($doctor, $date, null, true));
|
||||
$this->assertNull($calc->explainEmptyDay($doctor, $date, null, true));
|
||||
|
||||
// location هم باید تعیین شود تا نوبتِ دستی بدون آدرس ثبت نشود.
|
||||
$firstStart = (int) $sessions[0]['slots'][0]['start'];
|
||||
$this->assertSame(1, $calc->resolveSlotLocationId($doctor, $firstStart, null, true));
|
||||
$this->assertNull($calc->resolveSlotLocationId($doctor, $firstStart, null, false));
|
||||
}
|
||||
|
||||
public function testPastDateRejectedEvenForManagement(): void
|
||||
{
|
||||
[$doctor] = $this->makeDoctorWithBookingDisabled();
|
||||
$calc = static::getContainer()->get(SlotCalculatorService::class);
|
||||
|
||||
$yesterday = date('Y-m-d', strtotime('yesterday'));
|
||||
$this->assertSame([], $calc->getAllSlotsWithAvailability($doctor, $yesterday, null, true));
|
||||
}
|
||||
|
||||
private function slotsUri(Doctor $doctor, string $date, bool $management): string
|
||||
{
|
||||
return sprintf(
|
||||
'/api/v1/appointment-slots?doctor_uuid=%s&date=%s%s',
|
||||
$doctor->getUuid(),
|
||||
$date,
|
||||
$management ? '&management=1' : '',
|
||||
);
|
||||
}
|
||||
|
||||
public function testEndpointAnonymousGetsNoSlots(): void
|
||||
{
|
||||
[$doctor, $date] = $this->makeDoctorWithBookingDisabled();
|
||||
|
||||
$this->client->request('GET', $this->slotsUri($doctor, $date, false));
|
||||
$body = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertSame([], $body['data']['sessions']);
|
||||
self::assertSame(SlotCalculatorService::EMPTY_OUTSIDE_WINDOW, $body['data']['empty_reason']);
|
||||
}
|
||||
|
||||
public function testEndpointOwnerDoctorWithManagementGetsSlots(): void
|
||||
{
|
||||
[$doctor, $date] = $this->makeDoctorWithBookingDisabled();
|
||||
|
||||
$body = $this->authJson('GET', $this->slotsUri($doctor, $date, true), $doctor->getUser());
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertNotSame([], $body['data']['sessions']);
|
||||
}
|
||||
|
||||
public function testEndpointManagementFlagIgnoredForUnauthorizedUser(): void
|
||||
{
|
||||
[$doctor, $date] = $this->makeDoctorWithBookingDisabled();
|
||||
$stranger = $this->createUser(['ROLE_USER']);
|
||||
|
||||
// management=1 بدون دسترسی نباید توگل را دور بزند (fail-safe عمومی).
|
||||
$body = $this->authJson('GET', $this->slotsUri($doctor, $date, true), $stranger);
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertSame([], $body['data']['sessions']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user