<?php
namespace App\Controller;
use App\Entity\Client;
use App\Entity\Client\BillingInfos;
use App\Entity\Product\Famille;
use App\Entity\RDV;
use App\Entity\RDVProduct;
use App\Entity\Salon;
use App\Entity\Site;
use App\Form\ClientType;
use App\Form\RDVType;
use App\Repository\ClientRepository;
use App\Repository\CompanyRepository;
use App\Repository\FamilleRepository;
use App\Repository\ProductRepository;
use App\Repository\ProductServiceRepository;
use App\Repository\RDVProductRepository;
use App\Repository\RDVRepository;
use App\Repository\SalonRepository;
use App\Repository\SessionSalonRepository;
use App\Repository\SousFamilleRepository;
use App\Service\CreateUserService;
use App\Service\MailService;
use App\Service\PaytweakService;
use App\Traits\RDVManagerTrait;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
/**
* @Route("/reservation")
*/
class ReservationController extends AbstractController
{
use RDVManagerTrait;
/**
* @Route("/", name="rdv_index")
*/
public function index(SalonRepository $salonRepository): Response
{
return $this->render('reservation/index.html.twig', [
'types' => Site::getSiteTypes(),
'services' => Salon::getServices(),
'salons' => $salonRepository->findSalonsByTypeAndService(Site::TYPE_SSR, Salon::SERVICE_COIFFURE),
]);
}
/**
* @Route("/{salon}", name="rdv_category_selection", methods={"GET"})
*/
public function categorySelection(Salon $salon, FamilleRepository $familleRepository): Response
{
$familles = $familleRepository->findBy(['isReservation' => true]);
$typeSalon = $salon->getService();
foreach ($familles as $key => $famille) {
if (preg_match('/esthétique|esthetique/i', $famille->getName()) && $typeSalon === 'coiffure') {
unset($familles[$key]);
}
if (preg_match('/coiffure/i', $famille->getName()) && $typeSalon === 'esthetique') {
unset($familles[$key]);
}
}
return $this->render('reservation/category_selection.html.twig', [
'salon' => $salon,
'familles' => $familles
]);
}
/**
* @Route("/{salon}/{famille}", name="rdv_product_selection")
*/
public function productSelection(
Salon $salon,
Famille $famille,
ProductRepository $productRepository,
ProductServiceRepository $productServiceRepository,
SousFamilleRepository $sousFamilleRepository,
Request $request,
ManagerRegistry $doctrine,
PaytweakService $paytweakService,
ClientRepository $clientRepository,
SessionSalonRepository $sessionSalonRepository,
RDVRepository $rdvRepository
): Response {
$typeSite = $salon->getSite()->getType();
$products = $productRepository->getProductsByFamily($famille);
$productIds = array_map(function ($e) {
return $e->getId();
}, $products);
$sousFamilleIds = array_map(function ($e) {
return $e->getSousFamille()->getId();
}, $products);
$service = $this->serviceName(strtolower($famille->getName()));
$productServices = $productServiceRepository->findByProductIds($productIds);
$rdv = new RDV();
$form = $this->createForm(RDVType::class, $rdv, [
'view' => 'client',
'famille' => $famille,
'productIds' => $productIds
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var array $params */
$params = $request->request->get('rdv');
$session = $request->getSession();
$session->clear();
$session->set('recurrence', $params['recurrence']);
$session->set('civilite', $params['civilite']);
$session->set('lastname', $params['lastname']);
$session->set('firstname', $params['firstname']);
$session->set('email', $params['email']);
$session->set('hasAddService', $params['hasAddService']);
$session->set('comment', $params['comment']);
$session->set('isSession', true);
foreach ($params['products'] as $productId) {
$product = $productRepository->findOneById((int)$productId);
if ($product->getSousFamille()->isOneChoice()) { // radio
$session->set('sousfamille_' . $product->getSousFamille()->getId(), (int) $productId);
} else { //checkbox
$session->set('product_' . $productId, (int) $productId);
}
}
if (empty($params['start'])) {
$this->addFlash('danger', 'Une erreur s\'est produite lors que vous êtes retourné en arrière, veuillez réessayer');
return $this->redirectToRoute('rdv_product_selection', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
]);
}
// check if date available
$isDateAvailable = $this->checkDateAvailability($params['start'], $rdvRepository, $salon);
if ($isDateAvailable === false) {
$this->addFlash('danger', 'La date n\'est pas disponible, veuillez réessayer');
return $this->redirectToRoute('rdv_product_selection', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
]);
}
$clientExist = null;
$orderIdPaytweak = null;
$paymentLink = false;
$em = $doctrine->getManager();
// save the product for the rdv
if (!empty($form->getExtraData())) {
$products = $form->getExtraData()['products'];
$rdvProducts = [];
foreach ($products as $productId) {
$product = $productRepository->findOneById((int)$productId);
$rdvProduct = new RDVProduct();
$rdvProduct->setProduct($product);
$rdvProduct->setRdv($rdv);
$rdvProducts[] = $rdvProduct;
}
$clientExist = $clientRepository->findOneBy([
'lastname' => $params['lastname'],
'firstname' => $params['firstname'],
'civilite' => $params['civilite'],
'site' => $salon->getSite(),
]);
// check also
if($clientExist && ($clientExist->getStatusProspect() == Client::STATUS_INACTIF || $clientExist->getStatusProspect() == Client::STATUS_PARTI )){
$clientExist = null;
}
if($clientExist){
/** @var RDV $lastRDV */
$lastRDV = $this->RDVManager->getRepository()->findOneByClientAndToken($clientExist);
if($lastRDV){
$paymentLink = $lastRDV->getPaymentLink();
$path = parse_url($paymentLink, PHP_URL_PATH);
$orderIdPaytweak = basename($path);
}
$rdv->setClient($clientExist);
}
/** @var SessionSalon $sessionSalon */
$date = new \DateTime($params['start']);
$sessionSalon = $sessionSalonRepository->findSessionBySalonAndDate($salon, $date);
$end = clone $date;
$sessionLength = $typeSite === Site::TYPE_EHPAD ? '+15 minutes' : '+30 minutes';
$end->modify($sessionLength);
$rdv->setStart($date);
$rdv->setEnd($end);
$rdv->setIntervenant($sessionSalon[0]->getIntervenant());
$rdv->setRdvProducts($rdvProducts);
$rdv->setStatus(RDV::STATUS_NON_PAYE);
$rdv->setSalon($salon);
$rdv->setIsPlatform(true);
$em->persist($rdv);
$em->flush();
}
else {
$this->addFlash('danger', 'Aucun forfait selectionné');
return $this->redirectToRoute('rdv_product_selection', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
]);
}
// api call for Paytweak
if($clientExist && $paymentLink){
$rdv->setStatus(RDV::STATUS_PLANIFIER);
$em->persist($rdv);
$em->flush();
}
else {
$response = $paytweakService->createLink($orderIdPaytweak);
if ($response['code'] !== 'OK') {
// delete rdv if api KO
$em->remove($rdv);
$em->flush();
$this->addFlash('danger', 'Une erreur s\'est produite, veuillez réessayer');
return $this->redirectToRoute('rdv_product_selection', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
]);
}
else { // for payment
$rdv->setOrderId($response['order_id']);
$rdv->setPaymentLink($response['url']);
$em->persist($rdv);
$em->flush();
}
}
// redirect to payment if client is fully authenticated
if ($clientExist) {
return $this->redirectToRoute('rdv_summary', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
'rdv' => $rdv,
'alreadyPaid' => !!$paymentLink
]);
} else {
$this->addFlash('info', 'Veuillez remplir votre fiche client avant de procéder au paiement');
$session = $request->getSession();
$session->set('civilite', $params['civilite']);
$session->set('lastname', $params['lastname']);
$session->set('firstname', $params['firstname']);
$session->set('email', $params['email']);
return $this->redirectToRoute('new_client', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
'rdv' => $rdv->getId(),
]);
}
}
$datesCalendar = $this->prepareDatesCalendar($sessionSalonRepository, $rdvRepository, $service, $salon); // available dates for calendar picker
$halfHour = in_array($typeSite, [Site::TYPE_SSR, Site::TYPE_RESIDENCE]); // 15 or 30 min
return $this->render('reservation/product_selection.html.twig', [
'salon' => $salon->getId(),
'products' => $products,
'famille' => $famille,
'sousFamilles' => $sousFamilleRepository->findByFamille($famille),
'productServices' => $productServices,
'form' => $form->createView(),
'sousFamilleIds' => $sousFamilleIds,
'service' => $service,
'minuteIncrement' => $halfHour ? 30 : 15,
'maxTime' => $halfHour ? '17:30' : '17:45',
'availableDateTime' => $datesCalendar['availableDateTime'],
'datesToDisable' => $datesCalendar['datesToDisable']
]);
}
/**
* @Route("/{salon}/{famille}/{rdv}/client/new", name="new_client")
*/
public function newClient(
Salon $salon,
Famille $famille,
RDV $rdv,
Request $request,
ManagerRegistry $doctrine,
CreateUserService $createUserService,
ClientRepository $clientRepository,
CompanyRepository $companyRepository
): Response {
$client = new Client();
$form = $this->createForm(ClientType::class, $client, ['isReservation' => true]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($rdv->getClient() !== null) { // avoid creating multiple instances
return $this->redirectToRoute('rdv_summary', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
'rdv' => $rdv,
]);
}
$em = $doctrine->getManager();
// create new User
$createUserService->checkAndAddUser(
$client,
$doctrine
);
// check if client is already created
$clientExists = $clientRepository->findOneBy([
'civilite' => $client->getCivilite(),
'lastname' => $client->getLastname(),
'firstname' => $client->getFirstname(),
'site' => $salon->getSite(),
]);
// create new Client if don't exist
if ($clientExists === null || $clientExists->getStatusProspect() === Client::STATUS_PARTI || $clientExists->getStatusProspect() === Client::STATUS_INACTIF ) {
// new billing infos
$billingInfos = new BillingInfos();
$company = $companyRepository->findOneBy(['mail' => 'contact@silverbeaute.com']);
$billingInfos->setCompany($company);
$billingInfos->setBillingMode(BillingInfos::BILLING_MODE_TTC);
$billingInfos->setAccount('411' . $client->getLastname());
$billingInfos->setEmail($client->getEmail());
$billingInfos->setSendMode(BillingInfos::SEND_MODE_EMAIL);
$em->persist($billingInfos);
$em->flush();
$client->setBillingInfos($billingInfos);
$client->setSite($salon->getSite());
$em->persist($client);
$em->flush();
$client->setIdentifier($client->getId() . ' ' . $client->getLastname());
$em->persist($client);
$em->flush();
$rdv->setClient($client); // link client with rdv
} else {
$rdv->setClient($clientExists);
}
$em->persist($rdv);
$em->flush();
// redirect to summary page after new client is created
return $this->redirectToRoute('rdv_summary', [
'salon' => $salon->getId(),
'famille' => $famille->getId(),
'rdv' => $rdv,
]);
}
return $this->render('reservation/new_client.html.twig', [
'form' => $form->createView(),
]);
}
/**
* @Route("/{salon}/{famille}/{rdv}/summary/{alreadyPaid}", name="rdv_summary")
*/
public function summary(
Salon $salon,
Famille $famille,
RDV $rdv,
RDVProductRepository $RdvProductRepository,
Request $request,
$alreadyPaid = null
): Response {
$total = 0;
$products = [];
$rdvProducts = $RdvProductRepository->findBy(['rdv' => $rdv]);
foreach ($rdvProducts as $rdvProduct) {
$product = $rdvProduct->getProduct();
$products[] = $product;
$total += $product->getPriceSellTTCA();
}
$session = $request->getSession();
return $this->render('reservation/summary.html.twig', [
'salon' => $salon,
'famille' => $famille,
'products' => $products,
'total' => $total,
'rdv' => $rdv,
'email' => $session->get('email'),
'alreadyPaid' => $alreadyPaid
]);
}
/**
* Page confirmation prepayment
* @Route("/confirmation/paiement/success", name="rdv_confirmation_payment")
*/
public function confirmationPayment(
Request $request,
RDVRepository $rdvRepository
): Response {
$linkId = $request->query->get('id');
$rdv = $rdvRepository->findRdvWithLinkId($linkId);
return $this->render('reservation/confirmation_payment.html.twig', [
'rdv' => $rdv,
]);
}
/**
* @Route("/search/for/get-salons", name="get_salons")
*/
public function getSalons(Request $request, SalonRepository $salonRepository): Response
{
$type = $request->query->get('type');
$salon = $request->query->get('salon');
$service = $request->query->get('service');
$salons = $salonRepository->findSalonsByTypeServiceSalonText($type, $service, $salon);
return $this->render('reservation/list_salons.html.twig', [
'salons' => $salons,
]);
}
/**
* @Route("/search/for/get-salon-availability", name="get_salon_availability")
*/
public function getSalonAvailability(
Request $request,
SessionSalonRepository $sessionSalonRepository,
SalonRepository $salonRepository,
RDVRepository $rdvRepository
) {
$messageDates = [];
$isDateAvailable = false;
$service = str_replace('é', 'e', $request->query->get('service'));
$salon = $salonRepository->find((int) $request->query->get('salonId'));
// if no date available, then give the future dates availables
$limit = 5;
$datesAvailable = $this->getAvailability(
$sessionSalonRepository,
$rdvRepository,
$service,
$salon,
$limit
);
// format array result
$tabDates = [];
if (!empty($datesAvailable)) {
foreach ($datesAvailable as $key => $dateAvailable) {
$tabDates[$key] = $dateAvailable['minTime'];
}
}
if (count($tabDates) > 0) {
$messageDates = $tabDates;
$isDateAvailable = true;
}
return $this->json([
'template' => $this->render('reservation/dates_available.html.twig', [
'isDateAvailable' => $isDateAvailable,
'dates' => $messageDates
])->getContent(),
]);
}
/**
* @Route("/search/for/get-intervenant-date", name="get_intervenant_date")
*/
public function getIntervenantByDateRdv(
Request $request,
SessionSalonRepository $sessionSalonRepository,
SalonRepository $salonRepository
) {
$salon = $salonRepository->find((int) $request->query->get('salonId'));
$date = new \DateTime($request->query->get('date'));
$sessionSalon = $sessionSalonRepository->findSessionBySalonAndDate($salon, $date);
$intervenant = $sessionSalon[0]->getIntervenant();
return $this->json([
'intervenant' => $intervenant->getFullname(),
]);
}
/**
* find if the given date is available
* @param string $dateStr
* @param RDVRepository $rdvRepository
* @param Salon $salon
*
* @return boolean
*/
private function checkDateAvailability($dateStr, $rdvRepository, $salon)
{
$givenDate = new \DateTime($dateStr);
$now = new \DateTime('now');
$rdvDatesTaken = $this->getRdvDatesTaken($rdvRepository, $salon);
if ($givenDate <= $now) { // if the date is in the past
return false;
}
if (in_array($givenDate, $rdvDatesTaken)) {
return false;
}
return true;
}
/**
* Get active RDV by salon and return dates
* @param RDVRepository $rdvRepository
* @param Salon $salon
*
* @return array
*/
private function getRdvDatesTaken($rdvRepository, $salon): array
{
// get active rdvs
$rdvs = $rdvRepository->findForRdvWithSalonAndStatus($salon);
$rdvDatesTaken = [];
foreach ($rdvs as $rdv) {
$rdvDatesTaken[] = $rdv->getStart();
}
return $rdvDatesTaken;
}
/**
* Get dates available for a salon
* @param SessionSalonRepository $sessionSalonRepository
* @param RDVRepository $rdvRepository
* @param string $service
* @param Salon $salon
* @param int $limit
*
* @return array
*/
private function getAvailability(
$sessionSalonRepository,
$rdvRepository,
$service,
$salon,
$limit = null
): array {
$typeSite = $salon->getSite() ? $salon->getSite()->getType() : null;
$isEhpad = $typeSite === Site::TYPE_EHPAD ? true : false;
$rdvDatesTaken = $this->getRdvDatesTaken($rdvRepository, $salon);
$sessionSalons = $sessionSalonRepository->findSalonAvailability($salon, $service, '', $limit);
$datesAvailable = [];
$lengthSession = $isEhpad ? 'PT15M' : 'PT30M';
// init opening/closing time
// $openingTimeStr = Salon::DEFAULT_OPENING_TIME;
// $closingTimeStr = Salon::DEFAULT_CLOSING_TIME;
//
// // get salon times
// if ($salon->getOpeningTime() && $salon->getClosingTime()) {
// $openingTimeStr = $salon->getOpeningTime()->format('H:i:s');
// $closingTimeStr = $salon->getClosingTime()->format('H:i:s');
// }
foreach ($sessionSalons as $sessionSalon) {
// Get all session in a day
$timezone = new \DateTimeZone('Europe/Paris');
$period = new \DatePeriod(
new \DateTime($sessionSalon->getStart()->format('d-m-Y H:i:s' ),$timezone),
new \DateInterval($lengthSession),
new \DateTime($sessionSalon->getEnd() ? $sessionSalon->getEnd()->format('d-m-Y H:i:s') : $sessionSalon->getStart()->format('d-m-Y 18:00:00' ),$timezone)
);
// save available spots
$rdvAvailableBySessionSalon = [];
$now = new \DateTime('now', $timezone);
foreach ($period as $p) {
if ($p >= $now && !in_array($p, $rdvDatesTaken)) {
$rdvAvailableBySessionSalon[] = $p->format('H:i');
}
}
$datesAvailable[$sessionSalon->getStart()->format('d-m-Y')]['minTime'] = $rdvAvailableBySessionSalon;
}
return $datesAvailable;
}
/**
* @Route("/search/for/get-additionnal-products", name="get_additionnal_products")
*/
public function getAdditionnalProducts(Request $request, ProductServiceRepository $productServiceRepository)
{
$productId = $request->query->get('productId');
if ($productId !== '') {
$productServices = $productServiceRepository->findBy(['product' => $productId]);
}
return $this->render('reservation/additionnal_services_popup.html.twig', [
'productServices' => $productServices ?? []
]);
}
/**
* Return text if coiffure or esthétique
* @param string $name
*
* @return string
*/
private function serviceName($name): string
{
if (strpos($name, 'coiffure') !== false) {
$text = 'coiffure';
} else if (strpos($name, 'esthétique') !== false) {
$text = 'esthétique';
} else {
$text = '';
}
return $text;
}
/**
* @Route("/docs/privacy-policy/politique-de-confidentialite", name="privacy_policy")
*/
public function privacyPolicy(): Response
{
return $this->render('reservation/privacy_policy.html.twig');
}
/**
* send mail confirmation to client
* @Route("/search/for/send-confirmation-rdv", name="send_confirmation_rdv")
*/
public function sendConfirmationRdv(
Request $request,
MailService $mailService,
FamilleRepository $familleRepository,
RDVRepository $rdvRepository
) {
$famille = $familleRepository->findOneById((int)$request->query->get('familleId'));
$rdv = $rdvRepository->findOneById((int)$request->query->get('rdvId'));
$typeService = strpos(strtolower($famille->getName()), 'coiffure') !== false ? 'de coiffure' : 'd’esthétique';
$mailService->sendConfirmationRdv(
$request->query->get('email'),
[
'rdv' => $rdv,
'client' => $rdv->getClient(),
'typeService' => $typeService,
]
);
return $this->json($rdv->getPaymentLink());
}
/**
* Prepare dates for calendar
* @param mixed $sessionSalonRepository
* @param mixed $rdvRepository
* @param mixed $service
* @param mixed $salon
*
* @return array
*/
private function prepareDatesCalendar($sessionSalonRepository, $rdvRepository, $service, $salon): array
{
// available dates for calendar picker
$availableDates = $this->getAvailability($sessionSalonRepository, $rdvRepository, $service, $salon);
$availableDatetimeFormatted = [];
$availableDatetimeList = [];
$datesToDisable = [];
// convert format date
foreach ($availableDates as $key => $availableDate) {
$formattedDate = new \DateTime($key);
$availableDatetimeFormatted[$formattedDate->format('d-m-Y')] = $availableDate;
$availableDatetimeList[] = $formattedDate->format('d-m-Y');
}
// get disabled dates
$now = new \DateTime('now');
$end = clone $now;
$period = new \DatePeriod(
$now,
new \DateInterval('P1D'),
$end->modify('+12 month')
);
foreach ($period as $date) {
if (!in_array($date->format('d-m-Y'), $availableDatetimeList)) {
$datesToDisable[] = $date->format('d-m-Y');
}
}
return [
'availableDateTime' => json_encode($availableDatetimeFormatted),
'datesToDisable' => json_encode($datesToDisable),
];
}
/**
* @Route("/docs/cgv/politique-de-confidentialite", name="general_terms_conditions")
*/
public function generalTermsAndConditions(): Response
{
return $this->render('reservation/general_terms_conditions.html.twig');
}
}