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()); } }