feat: create patient record without prior signup

Extend POST /api/v1/patient to resolve user by uuid or mobile, and
create a new ROLE_USER (name + optional national code, no password)
when no user exists. Admin modal shows a new-patient form on lookup miss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-23 15:20:56 +03:30
co-authored by Claude Opus 4.8
parent 59fb590d65
commit 51d7e24e04
4 changed files with 231 additions and 23 deletions
@@ -0,0 +1,82 @@
# تعریف بیمار جدید بدون ثبت‌نام قبلی در سایت
## پروژه
`clinicpro` (backend + admin frontend)
## زمینه
در حال حاضر برای ایجاد پرونده‌ی بیمار، کاربر باید **از قبل در سیستم ثبت‌نام کرده باشد**: مودال «ایجاد پرونده بیمار» با شماره موبایل جستجو می‌کند و اگر `User` پیدا نشد، خطا می‌دهد.
```php
// src/Patient/Controller/PatientController.php — searchUser
$patient = $this->userRepo->findByMobile($mobile);
if ($patient === null) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'کاربری با این شماره یافت نشد', 404);
}
```
و `create` فقط با `user_uuid` کار می‌کند:
```php
$userUuid = trim($data['user_uuid'] ?? '');
$patient = $this->userRepo->findByUuid($userUuid);
```
## مشکل / هدف
کلینیک/مطب باید بتواند بیمار جدیدی تعریف کند که **هرگز در سایت ثبت‌نام نکرده**. یعنی اگر با شماره موبایل کاربری پیدا نشد، به‌جای خطا، امکان ساخت `User` جدید (به‌همراه پروفایل پایه: نام، کد ملی، جنسیت و …) و سپس ساخت `PatientRecord` فراهم شود.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `src/Patient/Controller/PatientController.php` | `searchUser` + `create` |
| `src/Patient/Service/PatientService.php` | منطق ساخت پرونده |
| `src/Auth/Entity/User.php` | کاربر؛ دارای `nationalCode`, `realName`, `mobileNumber` |
| `src/UserProfile/Entity/UserProfile.php` | پروفایل کامل بیمار (family, gender, dateOfBirth, insurance ids و …) |
| `src/Auth/Repository/UserRepository.php` | `findByMobile`, `findByUuid`, `save` |
| `assets/admin/pages/MyPatientsPage.tsx` | مودال «ایجاد پرونده بیمار» (state: `createRecordOpen`, `searchMobile`, `foundUser`) |
| `docs/api/patient.md` | مستندات API بیمار |
## وضعیت فعلی
- مودال فعلی: شماره موبایل می‌گیرد → `GET /api/v1/patient/search-user?mobile=` → اگر یافت شد کارت نتیجه + دکمه‌ی «ایجاد پرونده» (`POST /api/v1/patient` با `user_uuid`).
- اگر یافت نشد: فقط پیغام خطا «کاربری با این شماره یافت نشد»؛ راهی برای ساخت بیمار جدید نیست.
- `User` ساختنی است با `new User($mobileNumber)` و `setRealName`, `setNationalCode`.
- `UserProfile` فیلدهای دموگرافیک کامل دارد (`family`, `gender`, `dateOfBirth`, `basicInsuranceId`, `supplementaryInsuranceId` …).
## وظایف
### ۱. Backend — endpoint ساخت بیمار جدید
دو رویکرد ممکن (یکی را انتخاب و توضیح بده):
- **الف)** گسترش `POST /api/v1/patient`: اگر `user_uuid` نبود ولی `mobile` + `name` (+ فیلدهای اختیاری پروفایل) آمد، `User` جدید بساز (با نقش `ROLE_USER`)، در صورت نیاز `UserProfile` بساز، سپس `PatientRecord`.
- **ب)** endpoint جدا `POST /api/v1/patient/new-user`.
> توصیه: رویکرد الف. در `create`: اول با `user_uuid`؛ اگر نبود، با `mobile` جستجو کن؛ اگر `User` نبود و `name` آمده بود، بساز.
نکات:
- اگر شماره موبایل قبلاً وجود دارد (کاربر ثبت‌نام‌کرده)، همان `User` استفاده شود (نه duplicate).
- validation: موبایل `^09\d{9}$`, کد ملی در صورت ارسال `^\d{10}$`.
- فیلدهای پروفایل اختیاری ذخیره شوند (در `User` یا `UserProfile` طبق مدل واقعی).
### ۲. Admin Frontend — مودال
مودال «ایجاد پرونده بیمار» را گسترش بده:
- بعد از جستجو، اگر کاربر **یافت نشد**، به‌جای خطای صرف، یک فرم «ساخت بیمار جدید» نشان بده (نام، کد ملی، جنسیت، و فیلدهای پایه) با موبایل از پیش پرشده.
- دکمه‌ی «ایجاد پرونده» این بار `User` جدید را هم می‌سازد.
- اگر یافت شد، رفتار فعلی حفظ شود.
### ۳. مستندات
`docs/api/patient.md` را با تغییر `POST /api/v1/patient` (یا endpoint جدید) به‌روز کن: body جدید، حالت‌های یافت‌شد/ساخت جدید، error codeها.
## نکات مهم
- entity owner از `#[CurrentUser]` resolve می‌شود (`PatientController::resolveEntity`).
- duplicate موبایل را مدیریت کن.
- این پرامپت با `patient-record-profile-binding.md` هم‌پوشانی دارد (هر دو روی پروفایل بیمار کار می‌کنند)؛ اگر آن اجرا شده، از همان ساختار `UserProfile` استفاده کن.
- بیمار ساخته‌شده بدون رمز عبور است (بعداً می‌تواند با OTP وارد شود)؛ مطمئن شو ساخت `User` بدون `passwordHash` مجاز است (هست — `nullable: true`).