GET /api/v1/treatment-case/{uuid}/plan returns every session with a date and an
is_estimate flag, plus each session's area records — the device readings a staff
member actually logged. Until now nothing exposed either: due_at existed only
for the next session, and TreatmentCase::toArray() serialised sessions without
their areas, so 'what was done' was unreachable outside the staff panel.
Kept separate from GET /treatment-case/{uuid}; that response feeds the edit
modal, which needs neither the calendar nor the areas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
241 lines
9.4 KiB
PHP
241 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Treatment;
|
|
|
|
use App\Appointment\Entity\Appointment;
|
|
use App\Clinic\Entity\Clinic;
|
|
use App\ClinicService\Entity\CatalogCategory;
|
|
use App\ClinicService\Entity\ServiceItem;
|
|
use App\ClinicService\Entity\ServiceSection;
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Doctor\Entity\DoctorAddress;
|
|
use App\Patient\Entity\PatientRecord;
|
|
use App\Resource\Entity\ClinicResource;
|
|
use App\Resource\Entity\ResourceType;
|
|
use App\Staff\Entity\ClinicStaff;
|
|
use App\Tests\ApiTestCase;
|
|
use App\Treatment\Entity\TreatmentCase;
|
|
use App\Treatment\Entity\TreatmentCaseArea;
|
|
use App\Treatment\Entity\TreatmentProtocol;
|
|
use App\Treatment\Entity\TreatmentProtocolStaff;
|
|
use App\Treatment\Entity\TreatmentProtocolStep;
|
|
use App\Treatment\Entity\TreatmentSession;
|
|
|
|
class TreatmentCaseEndpointsTest extends ApiTestCase
|
|
{
|
|
/** @return array{user: \App\Auth\Entity\User, clinic: Clinic, case: TreatmentCase, resource: ClinicResource} */
|
|
private function scenario(): array
|
|
{
|
|
$user = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
|
|
$clinic = new Clinic($user);
|
|
$clinic->setName('کلینیک پرونده');
|
|
$this->em->persist($clinic);
|
|
$this->em->flush();
|
|
|
|
$doctor = new Doctor($this->createUser(['ROLE_USER', 'ROLE_DOCTOR']), 'دکتر ناظر');
|
|
$this->em->persist($doctor);
|
|
$clinic->getDoctors()->add($doctor);
|
|
|
|
$address = DoctorAddress::forClinic($clinic->getId());
|
|
$address->setName('شعبهٔ مرکزی');
|
|
$this->em->persist($address);
|
|
|
|
$type = new ResourceType('clinic', (int) $clinic->getId(), 'laser_' . bin2hex(random_bytes(3)), 'لیزر');
|
|
$this->em->persist($type);
|
|
$this->em->flush();
|
|
|
|
$resource = new ClinicResource($address, $type, 'دستگاه لیزر');
|
|
$resource->setSupervisor($doctor);
|
|
$this->em->persist($resource);
|
|
|
|
$section = new ServiceSection('clinic', (int) $clinic->getId(), 'لیزر');
|
|
$this->em->persist($section);
|
|
$category = new CatalogCategory('clinic', (int) $clinic->getId(), 'دست');
|
|
$this->em->persist($category);
|
|
|
|
$service = new ServiceItem($section, 'لیزر دست', 5_000_000);
|
|
$service->setCatalogCategory($category)->setDurationMinutes(30);
|
|
$this->em->persist($service);
|
|
|
|
$staff = new ClinicStaff('clinic', (int) $clinic->getId(), 'اپراتور');
|
|
$this->em->persist($staff);
|
|
|
|
$protocol = new TreatmentProtocol($service);
|
|
$this->em->persist($protocol);
|
|
$protocol->replaceSteps([
|
|
new TreatmentProtocolStep($protocol, 1, 0),
|
|
new TreatmentProtocolStep($protocol, 2, 30),
|
|
]);
|
|
$protocol->replaceAllowedStaff([new TreatmentProtocolStaff($protocol, $staff)]);
|
|
|
|
$record = new PatientRecord('clinic', (int) $clinic->getId(), $this->createUser(), 'clinic', (int) $clinic->getId());
|
|
$this->em->persist($record);
|
|
$this->em->flush();
|
|
|
|
$case = new TreatmentCase('clinic', (int) $clinic->getId(), $record, $service, $protocol);
|
|
$case->addArea(new TreatmentCaseArea($case, $category, 0));
|
|
$first = new TreatmentSession($case, 1);
|
|
$second = new TreatmentSession($case, 2);
|
|
$case->addSession($first);
|
|
$case->addSession($second);
|
|
$this->em->persist($case);
|
|
$this->em->flush();
|
|
|
|
return ['user' => $user, 'clinic' => $clinic, 'case' => $case, 'resource' => $resource, 'doctor' => $doctor];
|
|
}
|
|
|
|
public function testListReturnsCasesOfTheCurrentTenantOnly(): void
|
|
{
|
|
$mine = $this->scenario();
|
|
$other = $this->scenario();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/treatment-cases', $mine['user']);
|
|
|
|
self::assertSame(200, $this->responseCode());
|
|
$uuids = array_column($body['data'], 'uuid');
|
|
self::assertContains($mine['case']->getUuid(), $uuids);
|
|
self::assertNotContains($other['case']->getUuid(), $uuids);
|
|
}
|
|
|
|
public function testShowReturnsSessionsAndAreas(): void
|
|
{
|
|
$s = $this->scenario();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/treatment-case/' . $s['case']->getUuid(), $s['user']);
|
|
|
|
self::assertSame(200, $this->responseCode());
|
|
self::assertSame(2, $body['data']['total_sessions']);
|
|
self::assertSame(0, $body['data']['completed_sessions']);
|
|
self::assertCount(2, $body['data']['sessions']);
|
|
self::assertSame(['دست'], array_column($body['data']['areas'], 'name'));
|
|
}
|
|
|
|
public function testACaseFromAnotherTenantIsNotFound(): void
|
|
{
|
|
$mine = $this->scenario();
|
|
$other = $this->scenario();
|
|
|
|
$this->authJson('GET', '/api/v1/treatment-case/' . $other['case']->getUuid(), $mine['user']);
|
|
|
|
self::assertSame(404, $this->responseCode());
|
|
}
|
|
|
|
/** جلسهای که سررسیدش رسیده و رزرو نشده باید در صف دیده شود. */
|
|
public function testUnbookedQueueListsDueSessions(): void
|
|
{
|
|
$s = $this->scenario();
|
|
$sessions = $s['case']->getSessions()->toArray();
|
|
$sessions[0]->setDueAt(time() - 3600);
|
|
$this->em->flush();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/treatment-sessions/unbooked', $s['user']);
|
|
|
|
self::assertSame(200, $this->responseCode());
|
|
$uuids = array_column($body['data'], 'uuid');
|
|
self::assertContains($sessions[0]->getUuid(), $uuids);
|
|
// جلسهٔ دوم هنوز سررسید ندارد — لنگرش جلسهٔ اولِ انجامنشده است.
|
|
self::assertNotContains($sessions[1]->getUuid(), $uuids);
|
|
}
|
|
|
|
public function testUnbookedQueueSkipsSessionsThatAlreadyHaveAnAppointment(): void
|
|
{
|
|
$s = $this->scenario();
|
|
$sessions = $s['case']->getSessions()->toArray();
|
|
|
|
$appointment = $this->newAppointment(
|
|
$s['doctor'],
|
|
$this->createUser(['ROLE_USER']),
|
|
time() + 3600,
|
|
time() + 5400,
|
|
$s['clinic'],
|
|
);
|
|
$this->em->persist($appointment);
|
|
$this->em->flush();
|
|
|
|
$sessions[0]->setDueAt(time() - 3600);
|
|
$sessions[0]->attachAppointment($appointment);
|
|
$this->em->flush();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/treatment-sessions/unbooked', $s['user']);
|
|
|
|
self::assertNotContains($sessions[0]->getUuid(), array_column($body['data'], 'uuid'));
|
|
}
|
|
|
|
/** بدون منبع پیشفرض و بدون resource_uuid، باید صریح خطا بدهد نه فهرست خالی. */
|
|
public function testSlotSuggestionsWithoutAResourceIsRejected(): void
|
|
{
|
|
$s = $this->scenario();
|
|
$session = $s['case']->getSessions()->first();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/treatment-session/' . $session->getUuid() . '/slot-suggestions', $s['user']);
|
|
|
|
self::assertSame(422, $this->responseCode());
|
|
self::assertSame('resource_uuid', $body['errors'][0]['field']);
|
|
}
|
|
|
|
public function testSlotSuggestionsAcceptAnExplicitResource(): void
|
|
{
|
|
$s = $this->scenario();
|
|
$session = $s['case']->getSessions()->first();
|
|
|
|
$body = $this->authJson(
|
|
'GET',
|
|
'/api/v1/treatment-session/' . $session->getUuid() . '/slot-suggestions?resource_uuid=' . $s['resource']->getUuid(),
|
|
$s['user'],
|
|
);
|
|
|
|
self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));
|
|
self::assertSame($s['resource']->getUuid(), $body['data']['resource_uuid']);
|
|
self::assertArrayHasKey('days', $body['data']);
|
|
self::assertSame($session->getUuid(), $body['data']['session']['uuid']);
|
|
}
|
|
|
|
public function testAResourceFromAnotherTenantIsNotFound(): void
|
|
{
|
|
$mine = $this->scenario();
|
|
$other = $this->scenario();
|
|
$session = $mine['case']->getSessions()->first();
|
|
|
|
$this->authJson(
|
|
'GET',
|
|
'/api/v1/treatment-session/' . $session->getUuid() . '/slot-suggestions?resource_uuid=' . $other['resource']->getUuid(),
|
|
$mine['user'],
|
|
);
|
|
|
|
self::assertSame(404, $this->responseCode());
|
|
}
|
|
|
|
/** تقویم دوره: همهٔ جلسات با تاریخ، و جزئیات نواحیِ جلسهٔ انجامشده. */
|
|
public function testPlanReturnsEverySessionWithADate(): void
|
|
{
|
|
$s = $this->scenario();
|
|
|
|
$body = $this->authJson('GET', '/api/v1/treatment-case/' . $s['case']->getUuid() . '/plan', $s['user']);
|
|
|
|
self::assertSame(200, $this->responseCode());
|
|
self::assertSame(
|
|
$s['case']->getTotalSessions(),
|
|
count($body['data']['sessions']),
|
|
'تقویم باید همهٔ جلسات را بدهد، نه فقط آنهایی که سررسید نوشته دارند',
|
|
);
|
|
|
|
foreach ($body['data']['sessions'] as $row) {
|
|
self::assertArrayHasKey('planned_at', $row);
|
|
self::assertArrayHasKey('is_estimate', $row);
|
|
// نواحی باید بیایند — «چه کاری انجام شد» همین است.
|
|
self::assertArrayHasKey('areas', $row);
|
|
}
|
|
}
|
|
|
|
/** حالت مرزی: پروندهٔ محیط دیگر — تقویم هم مثل بقیه ۴۰۴ میگیرد نه ۴۰۳. */
|
|
public function testPlanOfAnotherTenantIs404(): void
|
|
{
|
|
$mine = $this->scenario();
|
|
$other = $this->scenario();
|
|
|
|
$this->authJson('GET', '/api/v1/treatment-case/' . $other['case']->getUuid() . '/plan', $mine['user']);
|
|
|
|
self::assertSame(404, $this->responseCode());
|
|
}
|
|
}
|