<?php
declare(strict_types=1);
namespace App\MDS\ApiBundle\Controller;
use App\Entity\ClientContact;
use App\MDS\VenuesBundle\Entity\Reservation;
use App\MDS\VenuesBundle\Entity\ReservationLoungeDetails;
use App\MDS\VenuesBundle\Entity\ReservationLoungeDescription;
use App\MDS\VenuesBundle\Entity\ReservationLoungeSimple;
use App\MDS\VenuesBundle\Entity\ReservationLoungePicture;
use App\MDS\VenuesBundle\Entity\ReservationService;
use App\MDS\AvexpressBundle\Entity\AveProduct;
use App\MDS\AvexpressBundle\Entity\AvePackageTemplate;
use App\MDS\AvexpressBundle\Entity\AvePackageTemplateItems;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ApiProposalsClientController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $em
) {}
/**
* @Route("/api/client/proposal/{token}/recommended/avproducts", name="api_proposal_recommended_products", methods={"GET", "POST"})
* Recoger la selección de productos y packs enviados por el cliente desde la web y asociarlos a la reserva general por Opción
*/
public function processRecommendedProductsAction(string $token, Request $request): JsonResponse
{
/** @var Reservation|null $reservation */
$reservation = $this->em->getRepository(Reservation::class)->findOneBy(['token' => $token]);
if (!$reservation) {
return new JsonResponse([
'success' => false,
'error' => 'No se encontró ninguna reserva asociada al token proporcionado.'
], Response::HTTP_NOT_FOUND);
}
$content = $request->getContent();
if (empty($content)) {
return new JsonResponse(['success' => false, 'error' => 'El cuerpo de la petición está vacío.'], Response::HTTP_BAD_REQUEST);
}
$payload = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return new JsonResponse(['success' => false, 'error' => 'Formato JSON inválido.'], Response::HTTP_BAD_REQUEST);
}
// Aceptamos tanto la clave "products" como "avProducts" enviada desde la web
$rawProducts = $payload['products'] ?? $payload['avProducts'] ?? null;
if (!is_array($rawProducts)) {
return new JsonResponse(['success' => false, 'error' => 'Estructura JSON incorrecta (se esperaba array de productos).'], Response::HTTP_BAD_REQUEST);
}
$processedItems = [];
$errors = [];
$ahora = new \DateTime("now");
$userId = $this->getUser() && method_exists($this->getUser(), 'getId') ? $this->getUser()->getId() : 1;
$this->em->beginTransaction();
try {
// Actualización de ajustes de Lounges si se envían
if (isset($payload['loungeSettings']) && is_array($payload['loungeSettings'])) {
foreach ($payload['loungeSettings'] as $setting) {
$loungeId = filter_var($setting['loungeId'] ?? null, FILTER_VALIDATE_INT);
if ($loungeId) {
$loungeEntity = $this->em->getRepository(ReservationLoungeSimple::class)->find($loungeId);
if ($loungeEntity && (int)$loungeEntity->getIdReservation() === (int)$reservation->getId()) {
if (isset($setting['showAudiovisualPrice'])) {
$loungeEntity->setShowAudiovisualPrice((bool)$setting['showAudiovisualPrice']);
}
if (isset($setting['showAudiovisualPackage'])) {
$loungeEntity->setShowAudiovisualPackage((bool)$setting['showAudiovisualPackage']);
}
$this->em->persist($loungeEntity);
}
}
}
}
$individualProducts = [];
$packagesToConsolidate = [];
$receivedKeys = [];
$activeRanksInPayload = [];
// MAPA PARA CONTROLAR Y DESDUPLICAR ELEMENTOS REPETIDOS EN EL MISMO PAYLOAD
$seenProductByOptionAndDate = [];
foreach ($rawProducts as $index => $item) {
$sourceType = filter_var($item['sourceType'] ?? null, FILTER_UNSAFE_RAW);
$packId = filter_var($item['packId'] ?? null, FILTER_VALIDATE_INT);
$productId = filter_var($item['productId'] ?? null, FILTER_VALIDATE_INT);
$dateStr = !empty($item['date']) ? trim((string)$item['date']) : null;
$quantity = isset($item['quantity']) ? (float)$item['quantity'] : 0.0;
$optionKey = filter_var($item['optionKey'] ?? null, FILTER_VALIDATE_INT);
$rankQuote = null;
if ($optionKey) {
/** @var ReservationLoungeSimple|null $loungeRef */
$loungeRef = $this->em->getRepository(ReservationLoungeSimple::class)->find($optionKey);
if ($loungeRef) {
$rankQuote = $loungeRef->getRankQuote();
}
}
if ($rankQuote !== null) {
$activeRanksInPayload[$rankQuote] = true;
}
if ($sourceType === 'avPackage' && $packId && !$productId) {
$key = sprintf('%d-%s-%s', $packId, $rankQuote ?? 'no-rank', $dateStr ?? 'no-date');
if (!isset($packagesToConsolidate[$key])) {
$packagesToConsolidate[$key] = [
'packId' => $packId,
'rankQuote' => $rankQuote,
'date' => $dateStr,
'index' => $index
];
}
}
elseif ($productId && $quantity > 0) {
// Prevenimos la duplicación si el frontend envía el mismo productId para la misma Opción y Fecha
$dedupKey = sprintf('%d_%s_%s', $productId, $rankQuote ?? 'norank', $dateStr ?? 'nodate');
if (isset($seenProductByOptionAndDate[$dedupKey])) {
continue;
}
$seenProductByOptionAndDate[$dedupKey] = true;
$individualProducts[] = [
'item' => $item,
'index' => $index
];
$key = sprintf('%d_%s', $productId, $rankQuote ?? 'norank');
$receivedKeys[$key] = true;
}
}
// --- RECONCILIACIÓN POR AUSENCIA (Acotada a los ranks recibidos) ---
$ranksToProcess = array_keys($activeRanksInPayload);
if (!empty($ranksToProcess)) {
$qbExisting = $this->em->createQueryBuilder()
->select('s')
->from(ReservationService::class, 's')
->where('s.reservationId = :resId')
->andWhere('s.toinvoice = true')
->andWhere('s.rank IN (:ranks)')
->setParameter('resId', $reservation->getId())
->setParameter('ranks', $ranksToProcess);
/** @var ReservationService[] $existingServicesForRanks */
$existingServicesForRanks = $qbExisting->getQuery()->getResult();
foreach ($existingServicesForRanks as $dbService) {
$dbServiceName = $dbService->getName() ?? '';
if (str_contains($dbServiceName, '[Recomendado]')) {
$dbProductId = (int) $dbService->getServiceId();
$dbRank = $dbService->getRank() ?? 'norank';
$dbKey = sprintf('%d_%s', $dbProductId, $dbRank);
if (!isset($receivedKeys[$dbKey])) {
$this->em->remove($dbService);
}
}
}
}
// --- PROCESAR PRODUCTOS INDIVIDUALES (SOLO FECHA, SIN HORA) ---
foreach ($individualProducts as $wrapped) {
$item = $wrapped['item'];
$index = $wrapped['index'];
$productId = filter_var($item['productId'] ?? null, FILTER_VALIDATE_INT);
$quantity = (float) $item['quantity'];
$dateStr = !empty($item['date']) ? trim((string)$item['date']) : null;
$optionKey = filter_var($item['optionKey'] ?? null, FILTER_VALIDATE_INT);
$rankQuote = null;
if ($optionKey) {
/** @var ReservationLoungeSimple|null $loungeRef */
$loungeRef = $this->em->getRepository(ReservationLoungeSimple::class)->find($optionKey);
if ($loungeRef) {
$rankQuote = $loungeRef->getRankQuote();
}
}
/** @var AveProduct|null $product */
$product = $this->em->getRepository(AveProduct::class)->find($productId);
if (!$product) {
continue;
}
$targetDate = $dateStr ? \DateTime::createFromFormat('d/m/Y', $dateStr) : null;
if (!$targetDate) {
$targetDate = $reservation->getDateStart();
}
// Normalizamos siempre a las 00:00:00
$dateIn = (clone $targetDate)->setTime(0, 0, 0);
$dateOut = (clone $targetDate)->setTime(23, 59, 59);
$dayStart = (clone $targetDate)->setTime(0, 0, 0);
$dayEnd = (clone $targetDate)->setTime(23, 59, 59);
$cleanProductName = trim($product->getName());
$expectedName = $cleanProductName . " [Recomendado]";
// BÚSQUEDA DQL DENTRO DEL RANGO DEL DÍA DE LA OPCIÓN
$qb = $this->em->createQueryBuilder()
->select('s')
->from(ReservationService::class, 's')
->where('s.reservationId = :resId')
->andWhere('s.serviceId = :prodId')
->andWhere('s.name = :expectedName')
->andWhere('s.dateInAt >= :dayStart AND s.dateInAt <= :dayEnd')
->setParameter('resId', $reservation->getId())
->setParameter('prodId', $product->getId())
->setParameter('expectedName', $expectedName)
->setParameter('dayStart', $dayStart)
->setParameter('dayEnd', $dayEnd);
if ($rankQuote !== null) {
$qb->andWhere('s.rank = :rankQuote')
->setParameter('rankQuote', $rankQuote);
} else {
$qb->andWhere('s.rank IS NULL');
}
/** @var ReservationService|null $reservationService */
$reservationService = $qb->setMaxResults(1)->getQuery()->getOneOrNullResult();
if ($reservationService && (float)$reservationService->getUnits() === $quantity) {
$processedItems[] = [
'productId' => $productId,
'name' => $expectedName,
'quantity' => $quantity,
'rank' => $rankQuote,
'date' => $dateIn->format('d/m/Y')
];
continue;
}
if (!$reservationService) {
$reservationService = new ReservationService();
$reservationService->setReservationId($reservation->getId());
$reservationService->setServiceId($product->getId());
$reservationService->setSupplierId(0);
$reservationService->setServiceCatId(0);
$reservationService->setCommission('0');
$reservationService->setContcolor('#32aaf0');
$reservationService->setViewInfo(true);
$reservationService->setCreatedAt($ahora);
$reservationService->setCreatedId($userId);
$reservationService->setToinvoice(true);
}
$reservationService->setRank($rankQuote);
$reservationService->setDateInAt($dateIn);
$reservationService->setDateOutAt($dateOut);
$reservationService->setPrice((string) $product->getPrice());
$reservationService->setUnits((string) $quantity);
$reservationService->setServiceCatName(!empty($product->getType()) ? $product->getType() : 'Audiovisuales');
$reservationService->setName($expectedName);
$reservationService->setUpdatedAt($ahora);
$reservationService->setUpdatedId($userId);
$this->em->persist($reservationService);
$processedItems[] = [
'productId' => $productId,
'name' => $expectedName,
'quantity' => $quantity,
'rank' => $rankQuote,
'date' => $dateIn->format('d/m/Y')
];
}
// --- PROCESAR PAQUETES CONSOLIDADOS ---
foreach ($packagesToConsolidate as $packData) {
$packId = $packData['packId'];
$rankQuote = $packData['rankQuote'];
$dateStr = $packData['date'];
$index = $packData['index'];
/** @var AvePackageTemplate|null $package */
$package = $this->em->getRepository(AvePackageTemplate::class)->find($packId);
if (!$package) {
$errors[] = "Fila {$index}: El paquete con ID {$packId} no existe.";
continue;
}
$expectedPackName = sprintf("[Pack: %s]", trim($package->getName()));
$packagePrice = $package->getPricePackage() !== null
? (float) $package->getPricePackage()
: (float) $package->getTotalNetPrice();
$targetDate = $dateStr ? \DateTime::createFromFormat('d/m/Y', $dateStr) : null;
if (!$targetDate) {
$targetDate = $reservation->getDateStart();
}
$dateIn = (clone $targetDate)->setTime(0, 0, 0);
$dateOut = (clone $targetDate)->setTime(23, 59, 59);
$dayStart = (clone $targetDate)->setTime(0, 0, 0);
$dayEnd = (clone $targetDate)->setTime(23, 59, 59);
$qb = $this->em->createQueryBuilder()
->select('s')
->from(ReservationService::class, 's')
->where('s.reservationId = :resId')
->andWhere('s.name = :packName')
->andWhere('s.toinvoice = true')
->andWhere('s.dateInAt >= :dayStart AND s.dateInAt <= :dayEnd')
->setParameter('resId', $reservation->getId())
->setParameter('packName', $expectedPackName)
->setParameter('dayStart', $dayStart)
->setParameter('dayEnd', $dayEnd);
if ($rankQuote !== null) {
$qb->andWhere('s.rank = :rankQuote')
->setParameter('rankQuote', $rankQuote);
} else {
$qb->andWhere('s.rank IS NULL');
}
/** @var ReservationService|null $reservationService */
$reservationService = $qb->setMaxResults(1)->getQuery()->getOneOrNullResult();
if ($reservationService && (float)$reservationService->getUnits() === 1.0) {
$processedItems[] = [
'packageId' => $packId,
'name' => $expectedPackName,
'quantity' => 1.0,
'rank' => $rankQuote,
'date' => $dateIn->format('d/m/Y')
];
continue;
}
if (!$reservationService) {
$reservationService = new ReservationService();
$reservationService->setReservationId($reservation->getId());
$reservationService->setServiceId(0);
$reservationService->setSupplierId(0);
$reservationService->setServiceCatId(0);
$reservationService->setCommission('0');
$reservationService->setContcolor('#32aaf0');
$reservationService->setViewInfo(true);
$reservationService->setCreatedAt($ahora);
$reservationService->setCreatedId($userId);
$reservationService->setToinvoice(true);
}
$reservationService->setRank($rankQuote);
$reservationService->setDateInAt($dateIn);
$reservationService->setDateOutAt($dateOut);
$reservationService->setPrice((string) $packagePrice);
$reservationService->setUnits('1.0');
$reservationService->setServiceCatName('Audiovisuales');
$reservationService->setName($expectedPackName);
$reservationService->setUpdatedAt($ahora);
$reservationService->setUpdatedId($userId);
$this->em->persist($reservationService);
$processedItems[] = [
'packageId' => $packId,
'name' => $expectedPackName,
'quantity' => 1.0,
'rank' => $rankQuote,
'date' => $dateIn->format('d/m/Y')
];
}
if (!empty($errors)) {
$this->em->rollback();
return new JsonResponse(['success' => false, 'errors' => $errors], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$this->em->flush();
$this->em->commit();
return new JsonResponse([
'success' => true,
'message' => 'Selección guardada y asociada a la reserva correctamente.',
'processedItems' => $processedItems
], Response::HTTP_OK);
} catch (\Exception $e) {
$this->em->rollback();
return new JsonResponse([
'success' => false,
'error' => 'Error interno al procesar e insertar la solicitud.',
'details' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @Route("/api/client/proposal/{token}", name="generate_token", methods={"GET"})
*/
public function generateToken(Request $request, string $token): JsonResponse
{
/** @var Reservation|null $reservation */
$reservation = $this->em->getRepository(Reservation::class)->findOneBy(['token' => $token]);
if (!$reservation) {
throw $this->createNotFoundException('No se encontró ninguna reserva para el token proporcionado.');
}
$formattedReservation = $this->formatReservation($reservation);
$formattedReservation['breakdownPackages'] = $reservation->isBreakdownPackages();
$data = [
'reservation' => $formattedReservation,
'options' => [],
'reservationService' => [],
'filteredServices' => [],
];
// 1. Obtener plantillas de paquetes e ítems maestros
$avPackages = $this->em->getRepository(AvePackageTemplate::class)->findAll();
$allPackageItems = $this->em->getRepository(AvePackageTemplateItems::class)->findBy([], ['rankAvPack' => 'ASC']);
$itemsByPackageId = [];
$packageItemPricesMap = [];
foreach ($allPackageItems as $item) {
$itemsByPackageId[(int) $item->getPackId()][] = $item;
$packageItemPricesMap[(int) $item->getPackId()][(int) $item->getProductId()] = (float) $item->getServicePrice();
}
$packagesByLoungeDetail = [];
$packNamesToDetailIdsMap = [];
$packNameToIdMap = [];
foreach ($avPackages as $pkg) {
if (!$pkg->getLounges()->isInitialized()) {
$pkg->getLounges()->initialize();
}
$packageItems = $itemsByPackageId[(int) $pkg->getId()] ?? [];
$formattedPackage = $this->formatPackageTemplate($pkg, $packageItems);
$packNameTrimmed = trim($pkg->getName());
$packNameToIdMap[$packNameTrimmed] = $pkg->getId();
foreach ($pkg->getLounges() as $lounge) {
$packagesByLoungeDetail[$lounge->getId()][] = $formattedPackage;
$packNamesToDetailIdsMap[$packNameTrimmed][] = $lounge->getId();
}
}
// Obtener las salas contratadas
$loungeItems = $this->em->getRepository(ReservationLoungeSimple::class)->findBy(
['idReservation' => $reservation->getId()],
['dateStart' => 'ASC']
);
// Obtener todas las líneas de servicio reales de la reserva
$allReservationServices = $this->em->getRepository(ReservationService::class)->findBy([
'reservationId' => $reservation->getId(),
'toinvoice' => true
]);
// MAPEAR LOS SERVICIOS FILTRANDO STRICTAMENTE POR FECHA Y OPCIÓN (rankQuote)
$selectionsByLoungeId = [];
$packageSelectionsByLoungeId = [];
foreach ($allReservationServices as $service) {
$serviceDate = $service->getDateInAt();
if (!$serviceDate) {
continue;
}
$dateKey = $serviceDate->format('Y-m-d');
$serviceId = (int)$service->getServiceId();
$units = (float)$service->getUnits();
$serviceName = $service->getName() ?? '';
$serviceRank = $service->getRank();
foreach ($loungeItems as $loungeItem) {
$loungeDate = $loungeItem->getDateStart();
$loungeRank = $loungeItem->getRankQuote();
if ($loungeDate && $loungeDate->format('Y-m-d') === $dateKey) {
if ($serviceRank !== null && (int)$serviceRank !== (int)$loungeRank) {
continue;
}
$loungeSimpleId = $loungeItem->getId();
if ($serviceId > 0 && str_contains($serviceName, '[Recomendado]')) {
$selectionsByLoungeId[$loungeSimpleId][$serviceId] = $units;
}
if (str_contains($serviceName, '[Pack:')) {
if (preg_match('/\[Pack:\s*(.*?)\]/', $serviceName, $matches)) {
$extractedPackName = trim($matches[1]);
$packageSelectionsByLoungeId[$loungeSimpleId][$extractedPackName] = $units;
}
}
}
}
}
$servicesGroupedByLoungeDetailId = [];
$globalRecommendedServices = [];
$specialServices = [];
$allowedCategories = ['limpieza', 'seguridad', 'parking', 'mobiliario'];
foreach ($allReservationServices as $service) {
$units = ($service->getUnits() > 0) ? (float) $service->getUnits() : 1.0;
$pax = ($service->getPax() > 0) ? (float) $service->getPax() : 1.0;
$priceOver = (float) $service->getOver();
$price = (float) $service->getPrice();
$commission = (float) $service->getCommission();
$iva = $service->getSageIva()?->getIva() ?? 0;
$totalPrice = ($price * $pax * $units);
$priceCommission = $totalPrice * ($commission / 100);
$totalConComision = $totalPrice + $priceCommission;
$dateInAt = $service->getDateInAt() ?? $reservation->getDateStart();
$dateOutAt = $service->getDateOutAt() ?? $reservation->getDateEnd();
$days = 1;
if ($dateInAt && $dateOutAt) {
$interval = $dateInAt->diff($dateOutAt);
$days = max(1, (int) $interval->days + 1);
}
$totalConDays = $totalConComision * $days;
$totalConOver = $totalConDays + $priceOver;
$priceIva = $totalConOver * ($iva / 100);
$totalPriceCalculated = $totalConOver + $priceIva;
$pricePackage = null;
$serviceName = $service->getName() ?? '';
$assigned = false;
$formattedService = [
'id' => $service->getId(),
'reservationId' => $service->getReservationId(),
'supplierId' => $service->getSupplierId(),
'serviceId' => $service->getServiceId(),
'serviceCatId' => $service->getServiceCatId(),
'serviceCatName' => $service->getServiceCatName(),
'name' => $service->getName(),
'price' => $service->getPrice(),
'pricePackage' => $pricePackage,
'currency' => $service->getCurrency(),
'units' => $units,
'opCommission' => $service->getOpCommission(),
'commission' => $service->getCommission(),
'opOver' => $service->getOpOver(),
'priceOver' => $priceOver,
'opIva' => $service->getOpIva(),
'iva' => $service->getSageIva(),
'pax' => $pax,
'hour' => $service->getHour(),
'dateInAt' => $dateInAt?->format('Y-m-d H:i:s'),
'dateOutAt' => $dateOutAt?->format('Y-m-d H:i:s'),
'contcolor' => $service->getContcolor(),
'rank' => $service->getRank(),
'assistantId' => $service->getAssistantId(),
'activityId' => $service->getActivityId(),
'pay' => $service->getPay(),
'createdAt' => $service->getCreatedAt()?->format('Y-m-d H:i:s'),
'createdId' => $service->getCreatedId(),
'updatedAt' => $service->getUpdatedAt()?->format('Y-m-d H:i:s'),
'updatedId' => $service->getUpdatedId(),
'toInvoice' => $service->getToinvoice(),
'totalSinIva' => $totalConOver,
'totalIva' => $priceIva,
'totalPrice' => $totalPriceCalculated,
'viewInfo' => $service->getViewInfo()
];
if (str_contains($serviceName, '[Pack:')) {
if (preg_match('/\[Pack:\s*(.*?)\]/', $serviceName, $matches)) {
$extractedPackName = trim($matches[1]);
if (isset($packNamesToDetailIdsMap[$extractedPackName])) {
$associatedPackId = $packNameToIdMap[$extractedPackName] ?? null;
$productId = (int) $service->getServiceId();
if ($associatedPackId !== null && isset($packageItemPricesMap[$associatedPackId][$productId])) {
$formattedService['pricePackage'] = $packageItemPricesMap[$associatedPackId][$productId];
}
$servicesGroupedByLoungeDetailId[$extractedPackName][] = $formattedService;
$assigned = true;
}
}
}
if (!$assigned) {
$globalRecommendedServices[] = $formattedService;
}
$catNameNormalized = mb_strtolower(trim($service->getServiceCatName() ?? ''));
if (in_array($catNameNormalized, $allowedCategories, true)) {
$specialServices[] = $formattedService;
}
}
// CONSTRUCCIÓN DE OPCIONES CON FECHAS E ÍTEMS INYECTADOS
foreach ($loungeItems as $item) {
$data['reservation']['idWebLanguage'] = $item->getLanguage() ?: 1;
$data['reservation']['showPackagePrice'] = $reservation->getShowPackagePrice() ?: 1;
$uniqueId = $item->getId();
$rankQuote = $item->getRankQuote();
$loungeIva = $item->getSageIva() ?? 21;
$loungeDateFormatted = $item->getDateStart() ? $item->getDateStart()->format('d/m/Y') : '';
$loungeData = $this->formatLoungeData($item, $uniqueId, $loungeIva);
$loungeDetailId = $loungeData['loungeId'] ? (int) $loungeData['loungeId'] : null;
$rawPackages = ($loungeDetailId && isset($packagesByLoungeDetail[$loungeDetailId]))
? $packagesByLoungeDetail[$loungeDetailId]
: [];
$formattedPackagesForThisLounge = [];
foreach ($rawPackages as $pkg) {
$packNameTrimmed = trim($pkg['name']);
$pkg['date'] = $loungeDateFormatted;
if (isset($packageSelectionsByLoungeId[$uniqueId][$packNameTrimmed])) {
$pkg['isChecked'] = true;
$pkg['quantity'] = $packageSelectionsByLoungeId[$uniqueId][$packNameTrimmed];
} else {
$pkg['isChecked'] = false;
$pkg['quantity'] = 0;
}
foreach ($pkg['items'] as &$subItem) {
$productId = (int)$subItem['productId'];
$subItem['date'] = $loungeDateFormatted;
if (isset($selectionsByLoungeId[$uniqueId][$productId])) {
$subItem['isChecked'] = true;
$subItem['quantity'] = $selectionsByLoungeId[$uniqueId][$productId];
} else {
$subItem['isChecked'] = false;
$subItem['quantity'] = 0;
}
}
unset($subItem);
$formattedPackagesForThisLounge[] = $pkg;
}
$loungeData['avPackages'] = $formattedPackagesForThisLounge;
$loungeData['loungeServices'] = [];
if ($loungeDetailId && isset($packagesByLoungeDetail[$loungeDetailId])) {
foreach ($packagesByLoungeDetail[$loungeDetailId] as $allowedPack) {
$allowedPackName = trim($allowedPack['name']);
if (isset($servicesGroupedByLoungeDetailId[$allowedPackName])) {
foreach ($servicesGroupedByLoungeDetailId[$allowedPackName] as $purchasedService) {
$loungeData['loungeServices'][] = $purchasedService;
}
}
}
}
if (!isset($data['options'][$rankQuote])) {
$data['options'][$rankQuote] = [];
}
$data['options'][$rankQuote][] = $loungeData;
}
$data['reservationService'] = $globalRecommendedServices;
$data['filteredServices'] = $specialServices;
return new JsonResponse($data);
}
private function formatPackageTemplate(AvePackageTemplate $template, array $items): array
{
$totalPrice = $template->getPricePackage() !== null
? (float) $template->getPricePackage()
: (float) $template->getTotalNetPrice();
return [
'id' => $template->getId(),
'name' => $template->getName(),
'description' => $template->getDescription(),
'totalPrice' => $totalPrice,
'isFeatured' => $template->isFeatured(),
'lounges' => array_map(fn($lounge) => [
'id' => $lounge->getId(),
'name' => $lounge->getName()
], $template->getLounges()->toArray()),
'items' => array_map(fn(AvePackageTemplateItems $item) => [
'id' => $item->getId(),
'productName' => $item->getProductName(),
'productId' => $item->getProductId(),
'percProductPrice' => $item->getPercProductPrice(),
'servicePrice' => $item->getServicePrice(),
'priceWithoutPack' => $item->getPriceWithoutPack(),
'rankAvPack' => $item->getRankAvPack(),
'description' => $item->getDescription(),
], $items),
];
}
private function formatReservation(Reservation $reservation): array
{
$contactEmail = null;
$clientContact = $reservation->getClientContact();
if ($clientContact instanceof ClientContact) {
$contactEmail = $clientContact->getEmail();
$clientContactId = $clientContact->getId();
} elseif (is_numeric($clientContact)) {
$contact = $this->em->getRepository(ClientContact::class)->find((int) $clientContact);
$contactEmail = $contact ? $contact->getEmail() : null;
$clientContactId = $contact ? $contact->getId() : null;
} else {
$contactEmail = $reservation->getContactUnregistered();
$clientContactId = $clientContact;
}
$client = $reservation->getClient();
$clientBlock = $client ? [
'id' => method_exists($client, 'getId') ? $client->getId() : null,
'name' => method_exists($client, 'getName') ? $client->getName() : null,
] : null;
$idProposal = $this->resolveProposalId($reservation) ?? $reservation->getId();
return [
'id' => $reservation->getId(),
'title' => $reservation->getTitle(),
'client' => $clientBlock,
'createdAt' => $reservation->getCreatedAt()?->format('Y-m-d H:i:s'),
'priority' => $reservation->getPriority(),
'dateStart' => $reservation->getDateStart()?->format('Y-m-d H:i:s'),
'dateEnd' => $reservation->getDateEnd()?->format('Y-m-d H:i:s'),
'createdBy' => $reservation->getCreatedBy(),
'supplier' => $reservation->getSupplier(),
'status' => $reservation->getStatus(),
'updatedAt' => $reservation->getUpdatedAt()?->format('Y-m-d H:i:s'),
'updatedBy' => $reservation->getUpdatedBy(),
'daysBlock' => $reservation->getDaysBlock(),
'idProposal' => $idProposal,
'pax' => $reservation->getPax(),
'accessKey' => $reservation->getAccessKey(),
'description' => $reservation->getDescription(),
'clientContact' => $clientContactId,
'contactUnregistered' => $reservation->getContactUnregistered(),
'days' => $reservation->getDays(),
'contract' => $reservation->getContract(),
'nameContactUnregistered' => $reservation->getNameContactUnregistered(),
'phoneContactUnregistered' => $reservation->getPhoneContactUnregistered(),
'token' => $reservation->getToken(),
'contactEmail' => $contactEmail,
];
}
private function formatLoungeData(ReservationLoungeSimple $item, $uniqueId, $loungeIva): array
{
$loungeName = $item->getLoungeName();
/** @var ReservationLoungeDetails|null $detail */
$detail = $this->em->getRepository(ReservationLoungeDetails::class)->findOneBy(['name' => $loungeName]);
if (!$detail) {
$dateEnd = $item->getDateEnd();
$xdateEnd = $dateEnd
? ($dateEnd->format('H:i') === '23:59' ? $dateEnd->format('Y-m-d 00:00:00') : $dateEnd->format('Y-m-d H:i:s'))
: null;
return [
'id' => $uniqueId,
'loungeId' => null,
'loungeName' => $loungeName,
'type' => $item->getType(),
'rankQuote' => $item->getRankQuote(),
'dateStart' => $item->getDateStart()?->format('Y-m-d H:i:s'),
'dateEnd' => $xdateEnd,
'pax' => $item->getPax(),
'price' => $item->getServicePrice(),
'loungeIva' => 0,
'combo' => null,
'isCombo' => 0,
'descriptions' => [],
'loungeDescription' => $item->getLoungeDescription(),
'importantDescription' => empty($item->getType()) ? $item->getImportantDescription() : '',
'importantDescGeneralText' => $item->getImportantDescGeneralText(),
'importantDescSchedules' => $item->getImportantDescSchedules(),
'importantDescParking' => $item->getImportantDescParking(),
'showAudiovisualPrice' => $item->isShowAudiovisualPrice(),
'showAudiovisualPackage' => $item->isShowAudiovisualPackage(),
'pictures' => [],
'comboDetails' => [],
'loungeDetails' => null,
'totalPrice' => $item->getServicePrice()
];
}
$ivaPct = is_numeric($loungeIva) ? (float) $loungeIva : 21.0;
$ivaImporte = $item->getServicePrice() * ($ivaPct / 100);
$comboIds = $detail->getCombo();
return [
'id' => $uniqueId,
'loungeId' => $detail->getId(),
'loungeName' => $detail->getName(),
'type' => $item->getType(),
'rankQuote' => $item->getRankQuote(),
'dateStart' => $item->getDateStart()?->format('Y-m-d H:i:s'),
'dateEnd' => $item->getDateEnd()?->format('Y-m-d H:i:s'),
'pax' => $item->getPax(),
'price' => $item->getServicePrice(),
'loungeIva' => $ivaImporte,
'combo' => $comboIds,
'isCombo' => $comboIds ? 1 : 0,
'descriptions' => $this->getDescriptions($detail),
'loungeDescription' => $item->getLoungeDescription(),
'importantDescription' => empty($item->getType()) ? $item->getImportantDescription() : '',
'importantDescGeneralText' => $item->getImportantDescGeneralText(),
'importantDescSchedules' => $item->getImportantDescSchedules(),
'importantDescParking' => $item->getImportantDescParking(),
'showAudiovisualPrice' => $item->isShowAudiovisualPrice(),
'showAudiovisualPackage' => $item->isShowAudiovisualPackage(),
'pictures' => $this->getPictures($detail),
'comboDetails' => $comboIds ? $this->getComboDetails($comboIds) : [],
'loungeDetails' => $this->getLoungeDetails($detail->getId()),
'totalPrice' => $item->getServicePrice() + $ivaImporte
];
}
private function getDescriptions(ReservationLoungeDetails $loungeDetail): array
{
$descriptions = [];
$result = $this->em->getRepository(ReservationLoungeDescription::class)->findBy(['loungeId' => $loungeDetail->getId()]);
foreach ($result as $description) {
$descriptions[] = [
'language' => $description->getLanguage(),
'description' => $description->getDescription(),
'createdAt' => $description->getCreatedAt()?->format('Y-m-d H:i:s'),
'updatedAt' => $description->getUpdatedAt()?->format('Y-m-d H:i:s'),
];
}
return $descriptions;
}
private function getPictures(ReservationLoungeDetails $loungeDetail): array
{
$pictures = [];
$result = $this->em->getRepository(ReservationLoungePicture::class)->findBy(['loungeId' => $loungeDetail->getId()]);
foreach ($result as $picture) {
$pictures[] = [
'id' => $picture->getId(),
'title' => $picture->getTitle(),
'imageLarge' => $picture->getImageLarge(),
'imageMedium' => $picture->getImageMedium(),
'imageSmall' => $picture->getImageSmall(),
'createdAt' => $picture->getCreatedAt()?->format('Y-m-d H:i:s'),
'updatedAt' => $picture->getUpdatedAt()?->format('Y-m-d H:i:s'),
];
}
return $pictures;
}
private function getComboDetails(string $comboIds): array
{
$comboDetails = [];
$comboIdArray = explode(',', $comboIds);
foreach ($comboIdArray as $comboId) {
$comboDetail = $this->em->getRepository(ReservationLoungeDetails::class)->find($comboId);
if ($comboDetail) {
$comboDetails[] = [
'id' => $comboDetail->getId(),
'loungeName' => $comboDetail->getName(),
'meters' => $comboDetail->getMeters(),
'length' => $comboDetail->getLength(),
'width' => $comboDetail->getWidth(),
'height' => $comboDetail->getHeight(),
'capSchool' => $comboDetail->getCapSchool(),
'capTheater' => $comboDetail->getCapTheater(),
'capCocktail' => $comboDetail->getCapCocktail(),
'capBanquet' => $comboDetail->getCapBanquet(),
'capImperial' => $comboDetail->getCapImperial(),
'rankLounge' => $comboDetail->getRankLounge(),
'descriptions' => $this->getDescriptions($comboDetail),
'pictures' => $this->getPictures($comboDetail),
];
}
}
return $comboDetails;
}
private function getLoungeDetails(int $id): ?array
{
$loungeDetail = $this->em->getRepository(ReservationLoungeDetails::class)->findOneBy(['id' => $id]);
if (!$loungeDetail) {
return null;
}
return [
'id' => $loungeDetail->getId(),
'name' => $loungeDetail->getName(),
'meters' => $loungeDetail->getMeters(),
'length' => $loungeDetail->getLength(),
'width' => $loungeDetail->getWidth(),
'height' => $loungeDetail->getHeight(),
'capSchool' => $loungeDetail->getCapSchool(),
'capTheater' => $loungeDetail->getCapTheater(),
'capCocktail' => $loungeDetail->getCapCocktail(),
'capBanquet' => $loungeDetail->getCapBanquet(),
'capImperial' => $loungeDetail->getCapImperial(),
'rankLounge' => $loungeDetail->getRankLounge(),
'createdAt' => $loungeDetail->getCreatedAt()?->format('Y-m-d H:i:s'),
'updatedAt' => $loungeDetail->getUpdatedAt()?->format('Y-m-d H:i:s'),
];
}
private function resolveProposalId(Reservation $reservation): ?int
{
if (method_exists($reservation, 'getProposal')) {
$proposal = $reservation->getProposal();
if (is_object($proposal) && method_exists($proposal, 'getId')) {
return $proposal->getId();
}
}
foreach (['getIdProposal', 'getProposalId'] as $m) {
if (method_exists($reservation, $m)) {
$val = $reservation->$m();
return is_numeric($val) ? (int) $val : null;
}
}
return null;
}
}