Files
clinicpro/tests/Patient/PatientAppointmentsTest.php
T
hamedandClaude Opus 4.8 1a783971c9 feat: port نوبت‌ها (appointments) tab from tauri to patient detail page
Replace the placeholder row list on the patient detail «نوبت‌ها» tab with the
card-grid design ported pixel-for-pixel from clinic-pro-tauri TurnsSection:

- New AppointmentTurnCard mirrors tauri TurnsCard (success icon, title, date/time
  chips, personnel, status). Status uses the live AppointmentStatusDropdown
  instead of the tauri mock.
- New AppointmentsTab in PatientDetailPage: sort/filter toolbar (client-side) +
  reserve/new buttons linking to existing /admin/appointments pages + card grid.
- Add CalendarD/ClockP/UserD/StatusGlobe icons (verbatim from tauri).
- Backend: expose version on GET /patient/{uuid}/appointments so the status
  dropdown can optimistic-lock. No new endpoint.
- Tests: PatientAppointmentsTest (shape/version/order/empty/ownership) +
  AppointmentTurnCard + tab data/empty cases. docs/api/patient.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:30:12 +03:30

98 lines
3.5 KiB
PHP

<?php
namespace App\Tests\Patient;
use App\Appointment\Entity\Appointment;
use App\Doctor\Entity\Doctor;
use App\Patient\Entity\PatientRecord;
use App\Tests\ApiTestCase;
/**
* List Patient Appointments (تب نوبت‌ها): shape (incl. version), ordering,
* empty boundary, and ownership scoping.
*/
class PatientAppointmentsTest extends ApiTestCase
{
/** @return array{0: \App\Auth\Entity\User, 1: Doctor, 2: PatientRecord} */
private function recordFor(): array
{
$owner = $this->createUser(['ROLE_DOCTOR']);
$doctor = new Doctor($owner, 'دکتر ژیلا فتحی');
$this->em->persist($doctor);
$this->em->flush();
$patient = $this->createUser(['ROLE_USER']);
$record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
$this->em->persist($record);
$this->em->flush();
return [$owner, $doctor, $record];
}
private function appointment(Doctor $doctor, PatientRecord $record, int $slotStart): Appointment
{
$appt = new Appointment($doctor, $record->getUser(), $slotStart, $slotStart + 1800);
$this->em->persist($appt);
$this->em->flush();
return $appt;
}
public function testListReturnsShapeWithVersion(): void
{
[$owner, $doctor, $record] = $this->recordFor();
$this->appointment($doctor, $record, 1_754_000_000);
$list = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $owner);
self::assertSame(200, $this->responseCode());
self::assertCount(1, $list['data']);
$row = $list['data'][0];
foreach (['uuid', 'starts_at', 'ends_at', 'status', 'version', 'doctor_name'] as $key) {
self::assertArrayHasKey($key, $row);
}
self::assertSame(1, $row['version']);
self::assertSame('pending', $row['status']);
self::assertSame('دکتر ژیلا فتحی', $row['doctor_name']);
self::assertSame(1_754_000_000, $row['starts_at']);
}
public function testSortedByStartDescending(): void
{
[$owner, $doctor, $record] = $this->recordFor();
$this->appointment($doctor, $record, 1_754_000_000);
$this->appointment($doctor, $record, 1_755_000_000);
$list = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $owner);
self::assertCount(2, $list['data']);
self::assertSame(1_755_000_000, $list['data'][0]['starts_at']);
self::assertSame(1_754_000_000, $list['data'][1]['starts_at']);
}
public function testEmptyWhenNoAppointments(): void
{
[$owner, , $record] = $this->recordFor();
$list = $this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $owner);
self::assertSame(200, $this->responseCode());
self::assertCount(0, $list['data']);
}
public function testNotFoundForUnknownRecord(): void
{
[$owner] = $this->recordFor();
$this->authJson('GET', '/api/v1/patient/00000000-0000-0000-0000-000000000000/appointments', $owner);
self::assertSame(404, $this->responseCode());
}
public function testOwnershipScoped(): void
{
[, $doctor, $record] = $this->recordFor();
$this->appointment($doctor, $record, 1_754_000_000);
[$other] = $this->recordFor();
$this->authJson('GET', '/api/v1/patient/' . $record->getUuid() . '/appointments', $other);
self::assertSame(404, $this->responseCode());
}
}