Files
clinicpro/.claude/prompt/secretary-welcome-sms.md
hamed a31e8b4314 Add AST JSON files for doctor service, tag controller, and SMS log entities
- Created new AST JSON file for the doctor service API documentation, detailing endpoints, parameters, and responses.
- Added AST JSON file for the TagController, including methods and their relationships with imported classes.
- Introduced AST JSON file for the SmsLog entity, outlining its methods and dependencies.
2026-07-01 14:21:08 +03:30

8.7 KiB

ارسال پیامک خوش‌آمد هنگام تعریف منشی جدید

پروژه

clinicpro (backend — Symfony)

زمینه

هنگام تعریف منشی جدید از طریق POST /api/v1/secretary، یک User با نقش ROLE_SECRETARY ساخته (یا کاربر موجود بازاستفاده) می‌شود، اما هیچ پیامکی به منشی ارسال نمی‌شود. منشی هیچ اطلاعی از شماره‌کاربری، رمز و لینک ورود ندارد.

الگوی مشابه در پروژه از قبل وجود دارد: هنگام تأیید پیش‌ثبت‌نام (PreRegistrationController با تگ TAG_PRE_REGISTRATION) و دعوت پزشک به کلینیک (ClinicInvitationService::sendSms با تگ TAG_CLINIC_INVITATION). این تسک همان الگو را برای منشی پیاده می‌کند.

هدف

بعد از ساخت موفق منشی جدید، یک پیامک خوش‌آمد به شماره منشی ارسال شود. متن پیامک باید از سیستم template پیامک بیاید (تگ اختصاصی + SmsMessageTemplate::DEFAULTS به‌عنوان fallback، قابل ویرایش از DB)، نه hard-code داخل کنترلر.

فایل‌های مرتبط

فایل نقش
src/Secretary/Controller/SecretaryController.php endpoint ساخت منشی — محل تزریق ارسال پیامک
src/Sms/Entity/SmsLog.php ثابت‌های تگ (TAG_*) — افزودن تگ جدید
src/Sms/Entity/SmsMessageTemplate.php آرایه DEFAULTS — افزودن متن پیش‌فرض template
src/Sms/Service/SmsService.php dispatchAsync(...) — ارسال async پیامک
src/Sms/Service/SmsTextResolver.php resolve(tag, vars) — resolve متن template و جایگزینی {key}
src/ClinicInvitation/Service/ClinicInvitationService.php الگوی مرجع ارسال پیامک با template
docs/api/secretary.md + docs/api/sms.md مستندات — باید به‌روز شوند

وضعیت فعلی

در SecretaryController::create() بعد از ساخت user، فقط ذخیره می‌شود و هیچ پیامکی نمی‌رود:

// Assign ROLE_SECRETARY
$roles = $secretaryUser->getRoles();
if (!in_array('ROLE_SECRETARY', $roles, true)) {
    $roles[] = 'ROLE_SECRETARY';
    $secretaryUser->setRoles(array_values(array_unique($roles)));
}
$this->userRepo->save($secretaryUser);

// ... check duplicate ...

$secretary = new DoctorSecretary($doctor, $secretaryUser, $ownerType, $ownerClinic);
// ...
$this->secretaryRepo->save($secretary);

return $this->success(['data' => $secretary->toArray()], 201);

الگوی مرجع ارسال پیامک با template (از ClinicInvitationService):

$message = $this->smsText->resolve(\App\Sms\Entity\SmsLog::TAG_CLINIC_INVITATION, [
    'clinic' => $clinicName,
    'link'   => $link,
]);
$this->smsService->dispatchAsync($inv->getMobile(), $message, tag: \App\Sms\Entity\SmsLog::TAG_CLINIC_INVITATION);

ساختار DEFAULTS در SmsMessageTemplate (هر تگ: title, body با placeholderهای {key}, variables[]):

SmsLog::TAG_PRE_REGISTRATION => [
    'title'     => 'پیش‌ثبت‌نام',
    'body'      => 'به کلینیک پرو خوش آمدید! شماره‌کاربری: {username} | رمز عبور: {password} | لینک ورود: {link}',
    'variables' => ['username', 'password', 'link'],
],

وظایف

۱. افزودن تگ جدید در SmsLog

در src/Sms/Entity/SmsLog.php یک ثابت تگ جدید اضافه کن (هم‌سبک بقیه تگ‌ها):

public const TAG_SECRETARY = 'secretary';

نکته: investigator گزارش داد تگی به نام TAG_WELCOME هم موجود است — قبل از افزودن، فایل را بخوان؛ اگر تگ عمومی خوش‌آمد مناسب بود از همان استفاده کن، در غیر این صورت TAG_SECRETARY را اضافه کن. یک تگ اختصاصی «منشی» بهتر است چون متن template مستقل و قابل ویرایش می‌شود.

