- Add RecordNumberSettingsController for managing patient record number patterns. - Create RecordNumberPattern entity to represent the pattern configuration. - Implement RecordNumberPatternRepository for database interactions. - Develop RecordNumberGenerator service for generating and validating record numbers. - Add tests for record number generation, backfilling, and API interactions. - Ensure proper access control for viewing and updating patterns based on user roles.
140 lines
5.9 KiB
PHP
140 lines
5.9 KiB
PHP
<?php
|
||
|
||
namespace App\Tests\Patient;
|
||
|
||
use App\Auth\Entity\User;
|
||
use App\Auth\Entity\UserActiveContext;
|
||
use App\Clinic\Entity\Clinic;
|
||
use App\Doctor\Entity\Doctor;
|
||
use App\Patient\Entity\RecordNumberPattern;
|
||
use App\Secretary\Entity\DoctorSecretary;
|
||
use App\Tests\ApiTestCase;
|
||
|
||
/**
|
||
* شمارهٔ پرونده هنگام ساختِ پرونده از الگو ساخته میشود — و ورودِ دستی فقط از
|
||
* صاحب مجموعه پذیرفته میشود.
|
||
*/
|
||
class RecordNumberOnCreateTest extends ApiTestCase
|
||
{
|
||
/** @return array{0: User, 1: Clinic, 2: Doctor} */
|
||
private function clinicWithPattern(?string $pattern = 'MD-{SEQ:4}', bool $enabled = true): array
|
||
{
|
||
$owner = $this->createUser(['ROLE_CLINIC']);
|
||
$clinic = new Clinic($owner);
|
||
$this->em->persist($clinic);
|
||
|
||
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر عضو');
|
||
$this->em->persist($doctor);
|
||
$clinic->getDoctors()->add($doctor);
|
||
|
||
$this->em->persist(new UserActiveContext($owner, $clinic->getUuid(), 'clinic'));
|
||
$this->em->flush();
|
||
|
||
if ($pattern !== null) {
|
||
$row = new RecordNumberPattern('clinic', $clinic->getId());
|
||
$row->setPattern($pattern)->setResetPolicy(RecordNumberPattern::RESET_NONE)->setEnabled($enabled);
|
||
$this->em->persist($row);
|
||
$this->em->flush();
|
||
}
|
||
|
||
// فیچرِ `patient_records` از پلنِ «free» میآید که ApiTestCase تضمینش میکند.
|
||
return [$owner, $clinic, $doctor];
|
||
}
|
||
|
||
private function createPatientPayload(string $suffix): array
|
||
{
|
||
return [
|
||
'mobile' => '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT),
|
||
'name' => 'بیمار ' . $suffix,
|
||
'national_code' => (string) random_int(1_000_000_000, 9_999_999_999),
|
||
];
|
||
}
|
||
|
||
/** ✅ موفق: بدون فرستادن شماره، سرور از الگو میسازد و دنباله جلو میرود. */
|
||
public function testCreateGeneratesSequentialNumbers(): void
|
||
{
|
||
[$owner] = $this->clinicWithPattern();
|
||
|
||
$first = $this->authJson('POST', '/api/v1/patient', $owner, $this->createPatientPayload('الف'));
|
||
self::assertSame(201, $this->responseCode());
|
||
self::assertSame('MD-0001', $first['data']['record_number']);
|
||
|
||
$second = $this->authJson('POST', '/api/v1/patient', $owner, $this->createPatientPayload('ب'));
|
||
self::assertSame('MD-0002', $second['data']['record_number']);
|
||
}
|
||
|
||
/** ⚠️ مرزی: بدون الگوی فعال، رفتار امروز حفظ میشود (شمارهٔ دستی، برای همه). */
|
||
public function testWithoutPatternTheManualNumberStillWins(): void
|
||
{
|
||
[$owner] = $this->clinicWithPattern(null);
|
||
|
||
$res = $this->authJson('POST', '/api/v1/patient', $owner, [
|
||
...$this->createPatientPayload('ج'),
|
||
'record_number' => 'کاغذی-77',
|
||
]);
|
||
|
||
self::assertSame(201, $this->responseCode());
|
||
self::assertSame('کاغذی-77', $res['data']['record_number']);
|
||
}
|
||
|
||
/** ✅ موفق: صاحب مجموعه میتواند خارج از دنباله شماره بگذارد. */
|
||
public function testOwnerMayOverrideTheGeneratedNumber(): void
|
||
{
|
||
[$owner] = $this->clinicWithPattern();
|
||
|
||
$res = $this->authJson('POST', '/api/v1/patient', $owner, [
|
||
...$this->createPatientPayload('د'),
|
||
'record_number' => 'ARCHIVE-1',
|
||
]);
|
||
|
||
self::assertSame(201, $this->responseCode());
|
||
self::assertSame('ARCHIVE-1', $res['data']['record_number']);
|
||
}
|
||
|
||
/** ❌ خطا: منشی حق ورود دستی ندارد — نه در ساخت، نه در ویرایش. */
|
||
public function testSecretaryCannotSetTheNumberManually(): void
|
||
{
|
||
[, $clinic, $doctor] = $this->clinicWithPattern();
|
||
|
||
$secretary = $this->createUser(['ROLE_SECRETARY']);
|
||
$rel = new DoctorSecretary($doctor, $secretary, $clinic);
|
||
$rel->mergePermissions(['resources' => ['patients' => ['view' => true, 'create' => true, 'update' => true]]]);
|
||
$this->em->persist($rel);
|
||
$this->em->persist(new UserActiveContext($secretary, $clinic->getUuid(), 'clinic'));
|
||
$this->em->flush();
|
||
|
||
$this->authJson('POST', '/api/v1/patient', $secretary, [
|
||
...$this->createPatientPayload('ه'),
|
||
'record_number' => 'HAND-9',
|
||
]);
|
||
self::assertSame(403, $this->responseCode());
|
||
|
||
// بدون شمارهٔ دستی همان منشی میتواند پرونده بسازد و شماره از الگو میآید.
|
||
$ok = $this->authJson('POST', '/api/v1/patient', $secretary, $this->createPatientPayload('و'));
|
||
self::assertSame(201, $this->responseCode());
|
||
self::assertSame('MD-0001', $ok['data']['record_number']);
|
||
}
|
||
|
||
/** ⚠️ مرزی: پروندهٔ خودکارِ نوبت هم در همان دنباله مینشیند. */
|
||
public function testAutoCreatedRecordFromAppointmentGetsANumber(): void
|
||
{
|
||
[, $clinic, $doctor] = $this->clinicWithPattern();
|
||
|
||
$patient = $this->createUser(['ROLE_USER']);
|
||
$patient->setRealName('بیمار نوبت');
|
||
$this->em->flush();
|
||
|
||
$appointment = new \App\Appointment\Entity\Appointment($doctor, $patient, time() + 3_600, time() + 5_400);
|
||
$appointment->setClinic($clinic);
|
||
$appointment->assignTenant(\App\Shared\Context\EntityContext::forBooking($doctor, $clinic));
|
||
$this->em->persist($appointment);
|
||
$this->em->flush();
|
||
|
||
$service = static::getContainer()->get(\App\Patient\Service\PatientService::class);
|
||
$session = $service->autoCreateOnAppointmentConfirm($appointment);
|
||
|
||
self::assertNotNull($session);
|
||
self::assertSame('MD-0001', $session->getRecord()->getRecordNumber());
|
||
}
|
||
}
|