<?php
namespace Aviatur\ParkBundle\Controller;
use Aviatur\TwigBundle\Services\TwigFolder;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Aviatur\GeneralBundle\Services\AviaturLogSave;
use Aviatur\GeneralBundle\Services\ExceptionLog;
use Aviatur\GeneralBundle\Services\AviaturWebService;
use Aviatur\GeneralBundle\Services\AviaturRestService;
use Aviatur\GeneralBundle\Services\AviaturErrorHandler;
use Aviatur\GeneralBundle\Services\AviaturChangeCoin;
use Aviatur\ParkBundle\Models\ParkAvailModel;
use Aviatur\ParkBundle\Models\ParkSearchModel;
use Aviatur\ParkBundle\Services\ListParkService;
use Aviatur\ParkBundle\Services\LegacyTicketParkService;
use Aviatur\ParkBundle\Services\SendVoucherEmailParkService;
use Aviatur\AgencyBundle\Entity\Agency;
class ParkAvailabilityController extends AbstractController
{
/**
* @var ManagerRegistry
*/
protected ManagerRegistry $managerRegistry;
/**
* @var AviaturRestService
*/
private $aviaturRestService;
/**
* @var SessionInterface
*/
protected $session;
/**
* @var Agency|object|null
*/
protected $agency;
protected static $exceptionLog;
private $em;
private $listParkService;
private $legacyTicketParkService;
private $sendVoucherEmailParkService;
public function __construct(ManagerRegistry $managerRegistry, SessionInterface $session, AviaturRestService $aviaturRestService, ExceptionLog $exceptionLog, ListParkService $listParkService, LegacyTicketParkService $legacyTicketParkService, SendVoucherEmailParkService $sendVoucherEmailParkService)
{
self::$exceptionLog = $exceptionLog;
$this->em = $managerRegistry->getManager();
$this->aviaturRestService = $aviaturRestService;
$this->session = $session;
$this->agency = $this->em->getRepository(Agency::class)->find($session->get('agencyId'));
$this->listParkService = $listParkService;
$this->legacyTicketParkService = $legacyTicketParkService;
$this->sendVoucherEmailParkService = $sendVoucherEmailParkService;
}
public function searchAction()
{
return $this->redirect(
$this->generateUrl(
'aviatur_search_parks',
[]
)
);
}
// Función vista de disponibilidad
public function availabilityAction(Request $request, AviaturErrorHandler $aviaturErrorHandler, ParameterBagInterface $parameterBag, TwigFolder $twigFolder)
{
$params = $request->attributes->get("_route_params");
// Listado de variables
$namePark = $params["park"];
$nameSede = $params["sede"];
$date1 = $params["datepark"];
$now = new \DateTime();
// Inicializar el mensaje de error
$redirectTitle = "Recomendación Automática";
$redirectText = "";
// VALIDACIÓN DE FECHA: Comprobamos si la fecha es válida
$dateIn = \DateTime::createFromFormat("Y-m-d", $date1);
if (!$dateIn || $dateIn->format("Y-m-d") !== $date1 || $dateIn <= $now) {
// Si la fecha no es válida se asigna la fecha dateIn
$dateIn = $now->modify("+1 day");
$redirectTitle = "Se requiere seleccionar una fecha con 24 horas de antelación.";
$redirectText .= "La búsqueda se ha ajustado automáticamente a la fecha más cercana permitida. \n";
}
// VALIDACIÓN DE ADULTOS
$passangerTickets = $params["passenger"];
list($adults, $children) = explode("-", $passangerTickets);
if (intval($adults) <= 0) {
$passangerTickets = "1-" . $children; // Si no hay adultos, se asigna 1 adulto
$redirectTitle = "Es necesario al menos un adulto.";
$redirectText .= "La búsqueda se ha ajustado automáticamente para incluir 1 adulto.";
}
// Si hay un mensaje de error
if ($redirectText) {
// Configurar el modelo de búsqueda
$parkSearchModel = new ParkSearchModel();
$parkSearchModel->setNamePark($namePark);
$parkSearchModel->setNameSedePark($nameSede);
$parkSearchModel->setDatePark($dateIn->format("Y-m-d"));
$parkSearchModel->setPassengerPark($passangerTickets);
$parkSearchAvail = $parkSearchModel->getData();
// Redirigir con el mensaje de error
return $this->redirect($aviaturErrorHandler->errorRedirectNoEmail($this->generateUrl('aviatur_park_avaliability', $parkSearchAvail), $redirectTitle, $redirectText));
}
// Validación de la existencia del parque y sede
$availabilityParameters = $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkSedes::class)->findAvailabilityParameters($this->agency, $namePark, $nameSede);
// Si no se encuentra el parque o la sede
if (empty($availabilityParameters)) {
$titleError = "La ubicación seleccionada no está disponible.";
$textError = "Ha sido redirigido al home para que pueda seguir explorando opciones.";
// Redirigir al Home de parques con el mensaje de error
return $this->redirect($aviaturErrorHandler->errorRedirectNoEmail($this->generateUrl('aviatur_search_parks', []), $titleError, $textError));
}
// Obtener parques y sedes disponibles
$parksAndSedes = $this->listParkService->getParksAndSedes($this->agency);
$agencyFolder = $twigFolder->twigFlux();
// Renderizar la vista con los datos
return $this->render($twigFolder->twigExists(sprintf('@AviaturTwig/%s/Park/parkAvailability_index.html.twig', $agencyFolder)), [
"listParks" => $parksAndSedes["parks"],
"listSedesPark" => $parksAndSedes["sedes"]
]);
}
// Función disponibilidad de los tickets del parque
public function getAvailableTicketsAction(Request $request, AviaturLogSave $logSave, AviaturWebService $aviaturWebService, AviaturErrorHandler $aviaturErrorHandler, ParameterBagInterface $parameterBag, AviaturChangeCoin $aviaturChangeCoin)
{
// Generación del transactionId
$transactionIdSessionName = $parameterBag->get('transaction_id_session_name');
$session = $this->session;
$makeLogin = true;
if ($request->query->has('transactionMulti')) {
$transactionId = base64_decode($request->query->get('transactionMulti'));
$session->set($transactionIdSessionName, $transactionId);
$makeLogin = false;
}
//generacion del transactionId
if ($makeLogin) {
$transactionIdResponse = $aviaturWebService->loginService('SERVICIO_MPT', 'dummy|http://www.aviatur.com.co/dummy/', []);
if ('error' == $transactionIdResponse || is_array($transactionIdResponse)) {
$aviaturErrorHandler->errorRedirect('', 'Error MPA', 'No se creo Login!');
return (new JsonResponse())->setData([
'tickets' => [],
'error' => true,
"message" => [
"title" => "No se encontró disponibilidad en este momento.",
"text" => "Estamos experimentando dificultades técnicas en este momento."
]
]);
}
$transactionId = (string) $transactionIdResponse;
$session->set($transactionIdSessionName, $transactionId);
}
// Parametros RQ
$params = json_decode($request->getContent(), true);
// VALIDACIÓN DE ADULTOS
$adultsTickets = (int) $params['adults'];
$childrenTickets = (int) $params['children'];
if (($adultsTickets + $childrenTickets) > 9) {
return (new JsonResponse())->setData([
"tickets" => [],
"error" => true,
"message" => [
"title" => "No se encontró disponibilidad en este momento.",
"text" => "Para más de 9 pasajeros, contáctenos para brindarle la mejor opción o modifique la búsqueda."
]
]);
}
// Obtener datos de BD para el RQ
$availabilityParameters = $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkSedes::class)->findAvailabilityParameters($this->agency, $params['park'], $params['sede']);
$availableTickets = [];
if (isset($availabilityParameters) && count($availabilityParameters) > 0) {
$date1 = $params['datepark'];
$date = new \DateTime($date1);
$date->modify('+1 day');
$date2 = $date->format('Y-m-d');
$parkAvailModel = new ParkAvailModel();
$parkAvailModel->setRequestKind($availabilityParameters['requestKind']);
$parkAvailModel->setStartDate($date1);
$parkAvailModel->setEndDate($date2);
$parkAvailModel->setEnvironmentCode($availabilityParameters['environmentCode']);
$parkRQ = $parkAvailModel->getData();
$logSave->logSave(json_encode($parkRQ), 'ParkAvail', $availabilityParameters['providerIdentifier'] . '_RQ', $transactionId);
$response = $this->aviaturRestService->callRestWebService($availabilityParameters['wsUrl'], 'dispo', $parkRQ);
$logSave->logSave(json_encode($response), 'ParkAvail', 'RS', $transactionId);
// Guardar en sessión el RS de disponibilidad encodeada
$ajaxParametersParksAvail = base64_encode(json_encode($response));
$session->set($transactionId.'[park][ticketsAvailability]', $ajaxParametersParksAvail);
// Verificar que el response tenga los datos necesarios
if (!empty($response) && $response["succeeded"] && !empty($response["productCatalogs"])) {
// Obtener TRM
$trmData = $aviaturChangeCoin->getExchangeRate('USD', 'COP', 'TRM');
$trmDataValue = $trmData['OfficialExchangeRate'];
// Mapear tickets RS
$availableTickets = $this->getAvailableTickets($response, $params, $trmDataValue, $availabilityParameters['deliveryMethod']);
}
}
return new JsonResponse([
"transactionId" => $transactionId,
"tickets" => $availableTickets,
"error" => empty($availableTickets),
"message" => [
"title" => empty($availableTickets) ? "No se encontró disponibilidad en este momento." : "",
"text" => empty($availableTickets) ? "Por favor, intente nuevamente más tarde o contáctenos para obtener más información." : ""
]
]);
}
// Obtener tipo de pasajero y calcular el precio del ticket
private function processTicketForPassengerType($ticket, $params, $markupPark, $trm, $adultsTickets, $childrenTickets)
{
// Determinar el tipo de pasajero (Adulto, Niño, No importa la edad)
$typePassengerTicket = !empty($ticket['ageValue']) ? $ticket['ageValue'] : $ticket['ageQualifies'] ?? null;
if (is_array($typePassengerTicket) && !empty($typePassengerTicket)) {
$typePassengerTicket = $typePassengerTicket[0]['ageType'];
}
// Definir cantidad de tickets y precios iniciales
$quantityTickets = 0;
$priceTicketAdult = 0;
$priceTicketChild = 0;
// Obtener el precio base y la moneda
$amountAfterTaxTicket = $ticket['price']['amountAfterTax'];
$decimalPlacesTicket = $ticket['price']['decimalPlaces'];
$currencyTicket = 'COP';
// Actualizamos la moneda si es necesario
if ($trm == 1) {
$currencyTicket = $ticket['price']['currency'];
}
// Precio del ticket en cop con trm y markup
$priceTicketCOP = $this->calculateTicketPrice($amountAfterTaxTicket, $decimalPlacesTicket, $markupPark, $trm);
// Calcular precios basados en el tipo de pasajero
switch ($typePassengerTicket) {
case 'Adult':
case '10 Años en adelante':
$quantityTickets = $adultsTickets;
$priceTicketAdult = $priceTicketCOP * $quantityTickets;
break;
case 'Child':
case 'Entre 3 y 9 años':
$quantityTickets = $childrenTickets;
$priceTicketChild = $priceTicketCOP * $quantityTickets;
break;
case 'No importa la edad':
$quantityTickets = $adultsTickets + $childrenTickets;
$priceTicketAdult = $priceTicketCOP * $adultsTickets;
$priceTicketChild = $priceTicketCOP * $childrenTickets;
break;
}
return [$priceTicketAdult, $priceTicketChild, $quantityTickets, $currencyTicket];
}
// Mapeo de tickets disponibles
private function getAvailableTickets($response, $params, $trm, $deliveryMethodSelect)
{
$allTickets = $response["productCatalogs"];
$adultsTickets = (int) $params['adults'];
$childrenTickets = (int) $params['children'];
$quantityTotalTickets = $adultsTickets + $childrenTickets;
$groupedTickets = [];
// Obtener el porcentaje de markup del parque
$markupPark = $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkMarkup::class)->getMarkupPark($params['park'], $params['sede']);
$markupPark = empty($markupPark) ? 0 : $markupPark;
// Procesar cada ticket
foreach ($allTickets as $ticket) {
// Filtrar tickets no deseados
if (
($params['park'] == "Universal" && stripos(strtolower($ticket['productName']), 'vip') !== false) ||
($params['park'] == "Universal" && stripos(strtolower($ticket['productName']), 'express') !== false) ||
($params['park'] == "Universal" && stripos(strtolower($ticket['salesProgramName']), 'express') !== false) ||
(!empty($ticket["date"]) && !str_contains($ticket["date"], $params["datepark"])) ||
(!empty($ticket["price"]) && !empty($ticket["price"]["amountAfterTax"]) && $ticket["price"]["amountAfterTax"] == 0) ||
(!empty($ticket["allowedDeliveryMethods"]) && !in_array($deliveryMethodSelect, $ticket["allowedDeliveryMethods"]))
) {
continue;
}
// Tickets especiales
$isSpecial = false;
if (
($params['park'] == "Disney" && isset($ticket['productId'][0]) && strtoupper($ticket['productId'][0]) == 'S')
) {
$isSpecial = true;
}
// Eliminar "Adult" o "Child" del nombre del ticket para agruparlos
$baseName = preg_replace('/(Adult|Child|Ad |Ch )/', '', $ticket['productName']);
$baseName = trim($baseName);
// Inicializar ticket agrupado si no existe
if (!isset($groupedTickets[$baseName])) {
$groupedTickets[$baseName] = [
'nameTicket' => $baseName,
'numberOfDays' => $ticket['numberOfDays'],
'salesProgramId' => $ticket['salesProgramId'],
'quantityTotalTickets' => $quantityTotalTickets,
'priceTicket' => [
'priceAdultTicket' => 0,
'priceChildTicket' => 0,
'priceTotalTicket' => 0,
'currency' => 'COP'
],
'findEvent' => $ticket['capacity'],
'isSpecial' => $isSpecial,
'detailTicket' => [],
'tickets' => []
];
}
// Llamada al método para procesar el ticket según el tipo de pasajero
list($priceTicketAdult, $priceTicketChild, $quantityTickets, $currencyTicket) = $this->processTicketForPassengerType($ticket, $params, $markupPark, $trm, $adultsTickets, $childrenTickets);
// Actualizamos los precios si es necesario
if (
($groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] != 0 && $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] < $priceTicketAdult) ||
($groupedTickets[$baseName]['priceTicket']['priceChildTicket'] != 0 && $groupedTickets[$baseName]['priceTicket']['priceChildTicket'] < $priceTicketChild)
) {
continue;
}
$groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] = $priceTicketAdult != 0 ? $priceTicketAdult : $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'];
$groupedTickets[$baseName]['priceTicket']['priceChildTicket'] = $priceTicketChild != 0 ? $priceTicketChild : $groupedTickets[$baseName]['priceTicket']['priceChildTicket'];
$groupedTickets[$baseName]['priceTicket']['currency'] = $currencyTicket;
// Agregar detalles del ticket
$ticketDetails = !empty($ticket['aditionalData']) ? $ticket['aditionalData'] : $ticket['detailTicket'];
if (!empty($ticketDetails)) {
$groupedTickets[$baseName]['detailTicket'] = $this->processTicketDetails($ticketDetails);
}
// Agregar el producto al grupo correspondiente
if ($quantityTickets > 0) {
$groupedTickets[$baseName]['priceTicket']['priceTotalTicket'] = $groupedTickets[$baseName]['priceTicket']['priceAdultTicket'] + $groupedTickets[$baseName]['priceTicket']['priceChildTicket'];
$groupedTickets[$baseName]['tickets'][] = $ticket['productId'];
}
}
// Verificamos si $groupedTickets no está vacío. Ordenamos 'priceTicket' de menor a mayor
if (!empty($groupedTickets)) {
usort($groupedTickets, function($a, $b) {
return $a['priceTicket']['priceTotalTicket'] <=> $b['priceTicket']['priceTotalTicket'];
});
// Eliminar los elementos que cumplen las condiciones especificadas
foreach ($groupedTickets as $key => $ticket) {
if (($adultsTickets != 0 && $ticket['priceTicket']['priceAdultTicket'] == 0) ||
($childrenTickets != 0 && $ticket['priceTicket']['priceChildTicket'] == 0)) {
unset($groupedTickets[$key]);
}
}
$availabilityParameters = $this->em->getRepository(\Aviatur\ParkBundle\Entity\ParkSedes::class)->findAvailabilityParameters($this->agency, $params['park'], $params['sede']);
// Mapear información legal de los tickets, politicas.
$groupedTickets = array_values($groupedTickets);
foreach ($groupedTickets as $key => $ticket) {
// Extraer número de parques si existe "X-Park"
$parkNumber = "";
if (preg_match('/(\d+)-Park/', $ticket['nameTicket'], $matches)) {
$parkNumber = $matches[1];
}
// Validar si es Park to Park y lista de parques permitidos
$aditionalData = $ticket["detailTicket"];
$isParkToPark = $this->sendVoucherEmailParkService->isParkToPark($aditionalData);
$parksVisitList = $this->getParksVisitList($aditionalData);
// Conector de listado de parques para text voucher
$conectorText = $isParkToPark ? "<strong>AND</strong>" : "<strong>OR</strong>";
$nameParksVisitList = "";
$countParks = count($parksVisitList);
foreach ($parksVisitList as $i => $park) {
$nameParksVisitList .= $park;
if ($i < $countParks - 1) {
$nameParksVisitList .= " $conectorText ";
}
}
// Agregar políticas legacy y reemplazar valores
$legacyTicketPark = $this->legacyTicketParkService->getLegacyTicketPark($availabilityParameters["parkSedesId"], $ticket['salesProgramId'], $ticket['nameTicket'], $ticket['numberOfDays'], $parkNumber);
if(empty($legacyTicketPark)){
unset($groupedTickets[$key]);
}else{
$legacyTicketPark = str_replace("{{productName}}", $ticket["nameTicket"], $legacyTicketPark);
$legacyTicketPark = str_replace("{{nameParksVisitList}}", $nameParksVisitList, $legacyTicketPark);
$groupedTickets[$key]['legacyTicketPark'] = $legacyTicketPark;
}
}
}
return $groupedTickets;
}
// Calcular el precio del ticket con markup en COP
private function calculateTicketPrice($priceTicket, $decimalPlacesTicket, $markupPark, $trm)
{
$priceTicket = $priceTicket * (1 / (100 - $markupPark)) * 100;
$priceTicket = round($priceTicket, (int) $decimalPlacesTicket); //Redondeando en USD
$priceTicket = $priceTicket * $trm;
return round($priceTicket, (int) $decimalPlacesTicket); //Redondeando en COP
}
// Función auxiliar para procesar los detalles del ticket
private function processTicketDetails($infodetailTicket)
{
$arrayDetailTicket = [];
// Si tenemos additionValues y additionType es "ValidPark", lo procesamos - DISNEY
$validParkItems = array_filter($infodetailTicket, function ($item) {
return isset($item['additionType']) && $item['additionType'] == "ValidPark";
});
if (!empty($validParkItems)) {
$validParkItems = reset($validParkItems);
$newItem = [
'titleDetailPark' => 'Parques que puedes visitar:',
'itemsDetailPark' => array_map(function ($subitem) {
return $subitem['name'] ?? '';
}, $validParkItems['additionValues'])
];
$arrayDetailTicket[] = $newItem;
$arrayDetailTicket[] = [
'titleDetailPark' => 'No se requiere reservar los parques.',
'itemsDetailPark' => []
];
} else {
// Si no hay "ValidPark", agregamos los títulos con itemsDetailPark vacíos - UNIVERSAL
foreach ($infodetailTicket as $item) {
$arrayDetailTicket[] = [
'titleDetailPark' => $item,
'itemsDetailPark' => []
];
}
}
return $arrayDetailTicket;
}
// Función para obtener la lista de parques del ticket
private function getParksVisitList(array $aditionalData): array {
$parksVisitList = [];
foreach ($aditionalData as $item) {
$text = is_array($item) && isset($item['titleDetailPark']) ? $item['titleDetailPark'] : (is_string($item) ? $item : '');
if (stripos($text, 'Posibilidad de entrar a') !== false) {
$parts = explode(':', $text, 2);
if (isset($parts[1])) {
$parks = explode(',', $parts[1]);
$parks = array_map('trim', $parks);
$parksVisitList = array_merge($parksVisitList, $parks);
}
}
}
return array_filter(array_unique($parksVisitList), fn($p) => $p !== '');
}
}