Files
clinicpro/src/Auth/Controller/PreRegistrationController.php
T
hamed 969dc9651f Refactor SMS sending to use KavehNegar VerifyLookup templates
- Removed SmsTextResolver dependency from multiple services and controllers.
- Introduced dispatchTemplate method in SmsService to handle SMS sending with templates.
- Updated existing SMS sending logic across various services (OtpService, PreRegistrationController, ClinicInvitationService, PaymentManager, SecretaryController, RepresentationActionController) to utilize the new dispatchTemplate method.
- Enhanced SmsMessageTemplate entity to include kavenegar_template and token_map fields.
- Created migration to add new fields to the sms_message_templates table and populate them with existing data.
- Updated SeedSmsMessageTemplatesCommand to handle new template structure.
- Added documentation for the new SMS template structure and usage.
2026-07-05 11:20:53 +03:30

192 lines
7.5 KiB
PHP

<?php
namespace App\Auth\Controller;
use App\Auth\Entity\PreRegistration;
use App\Auth\Entity\User;
use App\Auth\Repository\PreRegistrationRepository;
use App\Auth\Repository\UserRepository;
use App\Clinic\Entity\Clinic;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Entity\Doctor;
use App\Doctor\Repository\DoctorRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Sms\Service\SmsService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use OpenApi\Attributes as OA;
#[OA\Tag(name: 'Auth')]
class PreRegistrationController extends BaseController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly PreRegistrationRepository $preRegRepo,
private readonly UserRepository $userRepo,
private readonly DoctorRepository $doctorRepo,
private readonly ClinicRepository $clinicRepo,
private readonly UserPasswordHasherInterface $hasher,
private readonly SmsService $sms,
private readonly LoggerInterface $logger,
) {}
#[Route('/api/v1/pre-registration', methods: ['POST'])]
public function submit(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true) ?? [];
$type = trim($data['type'] ?? '');
$name = trim($data['name'] ?? '');
$mobile = trim($data['mobile'] ?? '');
$info = trim($data['info'] ?? '') ?: null;
$validTypes = [
PreRegistration::TYPE_INDEPENDENT_DOCTOR,
PreRegistration::TYPE_DOCTOR_WITH_CLINIC,
PreRegistration::TYPE_CLINIC_MANAGER,
];
if (!in_array($type, $validTypes, true)) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نوع حساب معتبر نیست', 422);
}
if (mb_strlen($mobile) < 10 || mb_strlen($mobile) > 15) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'شماره موبایل معتبر نیست', 422);
}
if (mb_strlen($name) < 2) {
return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام الزامی است', 422);
}
if ($this->preRegRepo->hasPendingForMobile($mobile)) {
return $this->error(ErrorCodes::DUPLICATE_REQUEST, 'درخواست ثبت‌نام شما در حال بررسی است', 409);
}
$preReg = new PreRegistration($type, $name, $mobile, $info);
$this->em->persist($preReg);
$this->em->flush();
return $this->success(['uuid' => $preReg->getUuid(), 'status' => $preReg->getStatus()], 201);
}
#[Route('/api/v1/admin/pre-registrations', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
public function list(Request $request): JsonResponse
{
$page = max(1, (int) $request->query->get('page', 1));
$limit = min(50, max(1, (int) $request->query->get('limit', 20)));
$status = $request->query->get('status', 'pending');
$qb = $this->em->createQueryBuilder()
->select('p.uuid, p.type, p.name, p.mobile, p.info, p.status, p.adminNote AS admin_note, p.createdAt AS created_at')
->from(PreRegistration::class, 'p');
if ($status !== 'all') {
$qb->where('p.status = :status')->setParameter('status', $status);
}
$total = (clone $qb)->select('COUNT(p.id)')->getQuery()->getSingleScalarResult();
$items = $qb
->orderBy('p.createdAt', 'DESC')
->setFirstResult(($page - 1) * $limit)
->setMaxResults($limit)
->getQuery()
->getArrayResult();
return $this->paginated($items, (int) $total, $page, $limit);
}
#[Route('/api/v1/admin/pre-registrations/{uuid}/approve', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')]
public function approve(string $uuid): JsonResponse
{
$preReg = $this->preRegRepo->findOneBy(['uuid' => $uuid]);
if (!$preReg) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'درخواست یافت نشد', 404);
}
if (!$preReg->isPending()) {
return $this->error(ErrorCodes::ERR_CONFLICT_001, 'این درخواست قبلاً پردازش شده است', 409);
}
$password = bin2hex(random_bytes(4));
$user = $this->userRepo->findOneBy(['mobileNumber' => $preReg->getMobile()]);
if (!$user) {
$user = new User($preReg->getMobile());
}
$user->setPasswordHash($this->hasher->hashPassword($user, $password));
$user->setRealName($preReg->getName());
$this->em->persist($user);
$type = $preReg->getType();
$doctor = null;
if ($type === PreRegistration::TYPE_INDEPENDENT_DOCTOR || $type === PreRegistration::TYPE_DOCTOR_WITH_CLINIC) {
$user->addRole('ROLE_DOCTOR');
$doctor = $this->doctorRepo->findOneBy(['user' => $user]);
if (!$doctor) {
$doctor = new Doctor($user, $preReg->getName());
$doctor->setMobileNumber($preReg->getMobile());
$this->em->persist($doctor);
}
}
if ($type === PreRegistration::TYPE_DOCTOR_WITH_CLINIC || $type === PreRegistration::TYPE_CLINIC_MANAGER) {
$user->addRole('ROLE_CLINIC');
if (!$this->clinicRepo->findOneBy(['user' => $user])) {
$clinic = new Clinic($user);
$clinic->setName($preReg->getName());
$clinic->setTelephone($preReg->getMobile());
$clinic->setNotificationMobile($preReg->getMobile());
if ($doctor !== null) {
$clinic->getDoctors()->add($doctor);
}
$this->em->persist($clinic);
}
}
$preReg->approve();
$this->em->flush();
try {
$this->sms->dispatchTemplate(
\App\Sms\Entity\SmsLog::TAG_PRE_REGISTRATION,
$preReg->getMobile(),
[
'username' => $preReg->getMobile(),
'password' => $password,
'link' => 'https://clinic-pro.ddev.site/admin',
],
);
} catch (\Throwable $e) {
$this->logger->warning('PreRegistration SMS failed', ['uuid' => $uuid, 'error' => $e->getMessage()]);
}
return $this->success(['message' => 'تأیید شد و اطلاعات ورود ارسال گردید']);
}
#[Route('/api/v1/admin/pre-registrations/{uuid}/reject', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')]
public function reject(string $uuid, Request $request): JsonResponse
{
$preReg = $this->preRegRepo->findOneBy(['uuid' => $uuid]);
if (!$preReg) {
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'درخواست یافت نشد', 404);
}
if (!$preReg->isPending()) {
return $this->error(ErrorCodes::ERR_CONFLICT_001, 'این درخواست قبلاً پردازش شده است', 409);
}
$data = json_decode($request->getContent(), true) ?? [];
$preReg->reject($data['note'] ?? null);
$this->em->flush();
return $this->success(['message' => 'درخواست رد شد']);
}
}