۲. افزودن template پیش‌فرض در SmsMessageTemplate::DEFAULTS

یک entry جدید برای تگ منشی اضافه کن. متن باید شامل نام کلینیک/دکتر و اطلاعات ورود باشد:

SmsLog::TAG_SECRETARY => [
    'title'     => 'دعوت به‌عنوان منشی',
    'body'      => "شما به‌عنوان منشی {owner} در کلینیک‌پرو تعریف شدید.\nشماره‌کاربری: {username}\nلینک ورود: {link}",
    'variables' => ['owner', 'username', 'link'],
],
  • {owner} = نام دکتر یا نام کلینیک (بسته به $ownerType).
  • {username} = شماره موبایل منشی ($mobile).
  • {link} = لینک ورود پنل (از سرویس $appUrl مثل بقیه، rtrim + مسیر ورود).
  • اگر رمز عبور در body لازم شد، فقط زمانی که کاربر جدید ساخته شده و password در ورودی آمده — رمز خام را نگه‌دار و در vars بگذار (متغیر محلی، نه از دیتابیس؛ hash قابل بازگشت نیست).

۳. تزریق سرویس‌های SMS در کنترلر

در constructor SecretaryController این‌ها را اضافه کن (هم‌سبک ClinicInvitationService):

private readonly \App\Sms\Service\SmsService       $smsService,
private readonly \App\Sms\Service\SmsTextResolver  $smsText,
private readonly string                            $appUrl,   // همان param که ClinicInvitationService می‌گیرد

$appUrl را از همان جایی bind کن که ClinicInvitationService می‌گیرد — config/services.yaml را بررسی کن و همان $appUrl را برای SecretaryController هم bind کن (یا اگر global bind است، خودکار تزریق می‌شود).

۴. ارسال پیامک بعد از ساخت موفق منشی

در create()، بعد از $this->secretaryRepo->save($secretary); و قبل از return، پیامک را resolve و dispatch کن:

$ownerName = $ownerClinic !== null
    ? ($ownerClinic->getName() ?? 'کلینیک')
    : ($doctor->getUser()->getRealName() ?? 'پزشک');

$link = rtrim($this->appUrl, '/') . '/login';

$message = $this->smsText->resolve(\App\Sms\Entity\SmsLog::TAG_SECRETARY, [
    'owner'    => $ownerName,
    'username' => $mobile,
    'link'     => $link,
]);

$this->smsService->dispatchAsync($mobile, $message, tag: \App\Sms\Entity\SmsLog::TAG_SECRETARY);

نکات مهم

  • ارسال async استdispatchAsync فقط در صف Messenger می‌گذارد؛ ارسال واقعی توسط worker (messenger:consume async) انجام می‌شود. برای تست local باید worker در حال اجرا باشد.
  • متن hard-code نکن — همیشه از SmsTextResolver::resolve(tag, vars) استفاده کن تا ادمین بتواند از DB متن را ویرایش کند. DEFAULTS فقط fallback است.
  • placeholderها دقیقاً {key} باشند و کلیدهای variables[] با کلیدهای آرایه vars که به resolve می‌دهی یکی باشند.
  • duplicate/خطا — اگر منشی تکراری بود (خط ۱۰۱–۱۰۳) یا هر خطای زودهنگام، return قبل از رسیدن به کد ارسال رخ می‌دهد؛ پس پیامک فقط روی مسیر موفق ساخت می‌رود. درست است — همین رفتار مطلوب است.
  • کاربر موجود بازاستفاده‌شده: تصمیم بگیر آیا برای کاربری که از قبل وجود داشت (findByMobile غیر null) هم پیامک برود یا فقط برای کاربر تازه‌ساخته. پیشنهاد: چون این یک «دعوت به نقش منشی» است، برای هر دو حالت (چون رابطه منشی جدید ساخته می‌شود) پیامک منطقی است؛ ولی اگر رمز در body هست، رمز را فقط برای کاربر جدید بگذار.
  • مستندات (Standing Rule): چون src/Secretary/* و src/Sms/* تغییر می‌کنند، در همین session هم docs/api/secretary.md (ذکر ارسال پیامک هنگام create) و هم docs/api/sms.md (تگ جدید + template پیش‌فرض) را به‌روز کن.
  • بعد از تغییر کد: ddev exec php bin/console cache:clear. اگر Entity تغییری در schema نداشت (فقط ثابت/آرایه DEFAULTS)، migration لازم نیست — ولی اگر template را seed می‌کنی، از همان مسیر seed موجود پروژه استفاده کن.