feat: Implement resource booking functionality
- Add service timeline builder for appointments to manage available slots. - Create a hook to fetch resource booking services with effective durations. - Develop ResourceBookingSlotController to handle API requests for resource booking slots. - Implement ResourceBookingSlotService to calculate available time slots based on resource occupancy and service durations. - Add tests for resource appointment creation and booking slot functionality to ensure correct behavior and edge cases.
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Appointment;
|
||||
|
||||
use App\Appointment\Availability\Entity\ResourceOccupancy;
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Auth\Entity\User;
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Doctor\Entity\DoctorAddress;
|
||||
use App\Resource\Entity\ClinicResource;
|
||||
use App\Resource\Entity\ResourceServiceOffering;
|
||||
use App\Resource\Entity\ResourceType;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* ثبت نوبت **برای یک منبع** از پنل: همان `POST /api/v1/my/appointment`، با
|
||||
* `resource_uuid`.
|
||||
*
|
||||
* مدت از زنجیرهٔ حلِ همان منبع میآید نه از پیشفرضِ سرویس، پزشک از ناظرِ منبع
|
||||
* استنتاج میشود، و تداخل روی خودِ منبع جدا از اسلاتِ پزشک سنجیده میشود.
|
||||
*/
|
||||
class ResourceAppointmentCreateTest extends ApiTestCase
|
||||
{
|
||||
/** @return array{0: User, 1: Doctor, 2: ClinicResource, 3: DoctorAddress} */
|
||||
private function doctorWithResource(): array
|
||||
{
|
||||
$user = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
||||
$doctor = new Doctor($user, 'دکتر منبع');
|
||||
$doctor->setMobileNumber($user->getMobileNumber());
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$address = DoctorAddress::forDoctor($doctor);
|
||||
$address->setName('مطب شخصی');
|
||||
$this->em->persist($address);
|
||||
$this->em->flush();
|
||||
|
||||
$type = new ResourceType('doctor', (int) $doctor->getId(), 'device', 'دستگاه');
|
||||
$this->em->persist($type);
|
||||
$this->em->flush();
|
||||
|
||||
$resource = new ClinicResource($address, $type, 'لیزر CO2');
|
||||
$resource->setSupervisor($doctor);
|
||||
$this->em->persist($resource);
|
||||
$this->em->flush();
|
||||
|
||||
return [$user, $doctor, $resource, $address];
|
||||
}
|
||||
|
||||
private function service(DoctorAddress $address, string $name, ?int $serviceMinutes): ServiceItem
|
||||
{
|
||||
$section = new ServiceSection($address->tenantEntityType(), $address->tenantEntityId(), 'بخش ' . $name);
|
||||
$this->em->persist($section);
|
||||
|
||||
$item = new ServiceItem($section, $name, 5_000_000);
|
||||
$item->setBookable(true);
|
||||
|
||||
if ($serviceMinutes !== null) {
|
||||
$item->setDurationMinutes($serviceMinutes);
|
||||
}
|
||||
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function offer(ClinicResource $resource, ServiceItem $item, ?int $minutes): void
|
||||
{
|
||||
$offering = new ResourceServiceOffering($resource, $item);
|
||||
$offering->setDurationMinutes($minutes);
|
||||
$this->em->persist($offering);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $extra */
|
||||
private function body(ClinicResource $resource, int $start, array $extra = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'resource_uuid' => $resource->getUuid(),
|
||||
'slot_start' => $start,
|
||||
'slot_end' => $start + 600,
|
||||
'duration_from_services' => true,
|
||||
'patient_mobile' => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
|
||||
'patient_name' => 'بیمار منبع',
|
||||
'patient_national_code' => '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT),
|
||||
], $extra);
|
||||
}
|
||||
|
||||
private function reload(string $uuid): Appointment
|
||||
{
|
||||
$this->em->clear();
|
||||
|
||||
return $this->em->getRepository(Appointment::class)->findOneBy(['uuid' => $uuid]);
|
||||
}
|
||||
|
||||
private function tomorrowAt(int $hour): int
|
||||
{
|
||||
return (int) strtotime('tomorrow midnight') + $hour * 3600;
|
||||
}
|
||||
|
||||
// ── ✅ موفق ──────────────────────────────────────────────────────────────
|
||||
|
||||
public function testTheAppointmentLandsOnTheResourceWithTheResourceDuration(): void
|
||||
{
|
||||
[$user, $doctor, $resource, $address] = $this->doctorWithResource();
|
||||
// پیشفرض سرویس ۳۰ دقیقه است، ولی این دستگاه ۵۰ دقیقه میگیرد.
|
||||
$service = $this->service($address, 'RF فرکشنال', 30);
|
||||
$this->offer($resource, $service, 50);
|
||||
|
||||
$start = $this->tomorrowAt(10);
|
||||
$res = $this->authJson('POST', '/api/v1/my/appointment', $user, $this->body($resource, $start, [
|
||||
'service_item_uuids' => [$service->getUuid()],
|
||||
]));
|
||||
|
||||
self::assertSame(201, $this->responseCode(), json_encode($res, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$appointment = $this->reload($res['data']['uuid']);
|
||||
self::assertSame($resource->getUuid(), $appointment->getResource()?->getUuid());
|
||||
self::assertSame($start + 50 * 60, $appointment->getSlotEnd());
|
||||
// پزشک فرستاده نشد؛ از ناظرِ منبع آمد.
|
||||
self::assertSame($doctor->getUuid(), $appointment->getDoctor()->getUuid());
|
||||
}
|
||||
|
||||
public function testSeveralServicesAddUpAndAPerRequestDurationWins(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->doctorWithResource();
|
||||
$laser = $this->service($address, 'لیزر زیربغل', 20);
|
||||
$cryo = $this->service($address, 'کرایوتراپی', 25);
|
||||
$this->offer($resource, $laser, 20);
|
||||
$this->offer($resource, $cryo, 25);
|
||||
|
||||
$start = $this->tomorrowAt(11);
|
||||
$res = $this->authJson('POST', '/api/v1/my/appointment', $user, $this->body($resource, $start, [
|
||||
'service_item_uuids' => [$laser->getUuid(), $cryo->getUuid()],
|
||||
'service_durations' => [$laser->getUuid() => 40],
|
||||
]));
|
||||
|
||||
self::assertSame(201, $this->responseCode(), json_encode($res, JSON_UNESCAPED_UNICODE));
|
||||
self::assertSame($start + 65 * 60, $this->reload($res['data']['uuid'])->getSlotEnd());
|
||||
}
|
||||
|
||||
// ── ⚠️ مرزی ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** ظرفیت ۲ یعنی نوبت دوم هنوز جا دارد. */
|
||||
public function testASecondBookingFitsWhileTheResourceStillHasCapacity(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->doctorWithResource();
|
||||
$resource->setCapacity(2);
|
||||
$this->em->flush();
|
||||
|
||||
$service = $this->service($address, 'اکسیژنتراپی', 60);
|
||||
$this->offer($resource, $service, 60);
|
||||
|
||||
$start = $this->tomorrowAt(9);
|
||||
$this->em->persist(new ResourceOccupancy($resource, $start, $start + 3600));
|
||||
$this->em->flush();
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/appointment', $user, $this->body($resource, $start, [
|
||||
'service_item_uuids' => [$service->getUuid()],
|
||||
]));
|
||||
|
||||
self::assertSame(201, $this->responseCode());
|
||||
}
|
||||
|
||||
// ── ❌ خطا ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** دستگاهی که موتور منبعمحور گرفته، از پنل دوباره فروخته نمیشود. */
|
||||
public function testATimeAlreadyTakenOnTheResourceIsRefused(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->doctorWithResource();
|
||||
$service = $this->service($address, 'مزوتراپی', 60);
|
||||
$this->offer($resource, $service, 60);
|
||||
|
||||
$start = $this->tomorrowAt(14);
|
||||
$this->em->persist(new ResourceOccupancy($resource, $start, $start + 3600));
|
||||
$this->em->flush();
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/appointment', $user, $this->body($resource, $start, [
|
||||
'service_item_uuids' => [$service->getUuid()],
|
||||
]));
|
||||
|
||||
self::assertSame(409, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAServiceTheResourceDoesNotOfferIsRefused(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->doctorWithResource();
|
||||
$service = $this->service($address, 'ویزیت عمومی', 15); // بدون offering
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/appointment', $user, $this->body($resource, $this->tomorrowAt(12), [
|
||||
'service_item_uuids' => [$service->getUuid()],
|
||||
]));
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAnUnknownResourceIsRefused(): void
|
||||
{
|
||||
[$user, , , $address] = $this->doctorWithResource();
|
||||
$service = $this->service($address, 'لیزر پا', 20);
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/appointment', $user, [
|
||||
'resource_uuid' => 'no-such-resource',
|
||||
'slot_start' => $this->tomorrowAt(13),
|
||||
'slot_end' => $this->tomorrowAt(13) + 1200,
|
||||
'duration_from_services' => true,
|
||||
'service_item_uuids' => [$service->getUuid()],
|
||||
'patient_mobile' => '09121234567',
|
||||
'patient_name' => 'بیمار تست',
|
||||
'patient_national_code' => '0012345678',
|
||||
]);
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
/** منبعِ محیط دیگر روی نوبت این محیط نمینشیند. */
|
||||
public function testAResourceOfAnotherEnvironmentIsRefused(): void
|
||||
{
|
||||
[$user, , , $address] = $this->doctorWithResource();
|
||||
[, , $foreignResource, $foreignAddress] = $this->doctorWithResource();
|
||||
|
||||
$service = $this->service($address, 'لیزر صورت', 30);
|
||||
$foreign = $this->service($foreignAddress, 'لیزر صورت', 30);
|
||||
$this->offer($foreignResource, $foreign, 30);
|
||||
|
||||
$this->authJson('POST', '/api/v1/my/appointment', $user, $this->body($foreignResource, $this->tomorrowAt(15), [
|
||||
'doctor_uuid' => $this->em->getRepository(Doctor::class)->findByUser($user)->getUuid(),
|
||||
'service_item_uuids' => [$service->getUuid()],
|
||||
]));
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Resource;
|
||||
|
||||
use App\Appointment\Availability\Entity\ResourceOccupancy;
|
||||
use App\Appointment\Entity\Appointment;
|
||||
use App\Auth\Entity\User;
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\ClinicService\Entity\ServiceSection;
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Doctor\Entity\DoctorAddress;
|
||||
use App\Resource\Entity\ClinicResource;
|
||||
use App\Resource\Entity\ResourceCalendar;
|
||||
use App\Resource\Entity\ResourceServiceOffering;
|
||||
use App\Shared\Context\EntityContext;
|
||||
|
||||
/**
|
||||
* زمانهای نوبتدهیِ یک منبع — همان چیزی که تایملاین و مودالِ «ثبت نوبت» صفحهٔ
|
||||
* نوبتها مصرف میکنند.
|
||||
*
|
||||
* منبع اسلات ثابت ندارد: بازهٔ کاری از تقویم خودش میآید و زمانها از مدتِ سرویسهای
|
||||
* انتخابشده ساخته میشوند.
|
||||
*/
|
||||
class ResourceBookingSlotTest extends ResourceTestCase
|
||||
{
|
||||
private const SHIFT_FROM = 540; // ۰۹:۰۰
|
||||
private const SHIFT_TO = 1020; // ۱۷:۰۰
|
||||
|
||||
/** @return array{0: User, 1: Doctor, 2: ClinicResource, 3: DoctorAddress} */
|
||||
private function resourceWithShift(): array
|
||||
{
|
||||
[$user, $doctor, $address] = $this->doctorWithAddress();
|
||||
|
||||
$resource = new ClinicResource($address, $this->resourceType($address), 'لیزر CO2');
|
||||
$resource->setSupervisor($doctor);
|
||||
$this->em->persist($resource);
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->persist(new ResourceCalendar(
|
||||
$resource,
|
||||
$this->dayOfWeek($this->midnight()),
|
||||
self::SHIFT_FROM,
|
||||
self::SHIFT_TO,
|
||||
));
|
||||
$this->em->flush();
|
||||
|
||||
return [$user, $doctor, $resource, $address];
|
||||
}
|
||||
|
||||
private function offer(ClinicResource $resource, DoctorAddress $address, string $name, ?int $minutes): ServiceItem
|
||||
{
|
||||
$section = new ServiceSection($address->tenantEntityType(), $address->tenantEntityId(), 'بخش ' . $name);
|
||||
$this->em->persist($section);
|
||||
|
||||
$item = new ServiceItem($section, $name, 5_000_000);
|
||||
$item->setBookable(true);
|
||||
$this->em->persist($item);
|
||||
$this->em->flush();
|
||||
|
||||
$offering = new ResourceServiceOffering($resource, $item);
|
||||
$offering->setDurationMinutes($minutes);
|
||||
$this->em->persist($offering);
|
||||
$this->em->flush();
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/** فردا انتخاب میشود تا «زمانِ گذشته» نتیجه را کوتاه نکند. */
|
||||
private function midnight(): int
|
||||
{
|
||||
return (int) strtotime('tomorrow midnight');
|
||||
}
|
||||
|
||||
private function date(): string
|
||||
{
|
||||
return date('Y-m-d', $this->midnight());
|
||||
}
|
||||
|
||||
/** ۰ = شنبه، همان قرارداد تقویم منبع. */
|
||||
private function dayOfWeek(int $timestamp): int
|
||||
{
|
||||
return ((int) date('w', $timestamp) + 1) % 7;
|
||||
}
|
||||
|
||||
private function bookOnResource(Doctor $doctor, User $user, ClinicResource $resource, int $start, int $end): void
|
||||
{
|
||||
$appointment = new Appointment($doctor, $user, $start, $end);
|
||||
$appointment->setResource($resource);
|
||||
$appointment->assignTenant(EntityContext::forBooking($doctor, null));
|
||||
$this->em->persist($appointment);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
// ── ✅ موفق ──────────────────────────────────────────────────────────────
|
||||
|
||||
public function testTheDayViewReturnsTheResourceShiftAsItsWorkingWindow(): void
|
||||
{
|
||||
[$user, , $resource] = $this->resourceWithShift();
|
||||
|
||||
$body = $this->authJson('GET', "/api/v1/resource/{$resource->getUuid()}/day-slots?date={$this->date()}", $user);
|
||||
|
||||
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
self::assertNull($body['data']['empty_reason']);
|
||||
self::assertCount(1, $body['data']['windows']);
|
||||
self::assertSame($this->midnight() + self::SHIFT_FROM * 60, $body['data']['windows'][0]['start']);
|
||||
self::assertSame($this->midnight() + self::SHIFT_TO * 60, $body['data']['windows'][0]['end']);
|
||||
}
|
||||
|
||||
public function testServiceSlotsAreChainedFromTheResourceDurationOfEachService(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->resourceWithShift();
|
||||
$laser = $this->offer($resource, $address, 'لیزر زیربغل', 25);
|
||||
$rf = $this->offer($resource, $address, 'RF فرکشنال', 35);
|
||||
|
||||
$body = $this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s&service_item_uuids[]=%s',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$laser->getUuid(),
|
||||
$rf->getUuid(),
|
||||
), $user);
|
||||
|
||||
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
||||
self::assertSame(60, $body['data']['total_duration_minutes']);
|
||||
// ۸ ساعت شیفت ÷ ۶۰ دقیقه = ۸ زمان، پشتسرهم از ۰۹:۰۰.
|
||||
self::assertCount(8, $body['data']['start_times']);
|
||||
self::assertSame('09:00', $body['data']['start_times'][0]['start_time']);
|
||||
self::assertSame('10:00', $body['data']['start_times'][1]['start_time']);
|
||||
}
|
||||
|
||||
public function testAnAppointmentOnTheResourceRemovesItsTimeFromTheOffer(): void
|
||||
{
|
||||
[$user, $doctor, $resource, $address] = $this->resourceWithShift();
|
||||
$service = $this->offer($resource, $address, 'لیزر صورت', 60);
|
||||
$occupied = $this->midnight() + 10 * 3600;
|
||||
|
||||
$this->bookOnResource($doctor, $user, $resource, $occupied, $occupied + 3600);
|
||||
|
||||
$body = $this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$service->getUuid(),
|
||||
), $user);
|
||||
|
||||
$starts = array_column($body['data']['start_times'], 'start_time');
|
||||
self::assertNotContains('10:00', $starts);
|
||||
self::assertContains('09:00', $starts);
|
||||
self::assertContains('11:00', $starts);
|
||||
}
|
||||
|
||||
/** مدتِ فرستادهشده فقط همین محاسبه را جابهجا میکند، نه پیشفرضِ سرویس را. */
|
||||
public function testAPerRequestDurationOverridesTheOffering(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->resourceWithShift();
|
||||
$service = $this->offer($resource, $address, 'کرایوتراپی', 25);
|
||||
|
||||
$body = $this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s&durations[%s]=120',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$service->getUuid(),
|
||||
$service->getUuid(),
|
||||
), $user);
|
||||
|
||||
self::assertSame(120, $body['data']['total_duration_minutes']);
|
||||
self::assertCount(4, $body['data']['start_times']); // ۸ ساعت ÷ ۲ ساعت
|
||||
}
|
||||
|
||||
// ── ⚠️ مرزی ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** اتاق دوتخته با یک نوبت پر نمیشود. */
|
||||
public function testCapacityKeepsTheTimeOnOfferUntilItIsFull(): void
|
||||
{
|
||||
[$user, $doctor, $resource, $address] = $this->resourceWithShift();
|
||||
$resource->setCapacity(2);
|
||||
$this->em->flush();
|
||||
|
||||
$service = $this->offer($resource, $address, 'اکسیژنتراپی', 60);
|
||||
$occupied = $this->midnight() + 10 * 3600;
|
||||
$this->bookOnResource($doctor, $user, $resource, $occupied, $occupied + 3600);
|
||||
|
||||
$body = $this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$service->getUuid(),
|
||||
), $user);
|
||||
|
||||
self::assertContains('10:00', array_column($body['data']['start_times'], 'start_time'));
|
||||
}
|
||||
|
||||
/** روزِ بیشیفت خطا نیست؛ باید بگوید چرا خالی است. */
|
||||
public function testADayWithoutAShiftExplainsItself(): void
|
||||
{
|
||||
[$user, , $resource] = $this->resourceWithShift();
|
||||
$otherDay = date('Y-m-d', $this->midnight() + 3 * 86400);
|
||||
|
||||
$body = $this->authJson('GET', "/api/v1/resource/{$resource->getUuid()}/day-slots?date={$otherDay}", $user);
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertSame([], $body['data']['windows']);
|
||||
self::assertNotNull($body['data']['empty_reason']);
|
||||
}
|
||||
|
||||
/** رزرو موقتِ موتور منبعمحور هم وقت را میگیرد، نه فقط نوبتِ ثبتشده. */
|
||||
public function testAHoldFromTheResourceEngineAlsoBlocksTheTime(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->resourceWithShift();
|
||||
$service = $this->offer($resource, $address, 'مزوتراپی', 60);
|
||||
$held = $this->midnight() + 12 * 3600;
|
||||
|
||||
$this->em->persist(new ResourceOccupancy($resource, $held, $held + 3600, ResourceOccupancy::STATUS_HOLD));
|
||||
$this->em->flush();
|
||||
|
||||
$body = $this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$service->getUuid(),
|
||||
), $user);
|
||||
|
||||
self::assertNotContains('12:00', array_column($body['data']['start_times'], 'start_time'));
|
||||
}
|
||||
|
||||
// ── ❌ خطا ───────────────────────────────────────────────────────────────
|
||||
|
||||
public function testAServiceThisResourceDoesNotOfferIsRefused(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->resourceWithShift();
|
||||
|
||||
$section = new ServiceSection($address->tenantEntityType(), $address->tenantEntityId(), 'بخش دیگر');
|
||||
$this->em->persist($section);
|
||||
$foreign = new ServiceItem($section, 'سرویس بیربط', 1_000_000);
|
||||
$foreign->setDurationMinutes(20)->setBookable(true);
|
||||
$this->em->persist($foreign);
|
||||
$this->em->flush();
|
||||
|
||||
$this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$foreign->getUuid(),
|
||||
), $user);
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAnOfferingWithoutADurationIsRefused(): void
|
||||
{
|
||||
[$user, , $resource, $address] = $this->resourceWithShift();
|
||||
$service = $this->offer($resource, $address, 'بدون مدت', null);
|
||||
|
||||
$this->authJson('GET', sprintf(
|
||||
'/api/v1/resource/%s/service-slots?date=%s&service_item_uuids[]=%s',
|
||||
$resource->getUuid(),
|
||||
$this->date(),
|
||||
$service->getUuid(),
|
||||
), $user);
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAMalformedDateIsRefused(): void
|
||||
{
|
||||
[$user, , $resource] = $this->resourceWithShift();
|
||||
|
||||
$this->authJson('GET', "/api/v1/resource/{$resource->getUuid()}/day-slots?date=05-08-2026", $user);
|
||||
|
||||
self::assertSame(422, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAnotherEnvironmentDoesNotSeeThisResource(): void
|
||||
{
|
||||
[, , $resource] = $this->resourceWithShift();
|
||||
[$stranger] = $this->doctorWithAddress();
|
||||
|
||||
$this->authJson('GET', "/api/v1/resource/{$resource->getUuid()}/day-slots?date={$this->date()}", $stranger);
|
||||
|
||||
self::assertSame(404, $this->responseCode());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user