feat(patients): phase A — records list + create/edit form (Figma)

Rebuild the patient records (پرونده‌ها) area, phase A of the Figma redesign:

- BE: add a clinic-scoped `record_number` and a TenantTag `tags` M2M to
  PatientRecord (migration + EAGER-hydrated collection). POST /patient and
  PATCH /patient/{uuid} now accept `record_number` and tenant-scoped `tags`
  (foreign tag → 422); demographic fields (gender, date_of_birth,
  referral_source, description) continue to live on UserProfile via PATCH.
- FE: new PatientsListPage (table + card views, search, pagination, tags
  column, "تشکیل پرونده") at /admin/patients, and PatientRecordFormPage
  (create/edit) that POSTs the record then PATCHes the demographics. Point
  the sidebar "پرونده" entry to the new list.

Phases B–E (tabbed patient file, service stepper, invoice, payments/wallet,
call-center) follow. Backend covered by PHPUnit, FE by Vitest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-13 14:54:38 +03:30
co-authored by Claude Opus 4.8
parent c46800610f
commit 15c6f5dce7
13 changed files with 671 additions and 4 deletions
+5 -1
View File
@@ -79,11 +79,15 @@ Creates a patient record for a user under the current entity. If the record alre
"user_uuid": "string (اختیاری)",
"mobile": "09xxxxxxxxx (اختیاری — برای جستجو یا ساخت بیمار جدید)",
"name": "string (الزامی فقط هنگام ساخت بیمار جدید)",
"national_code": "string (اختیاری، ۱۰ رقم)"
"national_code": "string (اختیاری، ۱۰ رقم)",
"record_number": "string (اختیاری) — شماره پرونده، مخصوص رکورد",
"tags": ["uuid برچسب‌های TenantTag (اختیاری) — باید متعلق به همین tenant باشند"]
}
```
- اگر `user_uuid` و `mobile` هر دو خالی باشند → خطا.
- `record_number` و `tags` روی خودِ رکورد ذخیره می‌شوند (نه پروفایل کاربر). سایر مشخصات دموگرافیک (`gender`, `date_of_birth`, `referral_source`, `description`, بیمه‌ها) روی `UserProfile` هستند و از طریق `PATCH /patient/{uuid}` ست می‌شوند. پاسخ همیشه `record_number` و `tags: [{uuid,name,color}]` را برمی‌گرداند.
- برچسب متعلق به tenant دیگر → `422 ERR_VALIDATION_001` (`field: tags`).
- `national_code` فقط وقتی روی کاربر ست می‌شود که کاربر کد ملی نداشته باشد.
- موبایل تکراری duplicate نمی‌سازد؛ همان کاربر استفاده می‌شود.
- **یکتایی کد ملی:** اگر `national_code` ارسالی قبلاً به پروفایل کاربر دیگری تعلق داشته باشد → `409` با کد `ERR_PROFILE_001` (`field: national_code`). پیام خطا شامل شماره موبایلِ ماسک‌شده‌ی صاحب کد است (مثلاً «این کد ملی قبلاً با شماره 0912****56 ثبت شده است»). یک کد ملی = یک بیمار در کل سیستم (هم‌راستا با قید یکتای `profiles.national_code`).