- 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.
192 lines
7.5 KiB
PHP
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' => 'درخواست رد شد']);
|
|
}
|
|
}
|