Files
clinicpro/tests/Patient/RecordNumberSettingsApiTest.php
hamed 85a27812c7 feat(patient): implement record number pattern management
- 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.
2026-08-04 12:30:17 +03:30

153 lines
6.1 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\Secretary\Entity\DoctorSecretary;
use App\Tests\ApiTestCase;
/**
* GET/PUT /api/v1/patient-record-number-settings — الگوی شمارهٔ پروندهٔ محیط جاری.
* خواندن برای هر کسی که بیماران را می‌بیند؛ نوشتن فقط برای صاحب محیط.
*/
class RecordNumberSettingsApiTest extends ApiTestCase
{
private const URI = '/api/v1/patient-record-number-settings';
/** @return array{0: User, 1: Clinic, 2: Doctor} مالک کلینیک + کلینیک + پزشکِ عضو */
private function clinic(): 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();
return [$owner, $clinic, $doctor];
}
public function testOwnerReadsDefaultsBeforeAnythingIsSaved(): void
{
[$owner] = $this->clinic();
$res = $this->authJson('GET', self::URI, $owner);
self::assertSame(200, $this->responseCode());
self::assertFalse($res['data']['enabled']);
self::assertSame('none', $res['data']['reset_policy']);
self::assertSame(0, $res['data']['counter']);
self::assertTrue($res['data']['can_edit']);
}
/** ✅ موفق: ذخیرهٔ الگو و پیش‌نمایش شمارهٔ بعدی. */
public function testOwnerSavesPatternAndGetsPreview(): void
{
[$owner] = $this->clinic();
$res = $this->authJson('PUT', self::URI, $owner, [
'enabled' => true,
'pattern' => 'MD-{SEQ:4}',
'reset_policy' => 'none',
]);
self::assertSame(200, $this->responseCode());
self::assertTrue($res['data']['enabled']);
self::assertSame('MD-{SEQ:4}', $res['data']['pattern']);
self::assertSame('MD-0001', $res['data']['next_preview']);
// GET بعدی همان مقدارِ ذخیره‌شده را می‌دهد (نه پیش‌فرض).
$read = $this->authJson('GET', self::URI, $owner);
self::assertSame('MD-{SEQ:4}', $read['data']['pattern']);
}
/** ❌ خطا: منشی نه می‌نویسد، نه `can_edit` می‌گیرد. */
public function testSecretaryCannotWriteButCanRead(): void
{
[, $clinic, $doctor] = $this->clinic();
$secretary = $this->createUser(['ROLE_SECRETARY']);
$this->em->persist(new DoctorSecretary($doctor, $secretary, $clinic));
$this->em->persist(new UserActiveContext($secretary, $clinic->getUuid(), 'clinic'));
$this->em->flush();
$read = $this->authJson('GET', self::URI, $secretary);
self::assertSame(200, $this->responseCode());
self::assertFalse($read['data']['can_edit']);
$this->authJson('PUT', self::URI, $secretary, [
'enabled' => true, 'pattern' => 'X-{SEQ}', 'reset_policy' => 'none',
]);
self::assertSame(403, $this->responseCode());
}
/** ❌ خطا: پزشکِ مهمانِ کلینیک هم صاحب آن محیط نیست. */
public function testGuestDoctorCannotChangeTheClinicPattern(): void
{
[, $clinic, $doctor] = $this->clinic();
$this->em->persist(new UserActiveContext($doctor->getUser(), $clinic->getUuid(), 'clinic'));
$this->em->flush();
$this->authJson('PUT', self::URI, $doctor->getUser(), [
'enabled' => true, 'pattern' => 'X-{SEQ}', 'reset_policy' => 'none',
]);
self::assertSame(403, $this->responseCode());
}
/** ❌ خطا: الگوهای نامعتبر همگی ۴۲۲ با فیلد درست. */
public function testInvalidPatternsAreRejected(): void
{
[$owner] = $this->clinic();
foreach ([
['MD-{FOO}-{SEQ}', 'none'], // توکن ناشناخته
['MD-0001', 'none'], // بدون {SEQ}
['', 'none'], // خالی
['{YY}-{SEQ}', 'none'], // سال بدون ریست
['{MM}-{SEQ}', 'yearly'], // ماه بدون ریست ماهانه
] as [$pattern, $reset]) {
$res = $this->authJson('PUT', self::URI, $owner, [
'enabled' => true, 'pattern' => $pattern, 'reset_policy' => $reset,
]);
self::assertSame(422, $this->responseCode(), "pattern: $pattern");
self::assertSame('pattern', $res['errors'][0]['field'] ?? null);
}
// سیاست ریستِ ناشناخته هم رد می‌شود، ولی فیلدش reset_policy است.
$res = $this->authJson('PUT', self::URI, $owner, [
'enabled' => true, 'pattern' => 'X-{SEQ}', 'reset_policy' => 'weekly',
]);
self::assertSame(422, $this->responseCode());
self::assertSame('reset_policy', $res['errors'][0]['field'] ?? null);
}
/** ⚠️ مرزی: تغییر الگو شمارنده را صفر نمی‌کند — شماره‌های صادرشده وجود دارند. */
public function testChangingThePatternKeepsTheCounter(): void
{
[$owner, $clinic] = $this->clinic();
$this->authJson('PUT', self::URI, $owner, [
'enabled' => true, 'pattern' => 'A-{SEQ:3}', 'reset_policy' => 'none',
]);
$repo = $this->em->getRepository(\App\Patient\Entity\RecordNumberPattern::class);
$row = $repo->findForPair('clinic', $clinic->getId());
$row->incrementCounter();
$row->incrementCounter();
$this->em->flush();
$res = $this->authJson('PUT', self::URI, $owner, [
'enabled' => true, 'pattern' => 'B-{SEQ:3}', 'reset_policy' => 'none',
]);
self::assertSame(2, $res['data']['counter']);
self::assertSame('B-003', $res['data']['next_preview']);
}
}