src/Controller/BookingApi/ApiTicketControllerApi.php line 49

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by Elements.at New Media Solutions GmbH
  4.  *
  5.  */
  6. namespace App\Controller\BookingApi;
  7. use App\Model\BookingApi\CartItemTicket;
  8. use App\Model\BookingApi\Exception\DataMappingException;
  9. use App\Model\BookingApi\Exception\DataMissingException;
  10. use App\Model\BookingApi\LocalizedText;
  11. use App\Model\BookingApi\TicketAcquisitionType;
  12. use App\Model\BookingApi\TicketAvailability;
  13. use App\Model\BookingApi\TicketAvailabilityIncludingUpgrades;
  14. use App\Model\BookingApi\TicketCatalogUpgradeRelation;
  15. use App\Model\BookingApi\TicketInsurancePrice;
  16. use App\Model\BookingApi\TicketUpgradePrice;
  17. use App\Model\Shop\Ticket\TicketProduct;
  18. use App\Model\Type\TenantType;
  19. use App\Service\BookingApi\ApiService;
  20. use App\Service\Shop\JsonDataService;
  21. use App\Service\Shop\ShopService;
  22. use App\Service\TicketShopFrameworkBundle\TicketFilter;
  23. use Carbon\Carbon;
  24. use Elements\Bundle\SkidataTicketingSwebBundle\Service\OrderService;
  25. use Elements\Bundle\TicketShopFrameworkBundle\Model\Constant\AcquisitionType;
  26. use Elements\Bundle\TicketShopFrameworkBundle\Model\DataObject\TicketCatalog;
  27. use Elements\Bundle\TicketShopFrameworkBundle\Model\Shop\Ticketing\TicketCatalogAvailability;
  28. use League\Csv\Exception;
  29. use OpenApi\Annotations as OA;
  30. use Pimcore\Model\DataObject\KeycardNumber;
  31. use Pimcore\Model\DataObject\ShopTicketCatalog;
  32. use Pimcore\Model\DataObject\TicketshopTicketAdditional;
  33. use Pimcore\Translation\Translator;
  34. use Symfony\Component\HttpFoundation\JsonResponse;
  35. use Symfony\Component\HttpFoundation\Request;
  36. use Symfony\Component\Routing\Annotation\Route;
  37. use Symfony\Component\Validator\Validator\ValidatorInterface;
  38. /**
  39.  * Class ApiTicketControllerApi
  40.  *
  41.  * @Route("/bookingAPI/ticket")
  42.  *
  43.  * @OA\Tag(name="Ticket")
  44.  */
  45. class ApiTicketControllerApi extends ApiAbstractController
  46. {
  47.     /**
  48.      * Get availability for ShopTicketCatalog and specific date
  49.      *
  50.      * @Route("/availability", name="ticket_availability")
  51.      *
  52.      * @param Request $request
  53.      *
  54.      * @return JsonResponse
  55.      *
  56.      * @OA\Post(
  57.      *      path="/bookingAPI/ticket/availability",
  58.      *     description="Get availability for ShopTicketCatalog and specific date",
  59.      *     tags={"Ticket"},
  60.      *
  61.      *     @OA\RequestBody(
  62.      *      required=true,
  63.      *
  64.      *           @OA\JsonContent(
  65.      *              required={"startDate","catalogId"},
  66.      *
  67.      *              @OA\Property( property="startDate", type="string", description="YYYY-MM-DD"),
  68.      *              @OA\Property( property="endDate", type="string", description="YYYY-MM-DD"),
  69.      *              @OA\Property( property="catalogId", type="int"),
  70.      *              @OA\Property( property="countryCode", type="string", description="required if acquisitionTypesPerConsumerCategory should be included in response (stop sale for delivery is set per country)"),
  71.      *
  72.      *              @OA\Examples(example="sandboxTicketExample", value="{
  73.     ""catalogId"": ""3000188"",
  74.     ""startDate"": ""2024-04-11"",
  75.     ""endDate"": ""2024-04-11"",
  76.     ""countryCode"": ""CH""
  77.     }", summary="SANDBOX ticket availability"),
  78.     @OA\Examples(example="liveTicketExample", value="{
  79.     ""catalogId"": ""1501805"",
  80.     ""startDate"": ""2024-03-30"",
  81.     ""endDate"": ""2024-03-30"",
  82.     ""countryCode"": ""CH""
  83.     }", summary="LIVE ticket availability")
  84.      *           )
  85.      *      ),
  86.      *     @OA\Response(
  87.      *          response=200,
  88.      *          description="Successful operation",
  89.      *
  90.      *          @OA\MediaType(
  91.      *              mediaType="application/json",
  92.      *
  93.      *              @OA\Schema(
  94.      *                  type = "object",
  95.      *
  96.      *                   @OA\Property(property="success", type="boolean"),
  97.      *                   @OA\Property(property="availabilities", type="array",
  98.      *
  99.      *                      @OA\Items(ref="#/components/schemas/TicketAvailability")
  100.      *                   )
  101.      *              )
  102.      *          )
  103.      *      ),
  104.      *
  105.      *     @OA\Response(
  106.      *          response=500,
  107.      *          description="Error",
  108.      *      ),
  109.      *     )
  110.      *
  111.      *
  112.      */
  113.     public function getTicketAvailability(Request $requestApiService $apiServiceShopService $shopHelperJsonDataService $jsonDataService): JsonResponse
  114.     {
  115.         try {
  116.             $body $request->getContent();
  117.             $requestData json_decode($bodytrue);
  118.             $cart $this->getCart();
  119.             $this->clearCartData($cart);
  120.             // only for testing purposes
  121.             if (!$requestData && $request->get('debug')) {
  122.                 $requestData = [
  123.                     'startDate' => '2024-04-20',
  124.                     'endDate' => '2024-04-25',
  125.                     'catalogId' => 1501817,
  126.                     'countryCode' => 'CH',
  127.                 ];
  128.             }
  129.             //check for required params
  130.             if (!is_array($requestData) || !array_key_exists('startDate'$requestData) || empty($requestData['startDate'])) {
  131.                 $this->bookingapiLogger->warning('required startDate for ticket/availability endpoint is missing');
  132.                 return $this->sendErrors(['startDate missing'], 400);
  133.             }
  134.             if (!is_array($requestData) || !array_key_exists('catalogId'$requestData) || empty($requestData['catalogId'])) {
  135.                 $this->bookingapiLogger->warning('required catalogId for ticket/availability endpoint is missing');
  136.                 return $this->sendErrors(['catalogId missing'], 400);
  137.             }
  138.             $from Carbon::createFromFormat('Y-m-d'$requestData['startDate']);
  139.             $from $from->startOfDay();
  140.             if (isset($requestData['endDate']) && $requestData['endDate']) {
  141.                 $to Carbon::createFromFormat('Y-m-d'$requestData['endDate']);
  142.             } else {
  143.                 $to $from->copy();
  144.             }
  145.             $to $to->endOfDay();
  146.             $countryCode = isset($requestData['countryCode']) ? $requestData['countryCode'] : null;
  147.             $catalog TicketCatalog::getById($requestData['catalogId']);
  148.             if (!$catalog) {
  149.                 throw new \Exception('ticketCatalog with id ' $requestData['catalogId'] . 'not found');
  150.             } elseif (!in_array(TenantType::MARKETPLACE$catalog->getTenant())) {
  151.                 $this->bookingapiLogger->warning('call to ticket/availability with catalogId' $requestData['catalogId'] . ' but catalog does not have marketplace tenant');
  152.                 return $this->sendErrors(['ticketCatalog with id ' $requestData['catalogId'] . ' not available for tenant ' TenantType::MARKETPLACE], 400);
  153.             }
  154.             $filter = new TicketFilter($from$to);
  155.             $filter->setExactDuration(true);
  156.             $filter->setUseTicketValidity(true);
  157.             $allowedConsumers = [];
  158.             foreach ($catalog->getTicketConsumerCategories() as $allowedConsumer) {
  159.                 $allowedConsumers[] = $allowedConsumer->getId();
  160.             }
  161.             if ($allowedConsumers) {
  162.                 $filter->setConsumers($allowedConsumers);
  163.             }
  164.             /**
  165.              * @var TicketCatalogAvailability $ticketCatalogAvailability
  166.              */
  167.             $ticketCatalogAvailability $filter->getTicketCatalogAvailability($catalogtrue);
  168.             //insurances
  169.             $insuranceData $shopHelper->getInsurancesForProduct($ticketCatalogAvailability);
  170.             $insurances $this->getInsuranceData($catalog$ticketCatalogAvailability$insuranceData);
  171.             $availabilityData = [];
  172.             foreach ($ticketCatalogAvailability->getTicketAvailability() as $productAvailability) {
  173.                 $ticketProduct $productAvailability->getTicketProduct();
  174.                 //upgrades
  175.                 /** @phpstan-ignore-next-line */
  176.                 $upgrades $this->getUpgradeData($catalog$ticketProduct$from$to);
  177.                 $consumerCategories = [];
  178.                 $consumerCategoryData = [];
  179.                 $acquisitionTypesByConsumerCategory = [];
  180.                 foreach ($productAvailability->getConsumerAvailabilities() as $consumerAvailability) {
  181.                     $priceInfo $consumerAvailability->getPriceInfo();
  182.                     if ($priceInfo) {
  183.                         $consumerCategories[$consumerAvailability->getTicketConsumer()->getId()] = $priceInfo->getPrice()->getGrossAmount()->asNumeric();
  184.                         $consumerCategoryData[] = $consumerAvailability->getTicketConsumer();
  185.                         if ($countryCode) {
  186.                             $possibleAquisitionTypes $apiService->calculatePossibleAcquisitionTypes($countryCode$productAvailability->getValidFrom(), $consumerAvailability->getTicketConsumer(), $productAvailability->getTicketProduct());
  187.                             $acquisitionTypesByConsumerCategory[$consumerAvailability->getTicketConsumer()->getId()] = array_values($possibleAquisitionTypes);
  188.                         }
  189.                     }
  190.                 }
  191.                 $localizedName = new LocalizedText();
  192.                 $localizedInfoBubbleText = new LocalizedText();
  193.                 $localizedCatalogInfoText = new LocalizedText();
  194.                 foreach ($this->languages as $language) {
  195.                     $localizedName->$language $ticketProduct->getName($language);
  196.                     $localizedCatalogInfoText->$language $catalog->getInfoBubbleDescription($language) ?: '';
  197.                     $localizedInfoBubbleText->$language $ticketProduct->getDescription($language) ?: '';
  198.                 }
  199.                 $apiTicketAvailability = new TicketAvailability();
  200.                 $apiTicketAvailability->setTicketId($productAvailability->getTicketProduct()->getId());
  201.                 $apiTicketAvailability->setName($localizedName);
  202.                 $apiTicketAvailability->setInfoText($localizedInfoBubbleText);
  203.                 $apiTicketAvailability->setInfoTextCatalog($localizedCatalogInfoText);
  204.                 $apiTicketAvailability->setConsumerCategories($this->getConsumerCategories($consumerCategoryData));
  205.                 $apiTicketAvailability->setPriceByConsumerCategory($consumerCategories);
  206.                 $apiTicketAvailability->setAcquisitionTypesByConsumerCategory($acquisitionTypesByConsumerCategory);
  207.                 $apiTicketAvailability->setDefaultAcquisitionType($ticketProduct->getDefaultAcquisitionType());
  208.                 $apiTicketAvailability->setPersonalization($productAvailability->getTicketProduct()->getRequirements() ?: []);
  209.                 $apiInsurances = isset($insurances[$productAvailability->getTicketProduct()->getId()]) ? $insurances[$productAvailability->getTicketProduct()->getId()] : [];
  210.                 $apiTicketAvailability->setInsurances(array_values($apiInsurances));
  211.                 $apiTicketAvailability->setUpgrades($upgrades);
  212.                 //delivery prices
  213.                 $deliveryPrices = [];
  214.                 if ($countryCode) {
  215.                     $deliveryPrices[$countryCode] = $apiService->getDeliveryPriceForCountry($countryCode);
  216.                 } else {
  217.                     $deliveryPrices $apiService->getDeliveryPricePerCountry();
  218.                 }
  219.                 $apiTicketAvailability->setDeliveryPricePerCountry($deliveryPrices);
  220.                 $availabilityData[] = $apiTicketAvailability->jsonSerialize();
  221.             }
  222.             if ($request->get('debug')) {
  223.                 p_r($availabilityData);
  224.                 die();
  225.             }
  226.             return new JsonResponse(
  227.                 [
  228.                     'success' => true,
  229.                     'availabilities' => $availabilityData,
  230.                 ]
  231.             );
  232.         } catch (\Throwable $throwable) {
  233.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  234.             return $this->sendErrors([$throwable->getMessage()], 500);
  235.         }
  236.     }
  237.     /**
  238.      * Get all  ticket acquisitionTypes with description
  239.      *
  240.      * @Route("/acquisitionTypes")
  241.      *
  242.      * @param Request $request
  243.      *
  244.      * @return JsonResponse
  245.      *
  246.      * @OA\Post(
  247.      *     path="/bookingAPI/ticket/acquisitionTypes",
  248.      *     description="Get all  ticket acquisitionTypes with description",
  249.      *     tags={"Ticket"},
  250.      *
  251.      *     @OA\RequestBody(
  252.      *      required=false
  253.      *      ),
  254.      *
  255.      *  @OA\Response(
  256.      *          response=200,
  257.      *          description="Successful operation",
  258.      *
  259.      *          @OA\MediaType(
  260.      *              mediaType="application/json",
  261.      *
  262.      *              @OA\Schema(
  263.      *                  type = "object",
  264.      *
  265.      *                      @OA\Property(property="success", type="boolean"),
  266.      *                      @OA\Property(property="acquisitionTypes", type="array",
  267.      *
  268.      *                          @OA\Items(type="object", ref="#/components/schemas/TicketAcquisitionType")
  269.      *                      ),
  270.      *              )
  271.      *          )
  272.      *      ),
  273.      *
  274.      *     @OA\Response(
  275.      *          response=500,
  276.      *          description="Error",
  277.      *      ),
  278.      *
  279.      * )
  280.      */
  281.     public function getAcquisitionTypes(Request $requestShopService $shopHelperTranslator $translator): JsonResponse
  282.     {
  283.         $acquisitionTypes = [AcquisitionType::SWISSSPASSAcquisitionType::BARCODEAcquisitionType::RELOADAcquisitionType::REQUESTAcquisitionType::TICKETPICKUP];
  284.         $returnData = [];
  285.         foreach ($acquisitionTypes as $acquisitionType) {
  286.             $descriptions = new LocalizedText();
  287.             foreach ($this->languages as $language) {
  288.                 $tk "shop.acquisition-type." $acquisitionType ".info-text";
  289.                 $translated $translator->trans($tk, [], null$language);
  290.                 $descriptions->$language $translated != $tk $translated '';
  291.             }
  292.             $apiAcquisitionType = new TicketAcquisitionType();
  293.             $apiAcquisitionType->setAcquisitionType($acquisitionType);
  294.             $apiAcquisitionType->setTexts($descriptions);
  295.             $returnData[] = $apiAcquisitionType;
  296.         }
  297.         return new JsonResponse(
  298.             [
  299.                 'success' => true,
  300.                 'acquisitionTypes' => $returnData,
  301.             ]
  302.         );
  303.     }
  304.     /**
  305.      * Get all possible insurances for a specific ShopTicketCatalog
  306.      *
  307.      * @Route("/insurances")
  308.      *
  309.      * @param Request $request
  310.      *
  311.      * @return JsonResponse
  312.      *
  313.      * @OA\Post(
  314.      *     path="/bookingAPI/ticket/insurances",
  315.      *     description="Get all possible insurances for a specific ShopTicketCatalog",
  316.      *     tags={"Ticket"},
  317.      *
  318.      *     @OA\RequestBody(
  319.      *      required=true,
  320.      *
  321.      *           @OA\JsonContent(
  322.      *              required={"catalogId","ticketId"},
  323.      *
  324.      *              @OA\Property( property="catalogId", type="int", example=1501805),
  325.      *              @OA\Property( property="ticketId", type="int", example=518928)
  326.      *           )
  327.      *      ),
  328.      *
  329.      *  @OA\Response(
  330.      *          response=200,
  331.      *          description="Successful operation",
  332.      *
  333.      *          @OA\MediaType(
  334.      *              mediaType="application/json",
  335.      *
  336.      *              @OA\Schema(
  337.      *                  type = "object",
  338.      *
  339.      *                      @OA\Property(property="success", type="boolean"),
  340.      *                      @OA\Property(property="insurances", type="array",
  341.      *
  342.      *                          @OA\Items(type="object", ref="#/components/schemas/TicketInsurancePrice")
  343.      *                      ),
  344.      *              )
  345.      *          )
  346.      *      ),
  347.      *
  348.      *     @OA\Response(
  349.      *          response=500,
  350.      *          description="Error",
  351.      *      ),
  352.      *
  353.      * )
  354.      */
  355.     public function getInsurances(Request $requestShopService $shopHelper): JsonResponse
  356.     {
  357.         try {
  358.             $body $request->getContent();
  359.             $requestData json_decode($bodytrue);
  360.             // only for testing purposes
  361.             if (!$requestData && $request->get('debug')) {
  362.                 $requestData['catalogId'] = 1501805;
  363.                 $requestData['ticketId'] = 518928;
  364.             }
  365.             //check for required params
  366.             $required = ['ticketId''catalogId'];
  367.             foreach ($required as $key) {
  368.                 if (!$requestData || !array_key_exists($key$requestData) || !$requestData[$key]) {
  369.                     $this->bookingapiLogger->warning('required data for ticket/insurances endpoint ist missing. missing ' $key);
  370.                     return $this->sendErrors([$key ' missing'], 400);
  371.                 }
  372.             }
  373.             $catalog TicketCatalog::getById($requestData['catalogId']);
  374.             if (!$catalog) {
  375.                 throw new \Exception('ticketCatalog with id ' $request['catalogId'] . 'not found');
  376.             }
  377.             $filter = new TicketFilter(Carbon::now());
  378.             $filter->setExactDuration(false);
  379.             $filter->setUseTicketValidity(true);
  380.             $allowedConsumers = [];
  381.             foreach ($catalog->getTicketConsumerCategories() as $allowedConsumer) {
  382.                 $allowedConsumers[] = $allowedConsumer->getId();
  383.             }
  384.             if ($allowedConsumers) {
  385.                 $filter->setConsumers($allowedConsumers);
  386.             }
  387.             $ticketCatalogAvailability $filter->getTicketCatalogAvailability($catalogtrue);
  388.             //insurances
  389.             $insuranceData $shopHelper->getInsurancesForProduct($ticketCatalogAvailability);
  390.             $insurances $this->getInsuranceData($catalog$ticketCatalogAvailability$insuranceData);
  391.             $returnData = [];
  392.             if (isset($insurances[$requestData['ticketId']])) {
  393.                 foreach ($insurances[$requestData['ticketId']] as $ticketInsurancePrice) {
  394.                     $returnData[] = $ticketInsurancePrice->jsonSerialize();
  395.                 }
  396.             }
  397.             if ($request->get('debug')) {
  398.                 p_r($returnData);
  399.                 die();
  400.             }
  401.             return new JsonResponse(
  402.                 [
  403.                     'success' => true,
  404.                     'insurances' => $returnData,
  405.                 ]
  406.             );
  407.         } catch (\Throwable $throwable) {
  408.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  409.             return $this->sendErrors([$throwable->getMessage()], 500);
  410.         }
  411.     }
  412.     /**
  413.      * Keycard validation
  414.      *
  415.      * @Route("/validateKeycard")
  416.      *
  417.      * @param Request $request
  418.      *
  419.      * @return JsonResponse
  420.      *
  421.      * @OA\Post(
  422.      *      path="/bookingAPI/ticket/validateKeycard",
  423.      *     description="validates key card number; if a short number is given, mapping to a skidata key card number is attempted",
  424.      *     tags={"Ticket"},
  425.      *
  426.      *      @OA\RequestBody(
  427.      *      required=true,
  428.      *
  429.      *           @OA\JsonContent(
  430.      *              required={"keycardNumber"},
  431.      *
  432.      *              @OA\Property( property="keycardNumber", type="string", example="01-11111111111111111111-1 or U111111"),
  433.      *           )
  434.      *      ),
  435.      *
  436.      *     @OA\Response(
  437.      *          response=200,
  438.      *          description="Successful operation",
  439.      *
  440.      *          @OA\MediaType(
  441.      *              mediaType="application/json",
  442.      *
  443.      *              @OA\Schema(
  444.      *                  type = "object",
  445.      *
  446.      *                  @OA\Property(property="success", type="boolean"),
  447.      *                  @OA\Property(property="keycardNumber", type="string"),
  448.      *                  @OA\Property(property="keycardShortNumber", type="string"),
  449.      *                  @OA\Property(property="valid", type="boolean"),
  450.      *              )
  451.      *          )
  452.      *
  453.      *     ),
  454.      *
  455.      *     @OA\Response(
  456.      *          response=500,
  457.      *          description="Error"
  458.      *     ),
  459.      *      @OA\Response(
  460.      *          response=400,
  461.      *          description="Invalid request - data is missing."
  462.      *     )
  463.      * )
  464.      */
  465.     public function validateKeycard(Request $requestApiService $apiServiceOrderService $orderServiceValidatorInterface $validator): JsonResponse
  466.     {
  467.         try {
  468.             $body $request->getContent();
  469.             $requestData json_decode($bodytrue);
  470.             // only for testing purposes
  471.             if (!$requestData && $request->get('debug')) {
  472.                 $requestData = [
  473.                     'keycardNumber' => '01-16147133534995632168-4',
  474.                 ];
  475.             }
  476.             // check for required params
  477.             if (!$requestData || !array_key_exists('keycardNumber'$requestData) || !$requestData['keycardNumber']) {
  478.                 $this->bookingapiLogger->warning('required keycardNumber for ticket/validateKeycard endpoint is missing');
  479.                 return $this->sendErrors(['keycardNumber missing'], 400);
  480.             }
  481.             $keycardNr $requestData['keycardNumber'];
  482.             $keycardNumberObj KeycardNumber::getByShortNumber($keycardNr1);
  483.             if (strpos($keycardNr'-') === false) {
  484.                 //this is a shortNumber - try to map
  485.                 if ($keycardNumberObj && $keycardNumberObj->getNumber()) {
  486.                     $keycardNr $keycardNumberObj->getNumber();
  487.                 }
  488.             }
  489.             $valid $apiService->validateKeycard($orderService$validator$keycardNr);
  490.             return new JsonResponse(
  491.                 [
  492.                     'success' => true,
  493.                     'keycardNumber' => $keycardNr,
  494.                     'keycardShortNumber' => $keycardNumberObj strval($keycardNumberObj->getShortNumber()) : '',
  495.                     'valid' => $valid,
  496.                 ]
  497.             );
  498.         } catch (\Throwable $throwable) {
  499.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  500.             return $this->sendErrors([$throwable->getMessage()], 500);
  501.         }
  502.     }
  503.     /**
  504.      * Swisspass validation
  505.      *
  506.      * @Route("/validateSwisspass")
  507.      *
  508.      * @param Request $request
  509.      *
  510.      * @return JsonResponse
  511.      *
  512.      * @OA\Post(
  513.      *      path="/bookingAPI/ticket/validateSwisspass",
  514.      *     description="validates Swisspass number",
  515.      *     tags={"Ticket"},
  516.      *
  517.      *      @OA\RequestBody(
  518.      *      required=true,
  519.      *
  520.      *           @OA\JsonContent(
  521.      *              required={"swisspassNumber","zip"},
  522.      *
  523.      *              @OA\Property( property="swisspassNumber", type="string", example="S42682117607"),
  524.      *              @OA\Property( property="zip", type="string", example="3048")
  525.      *           )
  526.      *      ),
  527.      *
  528.      *     @OA\Response(
  529.      *          response=200,
  530.      *          description="Successful operation",
  531.      *
  532.      *          @OA\MediaType(
  533.      *              mediaType="application/json",
  534.      *
  535.      *              @OA\Schema(
  536.      *                  type = "object",
  537.      *
  538.      *                  @OA\Property(property="success", type="boolean"),
  539.      *                  @OA\Property(property="valid", type="boolean"),
  540.      *              )
  541.      *          )
  542.      *
  543.      *     ),
  544.      *
  545.      *     @OA\Response(
  546.      *          response=500,
  547.      *          description="Error"
  548.      *     ),
  549.      *      @OA\Response(
  550.      *          response=400,
  551.      *          description="Invalid request - data is missing."
  552.      *     )
  553.      * )
  554.      */
  555.     public function validateSwisspass(Request $requestApiService $apiServiceOrderService $orderServiceValidatorInterface $validator): JsonResponse
  556.     {
  557.         try {
  558.             $body $request->getContent();
  559.             $requestData json_decode($bodytrue);
  560.             // only for testing purposes
  561.             if (!$requestData && $request->get('debug')) {
  562.                 $requestData = [
  563.                     'swisspassNumber' => 'S42682117607',
  564.                     'zip' => '3048',
  565.                 ];
  566.             }
  567.             // check for required params
  568.             if (!is_array($requestData) || !array_key_exists('swisspassNumber'$requestData) || empty($requestData['swisspassNumber'])) {
  569.                 $this->bookingapiLogger->warning('required swisspassNumber for ticket/validateSwisspass endpoint is missing');
  570.                 return $this->sendErrors(['swisspassNumber missing'], 400);
  571.             }
  572.             if (!is_array($requestData) || !array_key_exists('zip'$requestData) || empty($requestData['zip'])) {
  573.                 $this->bookingapiLogger->warning('required zip for ticket/validateSwisspass endpoint is missing');
  574.                 return $this->sendErrors(['zip missing'], 400);
  575.             }
  576.             $swisspassNumber $requestData['swisspassNumber'];
  577.             $zip $requestData['zip'];
  578.             try {
  579.                 $valid $apiService->validateSwisspass($swisspassNumber$zip);
  580.                 $success true;
  581.             } catch (\Throwable $throwable) {
  582.                 $valid false;
  583.                 $success false;
  584.             }
  585.             return new JsonResponse(
  586.                 [
  587.                     'success' => $success,
  588.                     'valid' => $valid,
  589.                 ]
  590.             );
  591.         } catch (\Throwable $throwable) {
  592.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  593.             return $this->sendErrors([$throwable->getMessage()], 500);
  594.         }
  595.     }
  596.     /**
  597.      * Validate ticket booking data before submitting the order (optional step)
  598.      *
  599.      * @Route("/validateData")
  600.      *
  601.      * @OA\Post(
  602.      *     path="/bookingAPI/ticket/validateData",
  603.      *     description="Validate ticket item booking data before submitting the order (optional step)",
  604.      *     tags={"Ticket"},
  605.      *
  606.      *      @OA\RequestBody(
  607.      *          required=true,
  608.      *
  609.      *           @OA\JsonContent(
  610.      *            required={"ticket"},
  611.      *
  612.      *            @OA\Property(property="tickets", type="array", @OA\Items(type="object", ref="#/components/schemas/CartItemTicket")),
  613.      *
  614.      *            @OA\Examples(example="ticketExample", value="{""tickets"": [{
  615.     ""price"": 10,
  616.     ""ticketId"": 518928,
  617.     ""catalogId"": 1501805,
  618.     ""consumerCategoryId"": 502338,
  619.     ""date"": ""2024-11-30"",
  620.     ""deliveryType"": ""reload"",
  621.     ""deliveryCountry"": """",
  622.     ""firstName"": ""John"",
  623.     ""lastName"": ""Doe"",
  624.     ""birthday"": ""2001-01-01"",
  625.     ""keycardNumber"": ""01-16147133534995632168-4"",
  626.     ""keycardShortnumber"": """",
  627.     ""swisspassNumber"": """",
  628.     ""swisspassZip"": """",
  629.     ""insuranceId"": 518668
  630.     }]}", summary="a valid example for ticket validation")
  631.      *           )
  632.      *      ),
  633.      *     @OA\Response(
  634.      *          response=200,
  635.      *          description="Successful operation",
  636.      *
  637.      *          @OA\MediaType(
  638.      *              mediaType="application/json",
  639.      *
  640.      *              @OA\Schema(
  641.      *                  type = "object",
  642.      *
  643.      *                  @OA\Property(property="success", type="boolean"),
  644.      *                  @OA\Property(property="valid", type="boolean"),
  645.      *                  @OA\Property(property="errors", type="array",
  646.      *
  647.      *                       @OA\Items(type="string", example="keycardNumber missing")
  648.      *                  ),
  649.      *              )
  650.      *          )
  651.      *
  652.      *     ),
  653.      *
  654.      *      @OA\Response(
  655.      *          response=500,
  656.      *          description="Error"
  657.      *     ),
  658.      *      @OA\Response(
  659.      *          response=400,
  660.      *          description="Invalid request - data is missing."
  661.      *     )
  662.      * )
  663.      *
  664.      * @param Request $request
  665.      *
  666.      * @return JsonResponse
  667.      */
  668.     public function validateTicketData(Request $requestApiService $apiServiceOrderService $orderServiceShopService $shopHelperValidatorInterface $validator): JsonResponse
  669.     {
  670.         try {
  671.             $body $request->getContent();
  672.             $requestData json_decode($bodytrue);
  673.             // only for testing purposes
  674.             if (!$requestData && $request->get('debug')) {
  675.                 $requestData = ['tickets' => [[
  676.                     'ticketId' => 518928,
  677.                     'catalogId' => 1501805,
  678.                     'consumerCategoryId' => 502338,
  679.                     'date' => '2024-11-04',
  680. //                    "profilePicture" =>  "string", // TODO TBD
  681.                     'deliveryType' => 'reload',
  682.                     'firstName' => 'Michaela',
  683.                     'lastName' => 'Steyrer',
  684.                     'birthday' => '2001-08-16',
  685.                     //'keycardNumber' =>  '01-16147133534995632168-4',
  686.                     "keycardShortnumber" => "U123123",
  687.                     //'insuranceId' => 518668,
  688.                     'deliveryCountry' => 'CH',
  689.                 ]]];
  690.             }
  691.             if (!isset($requestData['tickets'])) {
  692.                 $this->bookingapiLogger->warning('required array tickets for ticket/validateData endpoint is missing');
  693.                 return $this->sendErrors(['ticket data missing'], 400);
  694.             }
  695.             $errors = [];
  696.             try {
  697.                 foreach($requestData['tickets'] as $index => $ticketData) {
  698.                     $cartItemTicket = new CartItemTicket();
  699.                     $cartItemTicket->unmarshall($ticketData);
  700.                     $keycardCheck $this->keyCardNumberShortToLongCheck($cartItemTicket);
  701.                     if (!$keycardCheck) {
  702.                         return $this->sendErrors(['could not map keycard short number ' $cartItemTicket->getKeycardShortnumber()], 500);
  703.                     }
  704.                     $errors array_merge($errors$apiService->validateTicketData($shopHelper$orderService$validator$cartItemTicket'ticket '.($index+1).": "));
  705.                 }
  706.             } catch (DataMappingException $e) {
  707.                 $errors[] = $e->getMessage();
  708.             } catch (DataMissingException $e) {
  709.                 $this->bookingapiLogger->warning('required data for ticket/validateData endpoint is missing:' $e->getMessage());
  710.                 return $this->sendErrors([$e->getMessage()], 400);
  711.             }
  712.             return new JsonResponse(
  713.                 [
  714.                     'success' => true,
  715.                     'valid' => empty($errors),
  716.                     'errors' => $errors,
  717.                 ]
  718.             );
  719.         } catch (\Throwable $throwable) {
  720.             $this->bookingapiLogger->error($throwable->getMessage() . 'Stack Trace: ' $throwable->getTraceAsString());
  721.             return $this->sendErrors([$throwable->getMessage()], 500);
  722.         }
  723.     }
  724.     /**
  725.      * @param TicketCatalog $catalog
  726.      * @param TicketCatalogAvailability $ticketCatalogAvailability
  727.      * @param array<mixed> $insuranceData
  728.      *
  729.      * @return array<int,mixed>
  730.      *
  731.      * @throws \Exception
  732.      */
  733.     protected function getInsuranceData(TicketCatalog $catalogTicketCatalogAvailability $ticketCatalogAvailability, array $insuranceData): array
  734.     {
  735.         $insurances = [];
  736.         if (isset($insuranceData['insurances'])) {
  737.             foreach ($insuranceData['insurances'] as $ticketId => $insuranceProducts) {
  738.                 foreach ($insuranceProducts as $insuranceProduct) {
  739.                     /**
  740.                      * @var TicketProduct $insuranceProduct
  741.                      */
  742.                     $insuranceMetaListing = new TicketshopTicketAdditional\Listing();
  743.                     $insuranceMetaListing->setCondition("o_id in (select src_id from object_relations_TSF_Additional where dest_id = " $insuranceProduct->getId() . ")");
  744.                     $metaListings $insuranceMetaListing->load();
  745.                     $descriptionLocalized = new LocalizedText();
  746.                     $nameLocalized = new LocalizedText();
  747.                     $groupNameLocalized = new LocalizedText();
  748.                     $groupId 0;
  749.                     foreach ($this->languages as $language) {
  750.                         if (count($metaListings) > 0) {
  751.                             $descriptionLocalized->$language $metaListings[0]->getDescription($language) ?: '';
  752.                             $groupNameLocalized->$language $metaListings[0]->getName($language) ?: '';
  753.                             $groupId $metaListings[0]->getId();
  754.                         } else {
  755.                             $descriptionLocalized->$language '';
  756.                             $groupNameLocalized->$language '';
  757.                         }
  758.                         $nameLocalized->$language $insuranceProduct->getName($language) ?: '';
  759.                     }
  760.                     foreach ($ticketCatalogAvailability->getTicketAvailability() as $ticketAvailability) {
  761.                         if ($ticketAvailability->getTicketProduct()->getId() == $ticketId) {
  762.                             $insurancePricesPerConsumer = [];
  763.                             $ticketArticleId null;
  764.                             foreach ($ticketAvailability->getSortedConsumerAvailabilities() as $consumerAvailability) {
  765.                                 $insurancePriceInfo $insuranceProduct->getOSPriceInfo(1, [],
  766.                                     $ticketAvailability->getValidFrom(),
  767.                                     $consumerAvailability->getTicketConsumer(), catalog$catalog);
  768.                                 $insurancePrice $insurancePriceInfo->getTotalPrice()->getGrossAmount()->asNumeric();
  769.                                 $insurancePricesPerConsumer[$consumerAvailability->getTicketConsumer()->getId()] = $insurancePrice;
  770.                                 if (!$ticketArticleId || $ticketArticleId === $consumerAvailability->getId()) {
  771.                                     $ticketArticleId $consumerAvailability->getId();
  772.                                 } else {
  773.                                     continue 2;
  774.                                     //throw new \Exception('Insurance TicketArticle '.$ticketArticleId.' ID is not the same across all consumer availabilities for ticket ' . $ticketAvailability->getTicketProduct()->getId());
  775.                                 }
  776.                             }
  777.                             $apiInsurancePrice = new TicketInsurancePrice();
  778.                             $apiInsurancePrice->insuranceId $insuranceProduct->getId();
  779.                             $apiInsurancePrice->ticketArticleId $ticketArticleId;
  780.                             $apiInsurancePrice->priceByConsumerCategory $insurancePricesPerConsumer;
  781.                             $apiInsurancePrice->name $nameLocalized;
  782.                             $apiInsurancePrice->description $descriptionLocalized;
  783.                             $apiInsurancePrice->groupName $groupNameLocalized;
  784.                             $apiInsurancePrice->groupId $groupId;
  785.                             $insurances[$ticketAvailability->getTicketProduct()->getId()][$insuranceProduct->getId()] = $apiInsurancePrice;
  786.                         }
  787.                     }
  788.                 }
  789.             }
  790.         }
  791.         return $insurances;
  792.     }
  793.     /**
  794.      * @param ShopTicketCatalog $catalog
  795.      * @param TicketProduct $ticketProduct
  796.      * @param Carbon $from
  797.      * @param Carbon $to
  798.      *
  799.      * @return TicketUpgradePrice[]
  800.      *
  801.      * @throws \Exception
  802.      */
  803.     protected function getUpgradeData(ShopTicketCatalog $catalogTicketProduct $ticketProductCarbon $fromCarbon $to): array
  804.     {
  805.         $metaProduct $ticketProduct->getMetaProduct();
  806.         $relatedProductIds = [];
  807.         if($metaProduct){
  808.             foreach($metaProduct->getRelatedTo() as $relatedProduct){
  809.                 $relatedProductIds[]=$relatedProduct->getId();
  810.             }
  811.         }
  812.         $upgradeCatalogKeys = [];
  813.         foreach($catalog->getUpgrades() as $objectMetadata){
  814.             $upgradeCatalogKeys[$objectMetadata->getObject()->getId()] = $objectMetadata->getData()['name'];
  815.         }
  816.         $upgrades = [];
  817.         foreach ($catalog->getUpgrades() as $upgrade) {
  818.             /** @var TicketCatalog $upgradeObject */
  819.             $upgradeObject $upgrade->getObject();
  820.             $upgradeFilter = new TicketFilter($from$to);
  821.             $upgradeFilter->setExactDuration(true);
  822.             $upgradeFilter->setIgnorePrice(true);
  823.             $upgradeFilter->setUseTicketValidity(true);
  824.             $upgradeCatalogAvailability $upgradeFilter->getTicketCatalogAvailability($upgradeObjectfalse);
  825.             if ($upgradeCatalogAvailability->hasAvailability() && !$upgradeObject->getIsNotBookable()) {
  826.                 //special logic for flex passes where there is no shuttle
  827.                 $useUpgrade false;
  828.                 if ($metaProduct) {
  829.                     if (array_intersect($metaProduct->getRelatedTo(), $upgradeObject->getTicketProducts())) {
  830.                         $useUpgrade true;
  831.                     }
  832.                 } else {
  833.                     $useUpgrade true;
  834.                 }
  835.                 if ($useUpgrade) {
  836.                     $nameLocalized = new LocalizedText();
  837.                     $descriptionLocalized = new LocalizedText();
  838.                     $infoBubbleTextLocalized = new LocalizedText();
  839.                     foreach ($this->languages as $language) {
  840.                         $nameLocalized->$language $upgradeObject->getName($language) ?: '';
  841.                         $descriptionLocalized->$language $upgradeObject->getMarketplaceDescription() ?: '';
  842.                         $infoBubbleTextLocalized->$language $upgradeObject->getInfoBubbleDescription($language) ?: '';
  843.                     }
  844.                     $upgradePricesPerConsumerCategory = [];
  845.                     $ticketProductId null;
  846.                     foreach ($upgradeCatalogAvailability->getTicketAvailability() as $ticketAvailability) {
  847.                         foreach ($ticketAvailability->getConsumerAvailabilities() as $consumerAvailability) {
  848.                             $ticketProduct $consumerAvailability->getTicketProductAvailability()->getTicketProduct();
  849.                             if ($metaProduct) {
  850.                                 if ($ticketProduct != null && !in_array($ticketProduct->getId(), $relatedProductIds)) {
  851.                                     //this is not a directly related product, ignore
  852.                                     continue;
  853.                                 }
  854.                             }
  855.                             foreach ($this->languages as $language) {
  856.                                 if ($ticketProduct->getDescription($language)) {
  857.                                     $infoBubbleTextLocalized->$language $ticketProduct->getDescription($language);
  858.                                 }
  859.                             }
  860.                             if (!$ticketProductId || $ticketProductId === $ticketProduct->getId()) {
  861.                                 $ticketProductId $ticketProduct->getId();
  862.                             } else {
  863.                                 continue 2;
  864.                                 //throw new \Exception('Upgrade TicketArticle ID '.$ticketProductId.' is not the same across all consumer availabilities for ticket ' . $ticketAvailability->getTicketProduct()->getId());
  865.                             }
  866.                             /** @phpstan-ignore-next-line */
  867.                             $priceInfo $ticketProduct->getOSPriceInfo(1, [], $ticketAvailability->getValidFrom(), $consumerAvailability->getTicketConsumer(), $catalog);
  868.                             $price $priceInfo->getTotalPrice()->getGrossAmount()->asNumeric();
  869.                             $upgradePricesPerConsumerCategory[$consumerAvailability->getTicketConsumer()->getId()] = $price;
  870.                         }
  871.                     }
  872.                     $apiUpgradePrice = new TicketUpgradePrice();
  873.                     $apiUpgradePrice->upgradeId $upgradeObject->getId();
  874.                     if(isset($upgradeCatalogKeys[$upgradeObject->getId()])){
  875.                         $apiUpgradePrice->setUpgradeKey($upgradeCatalogKeys[$upgradeObject->getId()]);
  876.                     }
  877.                     $apiUpgradePrice->ticketArticleId $ticketProductId;
  878.                     $apiUpgradePrice->priceByConsumerCategory $upgradePricesPerConsumerCategory;
  879.                     $apiUpgradePrice->name $nameLocalized;
  880.                     $apiUpgradePrice->description $descriptionLocalized;
  881.                     $apiUpgradePrice->infoText $infoBubbleTextLocalized;
  882.                     $upgrades[] = $apiUpgradePrice;
  883.                 }
  884.             }
  885.         }
  886.         return $upgrades;
  887.     }
  888. }