<?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\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\Routing\Annotation\Route;
class ApiProposalsClientController extends AbstractController
{
// PHP 8.1: Promoción de propiedades en constructor y readonly para servicios inmutables
public function __construct(
private readonly EntityManagerInterface $em
) {}
/**
* @Route("/api/client/proposal/{token}", name="generate_token", methods={"GET"})
*/
public function generateToken(Request $request, string $token): JsonResponse
{
// Obtener reserva asociada al token
/** @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.');
}
$data = [
'reservation' => $this->formatReservation($reservation),
'options' => [],
'reservationService' => [],
];
// --- CARGA DE PAQUETES OPTIMIZADA (Array de colecciones) ---
$avPackages = $this->em->getRepository(AvePackageTemplate::class)->findAll();
$packagesByLounge = [];
foreach ($avPackages as $pkg) {
if (!$pkg->getLounges()->isInitialized()) {
$pkg->getLounges()->initialize();
}
$formattedPackage = $this->formatPackageTemplate($pkg);
foreach ($pkg->getLounges() as $lounge) {
$loungeId = $lounge->getId();
if (!isset($packagesByLounge[$loungeId])) {
$packagesByLounge[$loungeId] = [];
}
// Corregido: acumulamos en un array para permitir múltiples paquetes por salón
$packagesByLounge[$loungeId][] = $formattedPackage;
}
}
// Agregar servicios de la reserva
$reservationServices = $this->em->getRepository(ReservationService::class)->findBy(['reservationId' => $reservation->getId()]);
foreach ($reservationServices as $service) {
$units = ($service->getUnits() > 0) ? $service->getUnits() : 1;
$pax = ($service->getPax() > 0) ? $service->getPax() : 1;
$priceOver = $service->getOver();
$price = $service->getPrice();
$commission = $service->getCommission();
$iva = $service->getSageIva()?->getIva() ?? 0; // Uso de operador nullsafe de PHP 8.1
$totalPrice = ($price * $pax * $units);
$priceCommission = $totalPrice * ($commission / 100);
$totalConComision = $totalPrice + $priceCommission;
$dateInAt = $service->getDateInAt();
$dateOutAt = $service->getDateOutAt();
$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;
$data['reservationService'][] = [
'id' => $service->getId(),
'reservationId' => $service->getReservationId(),
'supplierId' => $service->getSupplierId(),
'serviceId' => $service->getServiceId(),
'serviceCatId' => $service->getServiceCatId(),
'serviceCatName' => $service->getServiceCatName(),
'name' => $service->getName(),
'price' => $service->getPrice(),
'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' => $service->getDateInAt()?->format('Y-m-d H:i:s'),
'dateOutAt' => $service->getDateOutAt()?->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()
];
}
// Procesar los salones
$loungeItems = $this->em->getRepository(ReservationLoungeSimple::class)->findBy(['idReservation' => $reservation->getId()]);
foreach ($loungeItems as $item) {
$data['reservation']['idWebLanguage'] = $item->getLanguage() ?: 1;
$uniqueId = $item->getId();
$rankQuote = $item->getRankQuote();
$loungeIva = $item->getSageIva();
if (empty($loungeIva) && !is_numeric($loungeIva)) {
$loungeIva = 21;
}
$loungeData = $this->formatLoungeData($item, $uniqueId, $loungeIva);
// --- ASIGNACIÓN DE MULTIPLES PAQUETES ---
// Modificado: Ahora 'avPackages' siempre devuelve un array con todos los elementos mapeados
$loungeId = $loungeData['loungeId'];
$loungeData['avPackages'] = ($loungeId && isset($packagesByLounge[$loungeId]))
? $packagesByLounge[$loungeId]
: [];
if (!isset($data['options'][$rankQuote])) {
$data['options'][$rankQuote] = [];
}
$data['options'][$rankQuote][] = $loungeData;
}
return new JsonResponse($data);
}
/**
* Formato datos packages
*/
private function formatPackageTemplate(AvePackageTemplate $template): array
{
$items = $this->em->getRepository(AvePackageTemplateItems::class)->findBy(['packId' => $template->getId()]);
return [
'id' => $template->getId(),
'name' => $template->getName(),
'description' => $template->getDescription(),
'totalPrice' => $template->getTotalNetPrice(),
'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),
];
}
/**
* Formatea datos de la reserva para el JSON
*/
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(),
'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(),
'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;
}
}