A doctor working both at their own practice and at a clinic had to write two independent schedules and neither panel could see the other, so the clinic showed an empty form even though the doctor had configured their practice. The schedule is now a single record owned by the doctor. What varies between days is the place: the context of a shift is read from its location_id, not from the record it lives in. Booking in a context therefore sees only that context's days, so a personal-practice secretary still cannot book a clinic day. The caller's own context decides which addresses they may assign: the doctor gets every place of theirs, a clinic manager only its own, and shifts outside their reach are returned for display but preserved verbatim on save. Existing per-clinic rows are merged by migration; location_id was already stored on every shift, so no context information is lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
149 lines
6.3 KiB
PHP
149 lines
6.3 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Appointment;
|
|
|
|
use App\ClinicService\Entity\ServiceItem;
|
|
use App\ClinicService\Entity\ServiceSection;
|
|
use App\Clinic\Entity\Clinic;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Doctor\Entity\DoctorAddress;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* نوبتدهی سرویسی، سرویسهای همان محیط را میخواهد. باگ اصلی این بود که شمارش
|
|
* همیشه با entity_type='doctor' انجام میشد، پس کلینیکی که سرویس bookable داشت هم
|
|
* خطای «حداقل یک سرویس لازم است» میگرفت.
|
|
*/
|
|
class ServiceModeContextTest extends ApiTestCase
|
|
{
|
|
private function makeDoctorInClinic(): array
|
|
{
|
|
$doctorUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
|
$doctor = new Doctor($doctorUser, 'دکتر تست');
|
|
$doctor->setMobileNumber($doctorUser->getMobileNumber());
|
|
$this->em->persist($doctor);
|
|
|
|
$ownerUser = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
|
|
$clinic = new Clinic($ownerUser);
|
|
$clinic->setName('کلینیک تست');
|
|
$clinic->getDoctors()->add($doctor);
|
|
$this->em->persist($clinic);
|
|
$this->em->flush();
|
|
|
|
return [$doctor, $clinic, $ownerUser];
|
|
}
|
|
|
|
private function bookableServiceFor(string $entityType, int $entityId): void
|
|
{
|
|
$section = new ServiceSection($entityType, $entityId, 'بخش تست');
|
|
$this->em->persist($section);
|
|
|
|
$item = new ServiceItem($section, 'ویزیت', 500_000);
|
|
$item->setBookable(true);
|
|
$item->setDurationMinutes(20);
|
|
$this->em->persist($item);
|
|
$this->em->flush();
|
|
}
|
|
|
|
private function servicePayload(Doctor $doctor, int $locationId, ?Clinic $clinic): array
|
|
{
|
|
return array_filter([
|
|
'doctor_uuid' => $doctor->getUuid(),
|
|
'clinic_uuid' => $clinic?->getUuid(),
|
|
'schedule' => [
|
|
['day' => 'saturday', 'sessions' => [
|
|
['active' => true, 'location_id' => $locationId, 'start' => '09:00', 'end' => '12:00'],
|
|
]],
|
|
],
|
|
'meta' => ['booking_mode' => 'service'],
|
|
], fn($v) => $v !== null);
|
|
}
|
|
|
|
public function testClinicServiceModeAcceptsClinicOwnedService(): void
|
|
{
|
|
[$doctor, $clinic, $owner] = $this->makeDoctorInClinic();
|
|
$this->bookableServiceFor('clinic', $clinic->getId());
|
|
|
|
$address = DoctorAddress::forClinic($clinic->getId());
|
|
$this->em->persist($address);
|
|
$this->em->flush();
|
|
|
|
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->servicePayload($doctor, $address->getId(), $clinic));
|
|
|
|
self::assertSame(201, $this->responseCode());
|
|
}
|
|
|
|
public function testPersonalServiceModeIgnoresClinicServices(): void
|
|
{
|
|
[$doctor, $clinic] = $this->makeDoctorInClinic();
|
|
$this->bookableServiceFor('clinic', $clinic->getId());
|
|
|
|
$address = DoctorAddress::forDoctor($doctor);
|
|
$this->em->persist($address);
|
|
$this->em->flush();
|
|
|
|
$body = $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->servicePayload($doctor, $address->getId(), null));
|
|
|
|
self::assertSame(422, $this->responseCode(), 'سرویس کلینیک نباید مطب شخصی را راضی کند');
|
|
self::assertSame('booking_mode', $body['errors'][0]['field'] ?? null);
|
|
}
|
|
|
|
public function testPersonalServiceModeAcceptsOwnService(): void
|
|
{
|
|
[$doctor] = $this->makeDoctorInClinic();
|
|
$this->bookableServiceFor('doctor', $doctor->getId());
|
|
|
|
$address = DoctorAddress::forDoctor($doctor);
|
|
$this->em->persist($address);
|
|
$this->em->flush();
|
|
|
|
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->servicePayload($doctor, $address->getId(), null));
|
|
|
|
self::assertSame(201, $this->responseCode());
|
|
}
|
|
|
|
/**
|
|
* برنامه یکی است، پس نوع نوبتدهی هم یکی است.
|
|
*
|
|
* پیشتر هر محیط رکورد خودش را داشت و میشد مطب را اسلاتی و کلینیک را سرویسی کرد.
|
|
* حالا که یک برنامه بیشتر نیست، تغییر نوع بعد از اولین ثبت رد میشود — همان قفلی
|
|
* که از قبل داخل یک محیط وجود داشت، فقط دامنهاش پزشک شده.
|
|
*/
|
|
public function testBookingModeIsLockedOnceForTheWholeDoctor(): void
|
|
{
|
|
[$doctor, $clinic, $owner] = $this->makeDoctorInClinic();
|
|
$this->bookableServiceFor('clinic', $clinic->getId());
|
|
|
|
$personal = DoctorAddress::forDoctor($doctor);
|
|
$inClinic = DoctorAddress::forClinic($clinic->getId());
|
|
$this->em->persist($personal);
|
|
$this->em->persist($inClinic);
|
|
$this->em->flush();
|
|
|
|
// مطب شخصی: اسلاتی
|
|
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), [
|
|
'doctor_uuid' => $doctor->getUuid(),
|
|
'schedule' => [['day' => 'saturday', 'sessions' => [
|
|
['active' => true, 'location_id' => $personal->getId(), 'start' => '09:00', 'end' => '12:00'],
|
|
]]],
|
|
'meta' => ['booking_mode' => 'slot'],
|
|
]);
|
|
self::assertSame(201, $this->responseCode());
|
|
|
|
// همان پزشک در کلینیک، این بار سرویسی — نوع قفل است و عوض نمیشود.
|
|
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->servicePayload($doctor, $inClinic->getId(), $clinic));
|
|
self::assertSame(422, $this->responseCode());
|
|
|
|
// ولی افزودن شیفت کلینیک با همان نوع اسلاتی مشکلی ندارد.
|
|
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, [
|
|
'doctor_uuid' => $doctor->getUuid(),
|
|
'clinic_uuid' => $clinic->getUuid(),
|
|
'schedule' => [['day' => 'monday', 'sessions' => [
|
|
['active' => true, 'location_id' => $inClinic->getId(), 'start' => '16:00', 'end' => '20:00'],
|
|
]]],
|
|
'meta' => ['booking_mode' => 'slot'],
|
|
]);
|
|
self::assertSame(201, $this->responseCode());
|
|
}
|
|
}
|