Files
clinicpro/tests/Appointment/ResourceModeReadinessTest.php
T
hamedandClaude Opus 5 d56c41c87e feat(admin): resource booking mode with a readiness guard
Task 06's engine could only be switched on through the API, and nothing
checked whether the environment was ready for it. Since the mode choice is
irreversible, picking it with no resources defined would lock a clinic into a
state where no appointment is ever computable.

Backend now refuses that: resource mode requires at least one active resource,
with a message that says what to define first. Same shape as the existing
service-mode guard, applied on both save paths.

The panel shows the same conditions as a ✓/✗ list before the choice is made,
each unmet one linking to where it gets fixed — a 422 after an irreversible
decision is the wrong place to learn about a prerequisite.

Also adds the search step (minimum 5 minutes) and extends the existing mode
cards to three rather than building a parallel component.

No strategy picker: task 06 never built the strategies, and an empty menu reads
worse than an absent one.

GET /api/v1/service-items now returns has_segments, computed with one aggregate
query for the whole list rather than one per service.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 19:51:29 +03:30

86 lines
3.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Tests\Appointment;
use App\Doctor\Entity\Doctor;
use App\Doctor\Entity\DoctorAddress;
use App\Resource\Entity\ClinicResource;
use App\Resource\Entity\ResourceType;
use App\Tests\ApiTestCase;
/**
* حالت نوبت‌دهی منبع‌محور بدون منبع فعال، هیچ وقتی نمی‌سازد — و چون انتخابِ حالت
* برگشت‌ناپذیر است، محیط برای همیشه قفل می‌شد. پس شرطش پیش از ثبت سنجیده می‌شود.
*/
class ResourceModeReadinessTest extends ApiTestCase
{
/** @return array{0: \App\Auth\Entity\User, 1: Doctor} */
private function makeDoctor(): array
{
$owner = $this->createUser(['ROLE_DOCTOR']);
$doctor = new Doctor($owner, 'دکتر منبع');
$this->em->persist($doctor);
$this->em->flush();
return [$owner, $doctor];
}
private function giveResource(Doctor $doctor): void
{
$address = DoctorAddress::forDoctor($doctor);
$address->setName('مطب');
$this->em->persist($address);
$this->em->flush();
$type = new ResourceType('doctor', (int) $doctor->getId(), 'room', 'اتاق');
$this->em->persist($type);
$this->em->flush();
$resource = new ClinicResource($address, $type, 'اتاق ۱');
$this->em->persist($resource);
$this->em->flush();
}
public function testResourceModeIsRejectedWithoutAnyResource(): void
{
[$owner, $doctor] = $this->makeDoctor();
$body = $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, [
'doctor_uuid' => $doctor->getUuid(),
'schedule' => [],
'meta' => ['booking_mode' => 'resource'],
]);
self::assertSame(422, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
self::assertStringContainsString('حداقل یک منبع فعال', json_encode($body, JSON_UNESCAPED_UNICODE));
}
public function testResourceModeIsAcceptedOnceAResourceExists(): void
{
[$owner, $doctor] = $this->makeDoctor();
$this->giveResource($doctor);
$body = $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, [
'doctor_uuid' => $doctor->getUuid(),
'schedule' => [],
'meta' => ['booking_mode' => 'resource'],
]);
self::assertSame(201, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
}
/** حالت اسلاتی هیچ شرط منبعی ندارد؛ نگهبان نباید مسیر موجود را بشکند. */
public function testSlotModeStillNeedsNothing(): void
{
[$owner, $doctor] = $this->makeDoctor();
$this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, [
'doctor_uuid' => $doctor->getUuid(),
'schedule' => [],
'meta' => ['booking_mode' => 'slot'],
]);
self::assertSame(201, $this->responseCode());
}
}