- 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.
151 lines
6.5 KiB
PHP
151 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Patient;
|
|
|
|
use App\Doctor\Entity\Doctor;
|
|
use App\Patient\Entity\RecordNumberPattern;
|
|
use App\Patient\Service\RecordNumberGenerator;
|
|
use App\Representation\Service\JalaliDateService;
|
|
use App\Tests\ApiTestCase;
|
|
|
|
/**
|
|
* تولید شمارهٔ پرونده: رندر توکنها، اعتبارسنجی الگو، و شمارندهٔ تراکنشی.
|
|
*/
|
|
class RecordNumberGeneratorTest extends ApiTestCase
|
|
{
|
|
/**
|
|
* سرویسهای دامنه در کانتینرِ تست public نیستند؛ ساختن مستقیم با همان
|
|
* وابستگیهای تزریقی، هم قرارداد را حفظ میکند هم تست را از کانتینر جدا میکند.
|
|
*/
|
|
private function generator(): RecordNumberGenerator
|
|
{
|
|
return new RecordNumberGenerator($this->patternRepo(), $this->em, new JalaliDateService());
|
|
}
|
|
|
|
private function patternRepo(): \App\Patient\Repository\RecordNumberPatternRepository
|
|
{
|
|
return $this->em->getRepository(RecordNumberPattern::class);
|
|
}
|
|
|
|
/** @return array{0:int,1:int} [سال، ماه] شمسی امروز — تست به تاریخ اجرا وابسته نماند */
|
|
private function todayJalali(): array
|
|
{
|
|
$now = new \DateTimeImmutable('now', new \DateTimeZone(JalaliDateService::TIMEZONE));
|
|
|
|
[$jy, $jm] = (new JalaliDateService())->gregorianToJalali(
|
|
(int) $now->format('Y'),
|
|
(int) $now->format('m'),
|
|
(int) $now->format('d'),
|
|
);
|
|
|
|
return [$jy, $jm];
|
|
}
|
|
|
|
private function pattern(string $pattern, string $reset, bool $enabled = true): Doctor
|
|
{
|
|
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست');
|
|
$this->em->persist($doctor);
|
|
$this->em->flush();
|
|
|
|
$row = new RecordNumberPattern('doctor', $doctor->getId());
|
|
$row->setPattern($pattern)->setResetPolicy($reset)->setEnabled($enabled);
|
|
$this->em->persist($row);
|
|
$this->em->flush();
|
|
|
|
return $doctor;
|
|
}
|
|
|
|
public function testRendersEveryToken(): void
|
|
{
|
|
[$jy, $jm] = $this->todayJalali();
|
|
$g = $this->generator();
|
|
|
|
self::assertSame((string) $jy, $g->render('{YYYY}', 1));
|
|
self::assertSame(substr((string) $jy, -2), $g->render('{YY}', 1));
|
|
self::assertSame(str_pad((string) $jm, 2, '0', STR_PAD_LEFT), $g->render('{MM}', 1));
|
|
self::assertSame('7', $g->render('{SEQ}', 7));
|
|
self::assertSame('0007', $g->render('{SEQ:4}', 7));
|
|
self::assertSame('MD-' . substr((string) $jy, -2) . '-0012', $g->render('MD-{YY}-{SEQ:4}', 12));
|
|
}
|
|
|
|
/** ⚠️ مرزی: شمارندهٔ بلندتر از پدینگ نباید بریده شود. */
|
|
public function testCounterOverflowKeepsAllDigits(): void
|
|
{
|
|
self::assertSame('1000', $this->generator()->render('{SEQ:3}', 1000));
|
|
}
|
|
|
|
public function testPreviewDoesNotConsumeTheCounter(): void
|
|
{
|
|
$doctor = $this->pattern('P-{SEQ:3}', RecordNumberPattern::RESET_NONE);
|
|
$g = $this->generator();
|
|
|
|
self::assertSame('P-001', $g->preview('P-{SEQ:3}', 0));
|
|
// شمارنده هنوز صفر است، پس اولین شمارهٔ واقعی همان ۱ میماند.
|
|
self::assertSame('P-001', $g->next('doctor', $doctor->getId()));
|
|
}
|
|
|
|
/** ✅ موفق: شمارهها پشت سر هم و بدون تکرار میآیند. */
|
|
public function testNextIncrementsSequentially(): void
|
|
{
|
|
$doctor = $this->pattern('P-{SEQ:3}', RecordNumberPattern::RESET_NONE);
|
|
$g = $this->generator();
|
|
|
|
self::assertSame('P-001', $g->next('doctor', $doctor->getId()));
|
|
self::assertSame('P-002', $g->next('doctor', $doctor->getId()));
|
|
self::assertSame('P-003', $g->next('doctor', $doctor->getId()));
|
|
}
|
|
|
|
/** ⚠️ مرزی: بدون الگو یا با الگوی خاموش، رفتار امروز حفظ میشود. */
|
|
public function testReturnsNullWhenPatternMissingOrDisabled(): void
|
|
{
|
|
$g = $this->generator();
|
|
|
|
$noPattern = new Doctor($this->createUser(['ROLE_DOCTOR']), 'بدون الگو');
|
|
$this->em->persist($noPattern);
|
|
$this->em->flush();
|
|
self::assertNull($g->next('doctor', $noPattern->getId()));
|
|
|
|
$disabled = $this->pattern('P-{SEQ}', RecordNumberPattern::RESET_NONE, false);
|
|
self::assertNull($g->next('doctor', $disabled->getId()));
|
|
}
|
|
|
|
/** ⚠️ مرزی: ورود به دورهٔ جدید شمارنده را صفر میکند. */
|
|
public function testCounterResetsWhenPeriodChanges(): void
|
|
{
|
|
$doctor = $this->pattern('{YY}-{SEQ:3}', RecordNumberPattern::RESET_YEARLY);
|
|
$g = $this->generator();
|
|
[$jy] = $this->todayJalali();
|
|
$yy = substr((string) $jy, -2);
|
|
|
|
self::assertSame("$yy-001", $g->next('doctor', $doctor->getId()));
|
|
self::assertSame("$yy-002", $g->next('doctor', $doctor->getId()));
|
|
|
|
// شبیهسازی سال قبل: مهرِ دوره را عقب میبریم، شمارنده باید صفر شود.
|
|
$row = $this->patternRepo()->findForPair('doctor', $doctor->getId());
|
|
$row->resetCounter((string) ($jy - 1));
|
|
$row->incrementCounter();
|
|
$row->incrementCounter();
|
|
$this->em->flush();
|
|
|
|
self::assertSame("$yy-001", $g->next('doctor', $doctor->getId()));
|
|
}
|
|
|
|
/** ❌ خطا: الگوهای نامعتبر. */
|
|
public function testValidatePatternRejectsBadInput(): void
|
|
{
|
|
$g = $this->generator();
|
|
|
|
self::assertNotEmpty($g->validatePattern('', RecordNumberPattern::RESET_NONE));
|
|
self::assertNotEmpty($g->validatePattern('MD-{FOO}-{SEQ}', RecordNumberPattern::RESET_NONE));
|
|
self::assertNotEmpty($g->validatePattern('MD-0001', RecordNumberPattern::RESET_NONE));
|
|
self::assertNotEmpty($g->validatePattern(str_repeat('x', 61) . '{SEQ}', RecordNumberPattern::RESET_NONE));
|
|
// {MM} بدون ریست ماهانه و {YY} بدون ریست، ترتیب را بیمعنا میکنند.
|
|
self::assertNotEmpty($g->validatePattern('{MM}-{SEQ}', RecordNumberPattern::RESET_YEARLY));
|
|
self::assertNotEmpty($g->validatePattern('{YY}-{SEQ}', RecordNumberPattern::RESET_NONE));
|
|
|
|
self::assertSame([], $g->validatePattern('MD-{YY}-{SEQ:4}', RecordNumberPattern::RESET_YEARLY));
|
|
self::assertSame([], $g->validatePattern('P-{SEQ:5}', RecordNumberPattern::RESET_NONE));
|
|
self::assertSame([], $g->validatePattern('{YY}{MM}-{SEQ:3}', RecordNumberPattern::RESET_MONTHLY));
|
|
}
|
|
}
|