fix: return patient national code from profile in patients list
National code is stored on the profiles table, not users, so the patients search returned null user_national_code for patients that actually have one — which blocked selecting an existing patient on the appointment create form. Backfill user_national_code from the profile (batch query) in the list endpoint. On the create page, show a picked patient's stored national code read-only and only prompt for input when the record genuinely lacks one. Add backend tests and update docs/api/patient.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -217,11 +217,20 @@ export default function AppointmentCreatePage() {
|
|||||||
<button type="button" className="btn sm ghost"
|
<button type="button" className="btn sm ghost"
|
||||||
onClick={() => { setPicked(null); setNationalCode(''); }}>تغییر</button>
|
onClick={() => { setPicked(null); setNationalCode(''); }}>تغییر</button>
|
||||||
</div>
|
</div>
|
||||||
<label style={label}>کد ملی</label>
|
{/^\d{10}$/.test((picked.user_national_code ?? '').replace(/\D/g, '')) ? (
|
||||||
<div className="field" style={{ marginTop: 6, height: 44 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 13 }}>
|
||||||
<DigitInput value={nationalCode} onChange={setNationalCode} maxDigits={10}
|
<span style={{ color: 'var(--text-3)' }}>کد ملی</span>
|
||||||
placeholder="کد ملی مراجعه کننده" />
|
<span dir="ltr" style={{ fontWeight: 600 }}>{nationalCode}</span>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<label style={label}>کد ملی (ثبتنشده — وارد کنید)</label>
|
||||||
|
<div className="field" style={{ marginTop: 6, height: 44 }}>
|
||||||
|
<DigitInput value={nationalCode} onChange={setNationalCode} maxDigits={10}
|
||||||
|
placeholder="کد ملی مراجعه کننده" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
+8
-2
@@ -45,9 +45,13 @@ Returns a paginated list of patient records belonging to the authenticated entit
|
|||||||
"uuid": "...",
|
"uuid": "...",
|
||||||
"entity_type": "doctor",
|
"entity_type": "doctor",
|
||||||
"entity_id": 5,
|
"entity_id": 5,
|
||||||
"user": { "uuid": "...", "fullName": "علی رضایی", "phone": "09123456789" },
|
"user_uuid": "...",
|
||||||
|
"user_name": "علی رضایی",
|
||||||
|
"user_mobile": "09123456789",
|
||||||
|
"user_national_code": "0012345675",
|
||||||
|
"record_number": "1024",
|
||||||
|
"tags": [],
|
||||||
"created_by_type": "doctor",
|
"created_by_type": "doctor",
|
||||||
"created_by_id": 5,
|
|
||||||
"created_at": 1718375000
|
"created_at": 1718375000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -59,6 +63,8 @@ Returns a paginated list of patient records belonging to the authenticated entit
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> `user_national_code` منبعِ حقیقتش جدول `profiles` است (نه `users`). اگر روی خودِ کاربر خالی باشد، از پروفایل پر میشود؛ اگر هیچکدام نداشته باشند `null` است.
|
||||||
|
|
||||||
**Errors:**
|
**Errors:**
|
||||||
|
|
||||||
| Code | HTTP | Description |
|
| Code | HTTP | Description |
|
||||||
|
|||||||
@@ -542,12 +542,18 @@ class PatientController extends BaseController
|
|||||||
$records = $this->recordRepo->findByEntity($entityType, $entityId, $page, $limit, $search, $filters);
|
$records = $this->recordRepo->findByEntity($entityType, $entityId, $page, $limit, $search, $filters);
|
||||||
$total = $this->recordRepo->countByEntity($entityType, $entityId, $search, $filters);
|
$total = $this->recordRepo->countByEntity($entityType, $entityId, $search, $filters);
|
||||||
|
|
||||||
return $this->paginated(
|
// کد ملی روی profiles ذخیره میشود نه users؛ اگر روی user خالی بود از پروفایل پر کن.
|
||||||
array_map(fn(PatientRecord $r) => $r->toArray(), $records),
|
$userIds = array_map(fn(PatientRecord $r) => $r->getUser()->getId(), $records);
|
||||||
$total,
|
$profileCodes = $this->profileRepo->nationalCodesByUserIds($userIds);
|
||||||
$page,
|
$rows = array_map(function (PatientRecord $r) use ($profileCodes) {
|
||||||
$limit
|
$row = $r->toArray();
|
||||||
);
|
if (empty($row['user_national_code'])) {
|
||||||
|
$row['user_national_code'] = $profileCodes[$r->getUser()->getId()] ?? null;
|
||||||
|
}
|
||||||
|
return $row;
|
||||||
|
}, $records);
|
||||||
|
|
||||||
|
return $this->paginated($rows, $total, $page, $limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/api/v1/patient', methods: ['POST'])]
|
#[Route('/api/v1/patient', methods: ['POST'])]
|
||||||
|
|||||||
@@ -29,6 +29,31 @@ class UserProfileRepository extends ServiceEntityRepository
|
|||||||
return $this->findOneBy(['nationalCode' => $nationalCode]);
|
return $this->findOneBy(['nationalCode' => $nationalCode]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* نگاشتِ userId → کد ملیِ ذخیرهشده روی پروفایل، فقط برای پروفایلهایی که کد ملی دارند.
|
||||||
|
* برای پر کردنِ گروهیِ کد ملی در لیست بیماران (کد ملی روی profiles است نه users).
|
||||||
|
*
|
||||||
|
* @param int[] $userIds
|
||||||
|
* @return array<int,string>
|
||||||
|
*/
|
||||||
|
public function nationalCodesByUserIds(array $userIds): array
|
||||||
|
{
|
||||||
|
if ($userIds === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$rows = $this->createQueryBuilder('p')
|
||||||
|
->select('IDENTITY(p.user) AS uid', 'p.nationalCode AS nc')
|
||||||
|
->where('p.user IN (:ids)')->setParameter('ids', $userIds)
|
||||||
|
->andWhere('p.nationalCode IS NOT NULL')
|
||||||
|
->getQuery()->getArrayResult();
|
||||||
|
|
||||||
|
$map = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$map[(int) $r['uid']] = (string) $r['nc'];
|
||||||
|
}
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
public function save(UserProfile $profile, bool $flush = true): void
|
public function save(UserProfile $profile, bool $flush = true): void
|
||||||
{
|
{
|
||||||
$this->getEntityManager()->persist($profile);
|
$this->getEntityManager()->persist($profile);
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Patient;
|
||||||
|
|
||||||
|
use App\Doctor\Entity\Doctor;
|
||||||
|
use App\Patient\Entity\PatientRecord;
|
||||||
|
use App\Tests\ApiTestCase;
|
||||||
|
use App\UserProfile\Entity\UserProfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* کد ملی روی جدول profiles ذخیره میشود نه users؛ لیست بیماران باید آن را
|
||||||
|
* در فیلد user_national_code برگرداند تا فرم ثبت نوبت بتواند از بیمار موجود استفاده کند.
|
||||||
|
*/
|
||||||
|
class PatientListNationalCodeTest extends ApiTestCase
|
||||||
|
{
|
||||||
|
private function doctor(): array
|
||||||
|
{
|
||||||
|
$owner = $this->createUser(['ROLE_DOCTOR']);
|
||||||
|
$doctor = new Doctor($owner, 'دکتر تست');
|
||||||
|
$this->em->persist($doctor);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
return [$owner, $doctor];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNationalCodeFromProfileIsReturned(): void
|
||||||
|
{
|
||||||
|
[$owner, $doctor] = $this->doctor();
|
||||||
|
|
||||||
|
$patient = $this->createUser(['ROLE_USER']);
|
||||||
|
// کد ملی فقط روی پروفایل، نه روی خود کاربر
|
||||||
|
$profile = new UserProfile($patient);
|
||||||
|
$profile->setNationalCode('0012345675');
|
||||||
|
$this->em->persist($profile);
|
||||||
|
|
||||||
|
$record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
|
||||||
|
$this->em->persist($record);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$res = $this->authJson('GET', '/api/v1/patients', $owner);
|
||||||
|
self::assertSame(200, $this->responseCode());
|
||||||
|
self::assertSame($record->getUuid(), $res['data'][0]['uuid']);
|
||||||
|
self::assertSame('0012345675', $res['data'][0]['user_national_code']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullWhenNoNationalCodeAnywhere(): void
|
||||||
|
{
|
||||||
|
[$owner, $doctor] = $this->doctor();
|
||||||
|
|
||||||
|
$patient = $this->createUser(['ROLE_USER']);
|
||||||
|
$record = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
|
||||||
|
$this->em->persist($record);
|
||||||
|
$this->em->flush();
|
||||||
|
|
||||||
|
$res = $this->authJson('GET', '/api/v1/patients', $owner);
|
||||||
|
self::assertSame(200, $this->responseCode());
|
||||||
|
self::assertNull($res['data'][0]['user_national_code']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user