From 800f9ad8cdb5dd09b1c72baa16c8ac9e17f2c081 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Tue, 16 Jun 2026 10:45:29 +0330 Subject: [PATCH] fix(profile): sync User.real_name from profile name on create/update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user completes their profile (POST/PATCH /api/v1/user-profile), the name was only stored on the profile (label/family) and User.real_name stayed empty — so users who registered via booking showed with no name in the admin user list. hydrate() now mirrors the profile's full name (label + family) onto the owning User.realName, persisted in the same flush. Co-Authored-By: Claude Opus 4.8 --- .../prompt/sync-user-realname-from-profile.md | 75 +++++++++++++++++++ .../Controller/UserProfileController.php | 7 ++ 2 files changed, 82 insertions(+) create mode 100644 .claude/prompt/sync-user-realname-from-profile.md diff --git a/.claude/prompt/sync-user-realname-from-profile.md b/.claude/prompt/sync-user-realname-from-profile.md new file mode 100644 index 00000000..d1daef20 --- /dev/null +++ b/.claude/prompt/sync-user-realname-from-profile.md @@ -0,0 +1,75 @@ +# همگام‌سازی نام کاربر (User.real_name) هنگام تکمیل پروفایل + +## پروژه + +`clinicpro` (Backend). + +> کاملاً backend است. سایت عمومی `nobat724_front` همان `PATCH/POST /api/v1/user-profile` را با فیلدهای `name`/`label` و `family` صدا می‌زند؛ قرارداد API تغییر نمی‌کند، فقط یک side-effect سرور اضافه می‌شود. نیازی به تغییر frontend نیست. + +## زمینه + +در پنل ادمین، لیست کاربران (`/api/v1/admin/users`) ستون «نام» را از `User.realName` (`users.real_name`) می‌خواند. اما وقتی کاربری از طریق نوبت‌دهی ثبت‌نام و پروفایلش را تکمیل می‌کند، نام فقط در `UserProfile` (`profiles.label` و `profiles.family`) ذخیره می‌شود و `users.real_name` خالی می‌ماند. نتیجه: کاربرانِ ثبت‌نام‌شده از مسیر نوبت‌دهی در لیست ادمین **بدون نام** نمایش داده می‌شوند. + +## مشکل / هدف + +هنگام ایجاد/به‌روزرسانی پروفایل، اگر `name`/`label` یا `family` تغییر کند، `User.realName` صاحبِ پروفایل هم با «نام کامل» (ترکیب نام و نام خانوادگی) همگام شود تا در پنل ادمین و هر جای دیگری که `real_name` را می‌خواند درست نمایش داده شود. + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `src/UserProfile/Controller/UserProfileController.php` | `hydrate()` فقط Profile را پر می‌کند؛ باید `User.realName` را هم sync کند | +| `src/UserProfile/Entity/UserProfile.php` | `getUser()`، `getLabel()`، `getFamily()` (مرجع داده) | +| `src/Auth/Entity/User.php` | `setRealName()` (موجود) | + +## وضعیت فعلی (کد واقعی) + +`hydrate()` فقط روی `UserProfile` کار می‌کند — `User.realName` هرگز ست نمی‌شود: +```php +private function hydrate(UserProfile $profile, array $data): void +{ + if (array_key_exists('name', $data)) $profile->setLabel($data['name']); + if (array_key_exists('label', $data)) $profile->setLabel($data['label']); + if (array_key_exists('family', $data)) $profile->setFamily($data['family']); + // ... بقیه فیلدها +} +``` + +و `create()` / `update()` بعد از `hydrate` فقط `$this->repository->save($profile)` را صدا می‌زنند. + +مرجع: لیست ادمین `u.realName as name` را برمی‌گرداند (`AdminApiController::users`)، پس `real_name` خالی = نام خالی در پنل. + +## وظایف + +### ۱. همگام‌سازی `User.realName` در `hydrate` + +در انتهای `hydrate()` (بعد از اعمال همه‌ی فیلدها روی profile)، اگر `name`/`label` یا `family` در ورودی بود، `real_name` کاربر را از مقدار **نهاییِ** پروفایل بساز: + +```php +private function hydrate(UserProfile $profile, array $data): void +{ + // ... ست‌کردن فیلدهای profile (بدون تغییر) ... + + if (array_key_exists('name', $data) || array_key_exists('label', $data) || array_key_exists('family', $data)) { + $fullName = trim(implode(' ', array_filter([$profile->getLabel(), $profile->getFamily()]))); + $profile->getUser()->setRealName($fullName !== '' ? $fullName : null); + } +} +``` + +- منبع، مقدار نهاییِ خودِ `profile` است (نه آرایه‌ی `$data` خام) تا اگر فقط یکی از `name`/`family` در PATCH آمده باشد، نام کامل با مقدار موجودِ دیگری به‌درستی ساخته شود. +- `array_filter` بخش‌های خالی را حذف می‌کند (مثلاً وقتی فقط `name` پر است). اگر نتیجه خالی شد، `null` (هماهنگ با امضای `setRealName(?string)`). + +### ۲. اطمینان از flush شدن تغییر `User` + +`create()` و `update()` بعد از `hydrate` `$this->repository->save($profile)` را صدا می‌زنند که `flush=true` است. چون `User` و `UserProfile` در همان Unit of Work مدیریت می‌شوند، تغییر `realName` در همان flush ذخیره می‌شود — نیازی به save جداگانه‌ی User نیست. فقط **تأیید کن** که `UserProfileRepository::save` واقعاً flush می‌کند (می‌کند) و اگر جایی `flush=false` پاس داده شده، اصلاح کن. + +## نکات مهم + +- **قرارداد API تغییر نمی‌کند** — همان فیلدهای `name`/`label`/`family` ورودی؛ فقط side-effect روی `User.realName`. +- این منطق هم در ایجاد (`POST`) و هم به‌روزرسانی (`PATCH`) باید اجرا شود — چون هر دو از همان `hydrate` استفاده می‌کنند، یک‌جا اصلاح کافی است. +- نام کامل = `label + ' ' + family`؛ اگر محصول ترجیح دیگری دارد (مثلاً فقط `label`)، در پیاده‌سازی همان را اعمال کن — ولی چون لیست ادمین یک ستون واحد «نام» دارد، نام کامل طبیعی‌تر است. +- این فقط نام را روی **صاحبِ پروفایل** ست می‌کند؛ پروفایلِ «شخص دیگر» (که بیمارِ غیرِخود است) از مسیر دیگری (`patient_name` در نوبت) مدیریت می‌شود و نباید `real_name` کاربر لاگین را تغییر دهد — `hydrate` همیشه روی پروفایلِ همان کاربر کار می‌کند، پس مشکلی نیست. +- تاریخ‌ها/الگوها طبق `BaseController`. بعد از تغییر: `ddev exec php -l` روی کنترلر. migration لازم نیست (فیلد `real_name` از قبل وجود دارد). +- تست رفتاری: یک کاربر بدون `real_name` بساز/پیدا کن → `PATCH /api/v1/user-profile/{userUuid}` با `{name, family}` → تأیید کن `users.real_name` پر شد و `GET /api/v1/admin/users` آن را در `name` نشان می‌دهد. همچنین PATCH فقط با `family` → نام کامل با `label` موجود ساخته شود. +- مستندسازی: اگر رفتار قرارداد را تغییر نمی‌دهد، `docs/api/` نیاز به تغییر بزرگ ندارد؛ ولی می‌توان در سند پروفایل (اگر وجود دارد) یک جمله درباره‌ی sync نام افزود. diff --git a/src/UserProfile/Controller/UserProfileController.php b/src/UserProfile/Controller/UserProfileController.php index 63dc3bc0..93753a8a 100644 --- a/src/UserProfile/Controller/UserProfileController.php +++ b/src/UserProfile/Controller/UserProfileController.php @@ -182,5 +182,12 @@ class UserProfileController extends BaseController : $data['other']; $profile->setOther($other); } + + // Keep the owning user's display name (real_name) in sync with the profile, + // so admin user listings and anywhere reading real_name show a name. + if (array_key_exists('name', $data) || array_key_exists('label', $data) || array_key_exists('family', $data)) { + $fullName = trim(implode(' ', array_filter([$profile->getLabel(), $profile->getFamily()]))); + $profile->getUser()->setRealName($fullName !== '' ? $fullName : null); + } } }