feat: Implement SMS sending functionality with KavehNegar and Rangineh providers

- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.
This commit is contained in:
hamed
2026-06-09 22:00:34 +03:30
commit de1a78a235
222 changed files with 36388 additions and 0 deletions
@@ -0,0 +1,94 @@
<?php
namespace App\Appointment\Repository;
use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Doctor\Entity\Doctor;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\Persistence\ManagerRegistry;
class AppointmentRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Appointment::class);
}
public function findByUuid(string $uuid): ?Appointment
{
return $this->findOneBy(['uuid' => $uuid]);
}
/** Check if a slot is already taken (confirmed or pending) */
public function isSlotTaken(Doctor $doctor, int $slotStart, int $slotEnd, ?int $excludeId = null): bool
{
$qb = $this->createQueryBuilder('a')
->select('COUNT(a.id)')
->where('a.doctor = :doctor')
->andWhere('a.status IN (:activeStatuses)')
->andWhere('a.slotStart < :slotEnd')
->andWhere('a.slotEnd > :slotStart')
->setParameter('doctor', $doctor)
->setParameter('activeStatuses', [Appointment::STATUS_PENDING, Appointment::STATUS_CONFIRMED])
->setParameter('slotStart', $slotStart)
->setParameter('slotEnd', $slotEnd);
if ($excludeId !== null) {
$qb->andWhere('a.id != :excludeId')->setParameter('excludeId', $excludeId);
}
return (int) $qb->getQuery()->getSingleScalarResult() > 0;
}
/** @return Appointment[] */
public function findByDoctor(Doctor $doctor, ?string $status = null): array
{
$criteria = ['doctor' => $doctor];
if ($status !== null) $criteria['status'] = $status;
return $this->findBy($criteria, ['slotStart' => 'ASC']);
}
/** @return Appointment[] */
public function findByUser(User $user, ?string $status = null): array
{
$criteria = ['user' => $user];
if ($status !== null) $criteria['status'] = $status;
return $this->findBy($criteria, ['slotStart' => 'DESC']);
}
/** @return Appointment[] pending appointments older than given timestamp */
public function findExpiredPending(int $before): array
{
return $this->createQueryBuilder('a')
->where('a.status = :status')
->andWhere('a.slotStart < :before')
->setParameter('status', Appointment::STATUS_PENDING)
->setParameter('before', $before)
->getQuery()
->getResult();
}
public function save(Appointment $entity, bool $flush = true): void
{
$this->getEntityManager()->persist($entity);
if ($flush) $this->getEntityManager()->flush();
}
/**
* @throws OptimisticLockException
*/
public function saveWithLock(Appointment $entity, int $expectedVersion): void
{
$this->getEntityManager()->lock($entity, \Doctrine\DBAL\LockMode::OPTIMISTIC, $expectedVersion);
$this->getEntityManager()->persist($entity);
$this->getEntityManager()->flush();
}
public function remove(Appointment $entity, bool $flush = true): void
{
$this->getEntityManager()->remove($entity);
if ($flush) $this->getEntityManager()->flush();
}
}
@@ -0,0 +1,39 @@
<?php
namespace App\Appointment\Repository;
use App\Appointment\Entity\DateOverride;
use App\Doctor\Entity\Doctor;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class DateOverrideRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, DateOverride::class);
}
public function findByUuid(string $uuid): ?DateOverride
{
return $this->findOneBy(['uuid' => $uuid]);
}
/** @return DateOverride[] */
public function findByDoctor(Doctor $doctor): array
{
return $this->findBy(['doctor' => $doctor], ['date' => 'ASC']);
}
public function save(DateOverride $entity, bool $flush = true): void
{
$this->getEntityManager()->persist($entity);
if ($flush) $this->getEntityManager()->flush();
}
public function remove(DateOverride $entity, bool $flush = true): void
{
$this->getEntityManager()->remove($entity);
if ($flush) $this->getEntityManager()->flush();
}
}
@@ -0,0 +1,48 @@
<?php
namespace App\Appointment\Repository;
use App\Appointment\Entity\Holiday;
use App\Doctor\Entity\Doctor;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class HolidayRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Holiday::class);
}
public function findByUuid(string $uuid): ?Holiday
{
return $this->findOneBy(['uuid' => $uuid]);
}
/** @return Holiday[] */
public function findActiveByDoctor(Doctor $doctor, int $from, int $to): array
{
return $this->createQueryBuilder('h')
->where('h.doctor = :doctor')
->andWhere('h.active = true')
->andWhere('h.startDate <= :to')
->andWhere('h.endDate >= :from')
->setParameter('doctor', $doctor)
->setParameter('from', $from)
->setParameter('to', $to)
->getQuery()
->getResult();
}
public function save(Holiday $entity, bool $flush = true): void
{
$this->getEntityManager()->persist($entity);
if ($flush) $this->getEntityManager()->flush();
}
public function remove(Holiday $entity, bool $flush = true): void
{
$this->getEntityManager()->remove($entity);
if ($flush) $this->getEntityManager()->flush();
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Appointment\Repository;
use App\Appointment\Entity\WeeklySchedule;
use App\Doctor\Entity\Doctor;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class WeeklyScheduleRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, WeeklySchedule::class);
}
public function findByDoctor(Doctor $doctor): ?WeeklySchedule
{
return $this->findOneBy(['doctor' => $doctor]);
}
public function findByUuid(string $uuid): ?WeeklySchedule
{
return $this->findOneBy(['uuid' => $uuid]);
}
public function save(WeeklySchedule $entity, bool $flush = true): void
{
$this->getEntityManager()->persist($entity);
if ($flush) $this->getEntityManager()->flush();
}
public function remove(WeeklySchedule $entity, bool $flush = true): void
{
$this->getEntityManager()->remove($entity);
if ($flush) $this->getEntityManager()->flush();
}
}