From 51d7e24e04afa95f09a950faf2993580459daa4c Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Tue, 23 Jun 2026 15:20:56 +0330 Subject: [PATCH] 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 --- .../prompt/create-patient-without-signup.md | 82 +++++++++++++ assets/admin/pages/MyPatientsPage.tsx | 116 ++++++++++++++++-- docs/api/patient.md | 17 ++- src/Patient/Controller/PatientController.php | 39 ++++-- 4 files changed, 231 insertions(+), 23 deletions(-) create mode 100644 .claude/prompt/create-patient-without-signup.md diff --git a/.claude/prompt/create-patient-without-signup.md b/.claude/prompt/create-patient-without-signup.md new file mode 100644 index 00000000..a45cfbb2 --- /dev/null +++ b/.claude/prompt/create-patient-without-signup.md @@ -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`). diff --git a/assets/admin/pages/MyPatientsPage.tsx b/assets/admin/pages/MyPatientsPage.tsx index b4ed5d7f..365e01a3 100644 --- a/assets/admin/pages/MyPatientsPage.tsx +++ b/assets/admin/pages/MyPatientsPage.tsx @@ -142,6 +142,8 @@ function MyPatientsPageInner() { } | null>(null); const [recordNationalCode, setRecordNationalCode] = useState(""); const [searchError, setSearchError] = useState(""); + const [notFound, setNotFound] = useState(false); + const [newPatientName, setNewPatientName] = useState(""); const mobileInputRef = useRef(null); const servicesTotal = selectedServices.reduce( @@ -285,22 +287,28 @@ function MyPatientsPageInner() { ), onSuccess: (res: any) => { setFoundUser(res?.data); + setRecordNationalCode(res?.data?.national_code ?? ""); + setNotFound(false); setSearchError(""); }, onError: () => { setFoundUser(null); - setSearchError("کاربری با این شماره در سیستم یافت نشد"); + setNotFound(true); + setSearchError(""); }, }); const createRecordMut = useMutation({ - mutationFn: (userUuid: string) => - api.post("/api/v1/patient", { user_uuid: userUuid }), + mutationFn: (body: Record) => + api.post("/api/v1/patient", body), onSuccess: () => { qc.invalidateQueries({ queryKey: ["patients"] }); setCreateRecordOpen(false); setSearchMobile(""); setFoundUser(null); + setRecordNationalCode(""); + setNotFound(false); + setNewPatientName(""); setSearchError(""); toast.success("پرونده بیمار ایجاد شد"); }, @@ -315,13 +323,32 @@ function MyPatientsPageInner() { } setSearchError(""); setFoundUser(null); + setNotFound(false); searchUserMut.mutate(digits); }; + const handleCreateRecord = () => { + if (foundUser) { + createRecordMut.mutate({ + user_uuid: foundUser.uuid, + national_code: recordNationalCode || undefined, + }); + } else if (notFound) { + createRecordMut.mutate({ + mobile: searchMobile.replace(/\D/g, ""), + name: newPatientName.trim(), + national_code: recordNationalCode || undefined, + }); + } + }; + const handleCreateRecordClose = () => { setCreateRecordOpen(false); setSearchMobile(""); setFoundUser(null); + setRecordNationalCode(""); + setNotFound(false); + setNewPatientName(""); setSearchError(""); }; @@ -493,12 +520,11 @@ function MyPatientsPageInner() {