src/Controller/MobileApi/OfferController.php line 238

Open in your IDE?
  1. <?php
  2. namespace Slivki\Controller\MobileApi;
  3. use Slivki\Controller\SiteController;
  4. use Slivki\Dao\Banner\AppCategoryBannerDaoInterface;
  5. use Slivki\Dao\TireFilter\TireFilterDaoInterface;
  6. use Slivki\Dto\Filter\PaginatorFilterDto;
  7. use Slivki\Dto\Filter\PriceFilterDto;
  8. use Slivki\Dto\Filter\SortFilterDto;
  9. use Slivki\Entity\City;
  10. use Slivki\Entity\CreditCard;
  11. use Slivki\Entity\OfferOrder;
  12. use Slivki\Entity\User;
  13. use Slivki\Entity\Visit;
  14. use Slivki\Enum\DeviceTypeEnum;
  15. use Slivki\Enum\LocalizationLanguage;
  16. use Slivki\Enum\Location\DefaultCoordinates;
  17. use Slivki\Enum\Offer\OfferApiSort;
  18. use Slivki\Message\Query\Tire\GetTireOffersQuery;
  19. use Slivki\Paginator\Offer\OfferIdsByGiftCertificateFilterPaginatorInterface;
  20. use Slivki\Services\CacheService;
  21. use Slivki\Services\Category\CategoryDeliveryFoodOffersCustomSorter;
  22. use Slivki\Services\Localization\TranslateService;
  23. use Slivki\Services\MainMenu\MainMenuAppCacheService;
  24. use Slivki\Services\MainMenu\MainMenuOplatiCacheService;
  25. use Slivki\Services\MobApiCacheService;
  26. use Slivki\Services\MobApiOfferResponseUpdater;
  27. use Slivki\Services\Offer\GeoLocationService;
  28. use Slivki\Services\Offer\OfferResponseCacheService;
  29. use Slivki\Services\Offer\PhoneService;
  30. use Slivki\Services\Payment\PaymentService;
  31. use Slivki\Services\Subscription\SubscriptionService;
  32. use Slivki\Services\UserGetter;
  33. use Slivki\ValueObject\Coordinate;
  34. use Symfony\Component\HttpFoundation\JsonResponse;
  35. use Symfony\Component\HttpFoundation\Response;
  36. use Symfony\Component\Routing\Annotation\Route;
  37. use Slivki\Entity\Category;
  38. use Slivki\Entity\GeoLocation;
  39. use Slivki\Entity\MainMenu;
  40. use Slivki\Entity\Offer;
  41. use Slivki\Services\Offer\OfferCacheService;
  42. use Slivki\Services\ImageService;
  43. use Slivki\Util\SoftCache;
  44. use Symfony\Component\HttpFoundation\Request;
  45. use Slivki\Util\Logger;
  46. use OpenApi\Annotations as OA;
  47. use Nelmio\ApiDocBundle\Annotation\Model;
  48. use Slivki\Entity\EntityOption;
  49. use Slivki\Services\BePaidService;
  50. use Slivki\Dto\Offer\GiftCertificate\FilterDto;
  51. use Slivki\Dto\Media\ImageWithTagDto;
  52. use function array_map;
  53. use function array_slice;
  54. use function count;
  55. class OfferController extends MobileApiController
  56. {
  57.     /**
  58.      * Список категорий по городам
  59.      * @Route("/mobile/api/v2/city/{cityID}/categories/{pageNumber}", methods={"GET"});
  60.      * @OA\Response(
  61.      *     response = 200,
  62.      *     description = "Список категорий",
  63.      *     @OA\Schema(
  64.      *        @OA\Property(property = "ID", type = "integer", description = "ID категории"),
  65.      *        @OA\Property(property = "name", type = "varchar", description = "название категории"),
  66.      *        @OA\Property(property = "entityCount", type = "integer", description = "количество акций"),
  67.      *        @OA\Property(property = "iconURL", type = "varchar", description = "изображение категории"),
  68.      *        @OA\Property(property = "offers", type = "object", description = "акции",
  69.      *          @OA\Property(property = "ID", type = "integer", description = "ID акции"),
  70.      *          @OA\Property(property = "name", type = "varchar", description = "название акции"),
  71.      *          @OA\Property(property = "regularPrice", type = "decimal", description = "обычная цена"),
  72.      *          @OA\Property(property = "offerPrice", type = "decimal", description = "цена со скидкой"),
  73.      *          @OA\Property(property = "discountPercent", type = "varchar", description = "процент скидки"),
  74.      *          @OA\Property(property = "saleCount", type = "integer", description = "количество проданных кодов"),
  75.      *          @OA\Property(property = "imageURL", type = "varchar", description = "URL изображения для тизера"),
  76.      *          @OA\Property(property = "verticalImageUrl", type = "varchar", description = "URL вертикального изображения для тизера"),
  77.      *          @OA\Property(property = "activeTill", type = "datetime", description = "дата окончания акции"),
  78.      *          @OA\Property(property = "rating", type = "decimal", description = "рейтинг акции"),
  79.      *          @OA\Property(property = "codeCost", type = "varchar", description = "цена кода"),
  80.      *          @OA\Property(property = "offerType", type = "integer", description = "тип оффера. 0 обычный, 1 - онлайн ордер, 2 оноайл ордер без возможности купить код отдельно, 3 трайпл, 4 шм, 5 сертификаты"),
  81.      *          @OA\Property(property = "phoneNumbersWithoutLocation", type = "object", description = "номера телефонов без местоположения",
  82.      *              @OA\Property(property = "phoneNumber", type = "varchar", description = "номер телефона"),
  83.      *              @OA\Property(property = "label", type = "varchar", description = "метка"),
  84.      *          ),
  85.      *          @OA\Property(property = "locations", type = "object", description = "местоположение",
  86.      *              @OA\Property(property = "address", type = "varchar", description = "адрес"),
  87.      *              @OA\Property(property = "workingHours", type = "text", description = "время роботы"),
  88.      *              @OA\Property(property = "phoneNumbers", type = "object", description = "номера телефонов",
  89.      *                  @OA\Property(property = "phoneNumber", type = "varchar", description = "номер телефона"),
  90.      *                  @OA\Property(property = "label", type = "varchar", description = "метка"),
  91.      *              ),
  92.      *              @OA\Property(property = "description", type = "varchar", description = "описание"),
  93.      *              @OA\Property(property = "geoLocation", type = "varchar", description = "массив координат"),
  94.      *          ),
  95.      *          @OA\Property(property = "visitCount", type = "integer", description = "количество посещений"),
  96.      *          @OA\Property(property = "address", type = "varchar", description = "адрес"),
  97.      *          @OA\Property(property = "isFreeCode", type = "boolean", description = "бесплатный ли код"),
  98.      *          @OA\Property(property = "companyLogoImage", type = "varchar", description = "URL лого компании"),
  99.      *          @OA\Property(property = "buyButtonLabel", type = "varchar", description = "текст кнопки покупки кода"),
  100.      *          @OA\Property(property = "openOnlineOrderButtonLabel", type = "varchar", description = "текст кнопки онлайн заказа"),
  101.      *        ),
  102.      *        @OA\Property(property = "categoryCount", type = "integer", description = "количество категорий"),
  103.      *        @OA\Property(property = "isLast", type = "boolean", description = "последняя ли страница"),
  104.      *        @OA\Property(property = "userBalance", type = "varchar", description = "баланс пользователя"),
  105.      *        type="object",
  106.      *     )
  107.      * )
  108.      * @OA\Parameter(
  109.      *     name = "cityID",
  110.      *     in = "path",
  111.      *     description = "ID города",
  112.      *     required = true,
  113.      *     @OA\Schema(type="integer"),
  114.      *)
  115.      * @OA\Parameter(
  116.      *     name = "pageNumber",
  117.      *     in = "path",
  118.      *     description = "номер страницы",
  119.      *     required = true,
  120.      *     @OA\Schema(type="integer"),
  121.      *)
  122.      * @OA\Parameter(
  123.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  124.      *     in = "header",
  125.      *     description = "Токен юзера",
  126.      *     required = true,
  127.      *     @OA\Schema(type="string"),
  128.      *)
  129.      * @OA\Tag(name="Category")
  130.      */
  131.     public function getCategoriesAction(MobApiCacheService $mobApiCacheServiceOfferCacheService $offerCacheServiceImageService $imageService$cityID$pageNumber)
  132.     {
  133.         ini_set('memory_limit''4g');
  134.         $categories $this->getCategories(
  135.             $mobApiCacheService,
  136.             $offerCacheService,
  137.             $imageService,
  138.             0,
  139.             $pageNumber,
  140.             $cityID,
  141.             true,
  142.             $this->getUser(),
  143.         );
  144.         return $this->getResponse($categories200);
  145.     }
  146.     /**
  147.      * Список категорий и субкатегорий
  148.      * @Route("/mobile/api/v2/city/{cityID}/categories-tree", methods={"GET"});
  149.      * @OA\Response(
  150.      *     response=Response::HTTP_OK,
  151.      *     description="Список категорий и субкатегорий",
  152.      *     @OA\JsonContent(
  153.      *         @OA\Property(property="categories", type="array", description="Категории",
  154.      *             @OA\Items(
  155.      *                 @OA\Property(property="ID", type="string", description="ID категории"),
  156.      *                 @OA\Property(property="topParentCategoryId", type="integer", nullable=true, description="Id главной родительской категории"),
  157.      *                 @OA\Property(property="name", type="string", description="название категории"),
  158.      *                 @OA\Property(property="entityCount", type="integer", description="количество акций"),
  159.      *                 @OA\Property(property="iconURL", type="string", description="изображение категории"),
  160.      *                 @OA\Property(property="workExamplesCount", type="integer", description="количество примеров работ"),
  161.      *                 @OA\Property(property="beautyMasterCount", type="integer", description="количество мастеров"),
  162.      *                 @OA\Property(property="interiorGalleryOffersCount", type="integer", description="количество акций с галереей интерьеров"),
  163.      *                 @OA\Property(property="hideWorkExamples", type="boolean", description="Не показывать примеры работ"),
  164.      *                 @OA\Property(property="tireFilterCategories", type="array", description="доступные категории для фильтра шиномонтажа",
  165.      *                     @OA\Items(
  166.      *                         @OA\Property(property="id", type="integer", description="ID категории"),
  167.      *                         @OA\Property(property="name", type="string", description="название категории"),
  168.      *                     ),
  169.      *                 ),
  170.      *                 @OA\Property(property="subcategories", type="array", description="субкатегории",
  171.      *                     @OA\Items(
  172.      *                         @OA\Property(property="ID", type="integer", description="ID субкатегории"),
  173.      *                         @OA\Property(property="topParentCategoryId", type="integer", nullable=true, description="Id главной родительской категории"),
  174.      *                         @OA\Property(property="name", type="string", description="название субкатегории"),
  175.      *                         @OA\Property(property="entityCount", type="integer", description="количество акций субкатегории"),
  176.      *                         @OA\Property(property="iconURL", type="string", description="изображение субкатегории"),
  177.      *                         @OA\Property(property="workExamplesCount", type="integer", description="количество примеров работ"),
  178.      *                         @OA\Property(property="beautyMasterCount", type="integer", description="количество мастеров"),
  179.      *                         @OA\Property(property="interiorGalleryOffersCount", type="integer", description="количество акций с галереей интерьеров"),
  180.      *                         @OA\Property(property="hideWorkExamples", type="boolean", description="Не показывать примеры работ"),
  181.      *                         @OA\Property(property="tireFilterCategories", type="array", description="доступные категории для фильтра шиномонтажа",
  182.      *                             @OA\Items(
  183.      *                                 @OA\Property(property="id", type="integer", description="ID категории"),
  184.      *                                 @OA\Property(property="name", type="string", description="название категории"),
  185.      *                             ),
  186.      *                         ),
  187.      *                         @OA\Property(property="tireFilterDiscountCategories", type="array", description="доступные категории cо скидками для фильтра шиномонтажа",
  188.      *                             @OA\Items(
  189.      *                                 @OA\Property(property="id", type="integer", description="ID категории"),
  190.      *                                 @OA\Property(property="name", type="string", description="название категории"),
  191.      *                             ),
  192.      *                         ),
  193.      *                         @OA\Property(property="giftCertificateFilterCategories", type="array", description="доступные категории для фильтра по сертификатам",
  194.      *                             @OA\Items(
  195.      *                                 @OA\Property(property="id", type="integer", description="ID категории"),
  196.      *                                 @OA\Property(property="name", type="string", description="название категории"),
  197.      *                             ),
  198.      *                         ),
  199.      *                     ),
  200.      *                 ),
  201.      *             ),
  202.      *        ),
  203.      *        @OA\Property(property="categoryCount", type="integer", description="количество категорий"),
  204.      *        type="object",
  205.      *     ),
  206.      * ),
  207.      * @OA\Parameter(
  208.      *     name = "cityID",
  209.      *     in = "path",
  210.      *     description = "ID города",
  211.      *     required = true,
  212.      *     @OA\Schema(type="integer"),
  213.      *)
  214.      * @OA\Parameter(
  215.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  216.      *     in = "header",
  217.      *     description = "Токен юзера",
  218.      *     required = true,
  219.      *     @OA\Schema(type="string"),
  220.      *)
  221.      * @OA\Tag(name="Category")
  222.      */
  223.     public function getCategoriesTreeAction(
  224.         Request $request,
  225.         MainMenuAppCacheService $mainMenuAppCacheService,
  226.         MainMenuOplatiCacheService $mainMenuOplatiCacheService,
  227.         TranslateService $translateService,
  228.         int $cityID
  229.     ): JsonResponse {
  230.         $locale LocalizationLanguage::language($request->query->get('language'));
  231.         $mainMenu User::OPLATI_PARTNER_TOKEN === $request->headers->get('HTTP-SLIVKi-PARTNER-TOKEN')
  232.             ? $mainMenuOplatiCacheService->getMainMenuCached($cityID)
  233.             : $mainMenuAppCacheService->getMainMenuCached($cityID);
  234.         foreach ($mainMenu['categories'] as $key => $category) {
  235.             $mainMenu['categories'][$key]['name'] = $translateService->getCategoryTranslationForField(
  236.                 $category['ID'],
  237.                 'name',
  238.                 $locale,
  239.                 $category['name']
  240.             );
  241.         }
  242.         return $this->getResponseWithoutUser($mainMenuResponse::HTTP_OK);
  243.     }
  244.     public function getCategories(MobApiCacheService $mobApiCacheServiceOfferCacheService $offerCacheServiceImageService $imageService$parentID$pageNumber$cityID null$withOffers false$user null) {
  245.         $categories $this->getCategoriesCached($mobApiCacheService$imageService$parentID$pageNumber$cityID$withOffers$user);
  246.         if (!$withOffers) {
  247.             return $categories;
  248.         }
  249.         $offerRepository $this->getDoctrine()->getRepository(Offer::class);
  250.         $result = [];
  251.         foreach ($categories['categories'] as $key => $category) {
  252.             foreach ($category['offers']['offers'] as $offerKey => $offerArray) {
  253.                 $offer $offerCacheService->getOffer($offerArray['ID']);
  254.                 $categories['categories'][$key]['offers']['offers'][$offerKey]['isFreeCode'] = false;
  255.                 if ($offer) {
  256.                     $categories['categories'][$key]['offers']['offers'][$offerKey]['isFreeCode'] = $offerRepository->isOfferFreeForUser($offer$user);
  257.                     if ($categories['categories'][$key]['offers']['offers'][$offerKey]['isFreeCode'] && !$offer->isFree()
  258.                         && $offer->getID() != Offer::PETROL_OFFER_ID) {
  259.                         $categories['categories'][$key]['offers']['offers'][$offerKey]['freeCodeCount'] = 1;
  260.                     }
  261.                 }
  262.             }
  263.             $result['categories'][] = $categories['categories'][$key];
  264.         }
  265.         $result['categoryCount'] = $categories['categoryCount'];
  266.         $result['isLast'] = $categories['isLast'];
  267.         return $result;
  268.     }
  269.     private function getCategoriesCached(MobApiCacheService $mobApiCacheServiceImageService $imageService$parentID$pageNumber$cityID null$withOffers false$user null) {
  270.         $softCache = new SoftCache('MOBILE-1-');
  271.         $cacheKey 'categories-19-' $parentID;
  272.         if ($pageNumber) {
  273.             $cacheKey .= '-page-' $pageNumber;
  274.         }
  275.         if ($cityID) {
  276.             $cacheKey .= '-city-' $cityID;
  277.         }
  278.         if ($withOffers) {
  279.             $cacheKey .= '-offers';
  280.         }
  281.         $result $softCache->get($cacheKey);
  282.         if ($result) {
  283.             return $result;
  284.         }
  285.         Logger::instance('CACHEDEBUG')->info('category not in cache ' $parentID);
  286.         $result =  [];
  287.         $entityManager $this->getDoctrine()->getManager();
  288.         $itemList $entityManager->getRepository(MainMenu::class)->getItemList(MainMenu::MENU_ID_MAIN$cityID);
  289.         $categoryIDs = [];
  290.         /** @var MainMenu $item */
  291.         foreach ($itemList as $item) {
  292.             if ($item->getType() == MainMenu::TYPE_OFFER_CATEGORY) {
  293.                 $categoryID $item->getEntityID();
  294.                 $category $entityManager->getRepository(Category::class)->getCategoryForMobileApi($categoryID);
  295.                 if (!$category) {
  296.                     continue;
  297.                 }
  298.                 $categoryIDs[] = $item->getEntityID();
  299.             }
  300.         }
  301.         $categoryCount count($categoryIDs);
  302.         if ($cityID == City::DEFAULT_CITY_ID) {
  303.             array_unshift($categoryIDsCategory::FOOD_DELIVERY_CATEGORY_ID);
  304.         }
  305.         $perPage 3;
  306.         $isLast true;
  307.         if ($pageNumber) {
  308.             $offset = ($pageNumber 1) * $perPage;
  309.             $isLast count($categoryIDs) <= $offset $perPage;
  310.             $categoryIDs array_slice($categoryIDs$offset$perPage);
  311.         }
  312.         /** @var Category $category */
  313.         foreach ($categoryIDs as $categoryID) {
  314.             $category $entityManager->getRepository(Category::class)->getCategoryForMobileApi($categoryID);
  315.             if (!$category) {
  316.                 continue;
  317.             }
  318.             $iconURL 'https://www.slivki.by';
  319.             $media $category->getAppIconMedia();
  320.             if (!$media) {
  321.                 $media $category->getMobileMenuIconMedia();
  322.             }
  323.             $iconURL .= $media $imageService->getImageURL($media232232) : ImageService::FALLBACK_IMAGE;
  324.             $item = [
  325.                 'ID' => $category->getID(),
  326.                 'name' => $category->getName(),
  327.                 'entityCount' => $category->getEntityCount(),
  328.                 'iconURL' => $iconURL
  329.             ];
  330.             if ($withOffers) {
  331.                 $item['offers'] = $mobApiCacheService->getOffersByCategoryIDCached($category->getID(), 10);
  332.             }
  333.             $result[] = $item;
  334.         }
  335.         $data = [];
  336.         $data['categoryCount'] = $categoryCount;
  337.         if ($parentID == 0) {
  338.             $data['categories'] = $result;
  339.         } else {
  340.             $data $result;
  341.         }
  342.         if ($pageNumber) {
  343.             $data['isLast'] = $isLast;
  344.         }
  345.         $softCache->set($cacheKey$data,  60 60);
  346.         return $data;
  347.     }
  348.     /**
  349.      * Список акций по категории
  350.      * @Route("/mobile/api/v2/category/{categoryID}/{pageNumber}", methods={"GET"});
  351.      * @OA\Response(
  352.      *     response = 200,
  353.      *     description = "Список акций по категории",
  354.      *     @OA\Schema(
  355.      *        @OA\Property(property = "offers", type = "object", description = "акции",
  356.      *          @OA\Property(property = "ID", type = "integer", description = "ID акции"),
  357.      *          @OA\Property(property = "name", type = "varchar", description = "название акции"),
  358.      *          @OA\Property(property = "regularPrice", type = "decimal", description = "обычная цена"),
  359.      *          @OA\Property(property = "offerPrice", type = "decimal", description = "цена со скидкой"),
  360.      *          @OA\Property(property = "discountPercent", type = "varchar", description = "процент скидки"),
  361.      *          @OA\Property(property = "saleCount", type = "integer", description = "количество проданных кодов"),
  362.      *          @OA\Property(property = "imageURL", type = "varchar", description = "URL изображения для тизера"),
  363.      *          @OA\Property(property = "verticalImageUrl", type = "varchar", description = "URL вертикального изображения для тизера"),
  364.      *          @OA\Property(property = "activeTill", type = "datetime", description = "дата окончания акции"),
  365.      *          @OA\Property(property = "rating", type = "decimal", description = "рейтинг акции"),
  366.      *          @OA\Property(property = "codeCost", type = "varchar", description = "цена кода"),
  367.      *          @OA\Property(property = "phoneNumbersWithoutLocation", type = "object", description = "номера телефонов без местоположения",
  368.      *              @OA\Property(property = "phoneNumber", type = "varchar", description = "номер телефона"),
  369.      *              @OA\Property(property = "label", type = "varchar", description = "метка"),
  370.      *          ),
  371.      *          @OA\Property(property = "locations", type = "object", description = "местоположение",
  372.      *              @OA\Property(property = "address", type = "varchar", description = "адрес"),
  373.      *              @OA\Property(property = "workingHours", type = "text", description = "время роботы"),
  374.      *              @OA\Property(property = "phoneNumbers", type = "object", description = "номера телефонов",
  375.      *                  @OA\Property(property = "phoneNumber", type = "varchar", description = "номер телефона"),
  376.      *                  @OA\Property(property = "label", type = "varchar", description = "метка"),
  377.      *              ),
  378.      *              @OA\Property(property = "description", type = "varchar", description = "описание"),
  379.      *              @OA\Property(property = "geoLocation", type = "varchar", description = "массив координат"),
  380.      *          ),
  381.      *          @OA\Property(property = "visitCount", type = "integer", description = "количество посещений"),
  382.      *          @OA\Property(property = "address", type = "varchar", description = "адрес"),
  383.      *          @OA\Property(property = "offerType", type = "integer", description = "тип акции"),
  384.      *          @OA\Property(property = "isFreeCode", type = "boolean", description = "бесплатный ли код"),
  385.      *          @OA\Property(property = "companyLogoImage", type = "object", description = "URL лого компании"),
  386.      *          @OA\Property(property="buyButtonLabel", type="string", description="текст кнопки покупки кода"),
  387.      *          @OA\Property(property="openOnlineOrderButtonLabel", type="string", description="текст кнопки онлайн заказа"),
  388.      *        ),
  389.      *        @OA\Property(property = "isLast", type = "boolean", description = "последняя ли страница"),
  390.      *        @OA\Property(property = "offersCount", type = "integer", description = "количество акций"),
  391.      *        @OA\Property(property = "userBalance", type = "varchar", description = "баланс пользователя"),
  392.      *        type="object",
  393.      *     )
  394.      * )
  395.      * @OA\Parameter(
  396.      *     name = "categoryID",
  397.      *     in = "path",
  398.      *     description = "ID категории",
  399.      *     required = true,
  400.      *     @OA\Schema(type="integer"),
  401.      *)
  402.      * @OA\Parameter(
  403.      *     name = "pageNumber",
  404.      *     in = "path",
  405.      *     description = "номер страницы",
  406.      *     required = true,
  407.      *     @OA\Schema(type="integer"),
  408.      *)
  409.      * @OA\Parameter(
  410.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  411.      *     in = "header",
  412.      *     description = "Токен юзера",
  413.      *     required = true,
  414.      *     @OA\Schema(type="string"),
  415.      *)
  416.      * @OA\Tag(name="Category")
  417.      */
  418.     public function getOffersAction(
  419.         OfferCacheService $offerCacheService,
  420.         MobApiCacheService $mobApiCacheService,
  421.         $categoryID,
  422.         $pageNumber
  423.     ): JsonResponse {
  424.         ini_set('memory_limit''4g');
  425.         $user $this->getUser();
  426.         $result $mobApiCacheService->getOffersByCategoryIDCached($categoryID$pageNumber);
  427.         $offerRepository $this->getDoctrine()->getRepository(Offer::class);
  428.         foreach ($result['offers'] as $key => $offerArray) {
  429.             $offer $offerCacheService->getOffer($offerArray['ID']);
  430.             if ($offer) {
  431.                 if ($offer->isBuyCodeDisable() || $offer->isHideInApp()) {
  432.                     unset($result['offers'][$key]);
  433.                 }
  434.                 $result['offers'][$key]['isFreeCode'] = $offerRepository->isOfferFreeForUser($offer$user);
  435.             }
  436.         }
  437.         return $this->getResponse($result200);
  438.     }
  439.     /**
  440.      * Отсортированные акции категории
  441.      * @Route("/mobile/api/v2/category-sorted", methods={"POST"});
  442.      * @OA\Response(
  443.      *     response = 200,
  444.      *     description = "Отсортированные акции категории",
  445.      *     @OA\JsonContent(
  446.      *        @OA\Property(property="offers", type="object", description="акции",
  447.      *          @OA\Property(property="ID", type="integer", description="ID акции"),
  448.      *          @OA\Property(property="name", type="string", description="название акции"),
  449.      *          @OA\Property(property="regularPrice", type="number", format="decimal", description="обычная цена"),
  450.      *          @OA\Property(property="offerPrice", type="number", format="decimal", description="цена со скидкой"),
  451.      *          @OA\Property(property="discountPercent", type="string", description="процент скидки"),
  452.      *          @OA\Property(property="isWithoutCodes", type="boolean", description="Акция без промокодов"),
  453.      *          @OA\Property(property="saleCount", type="integer", description="количество проданных кодов"),
  454.      *          @OA\Property(property="imageURL", type="string", description="URL изображения для тизера"),
  455.      *          @OA\Property(property="verticalImageUrl", type="string", description="URL вертикального изображения для тизера"),
  456.      *          @OA\Property(property="activeTill", type="datetime", description="дата окончания акции"),
  457.      *          @OA\Property(property="rating", type="number", format="decimal", description="рейтинг акции"),
  458.      *          @OA\Property(property="codeCost", type="string", description="цена кода"),
  459.      *          @OA\Property(property="conversion", type="number", description="Конверсия"),
  460.      *          @OA\Property(property="commentsCount", type="integer", description="Количество комментариев"),
  461.      *          @OA\Property(property="phoneNumbersWithoutLocation", type="object", description="номера телефонов без местоположения",
  462.      *              @OA\Property(property="phoneNumber", type="string", description="номер телефона"),
  463.      *              @OA\Property(property="label", type="string", description="метка"),
  464.      *          ),
  465.      *          @OA\Property(
  466.      *              property="images",
  467.      *              description="Список изображений акции",
  468.      *              type="array",
  469.      *              @OA\Items(ref=@Model(type=ImageWithTagDto::class)),
  470.      *          ),
  471.      *          @OA\Property(property="locations", type="object", description="местоположение",
  472.      *              @OA\Property(property="address", type="string", description="адрес"),
  473.      *              @OA\Property(property="workingHours", type="string", description="время работы"),
  474.      *              @OA\Property(property="phoneNumbers", type="object", description="номера телефонов",
  475.      *                  @OA\Property(property="phoneNumber", type="string", description="номер телефона"),
  476.      *                  @OA\Property(property="label", type="string", description="метка"),
  477.      *              ),
  478.      *              @OA\Property(property="description", type="string", description="описание"),
  479.      *              @OA\Property(property="geoLocation", type="string", description="массив координат"),
  480.      *          ),
  481.      *          @OA\Property(property="visitCount", type="integer", description="количество посещений"),
  482.      *          @OA\Property(property="address", type="string", description="адрес"),
  483.      *          @OA\Property(property="offerType", type="integer", description="тип акции"),
  484.      *          @OA\Property(property="dynamicData", type="object", description="Динамические данные акции",
  485.      *               @OA\Property(property="offerId", type="integer", description="Id акции"),
  486.      *               @OA\Property(property="realTimeDeliveryTime", type="integer", description="Количество минут до ближайшей доставки"),
  487.      *               @OA\Property(property="realTimeShippingTime", type="integer", description="Количество минут до ближайшего самовывоза"),
  488.      *          ),
  489.      *          @OA\Property(property="allowedOnlineOrderTypes", type="array", description="Типы онлайн заказа",
  490.      *              @OA\Items(type="integer", description="1 - Food, 2 - Gift-certificate, 3 - tire"),
  491.      *          ),
  492.      *          @OA\Property(property="isFreeCode", type="boolean", description="бесплатный ли код"),
  493.      *          @OA\Property(property="companyLogoImage", type="object", description="URL лого компании"),
  494.      *          @OA\Property(property="buyButtonLabel", type="string", description="текст кнопки покупки кода"),
  495.      *          @OA\Property(property="openOnlineOrderButtonLabel", type="string", description="текст кнопки онлайн заказа"),
  496.      *          @OA\Property(property="deliveryTime", type="object", nullable="true", description="Время работы доставки",
  497.      *              @OA\Property(property="text", type="string", description="Текст доставки"),
  498.      *              @OA\Property(property="isOpen", type="boolean", description="Открыто заведение"),
  499.      *          ),
  500.      *        ),
  501.      *        @OA\Property(property="paymentMethodsForOnlineOrder", nullable=true, type="object", description="Способы оплаты",
  502.      *           @OA\Property(property="delivery", type="object", description="Доставка",
  503.      *           @OA\Property(property="online", type="boolean", description="Онлайн оплата"),
  504.      *           @OA\Property(property="cache", type="boolean", description="Оплата наличными"),
  505.      *           @OA\Property(property="terminal", type="boolean", description="Оплата картой"),
  506.      *           @OA\Property(property="slivki-pay", type="boolean", description="Оплата через Slivki Pay"),
  507.      *        )),
  508.      *       @OA\Property(property = "giftPaymentMethodsForOnlineOrder", type = "object", description = "Способы оплаты",
  509.      *          @OA\Property(property="online", type="boolean", description="Онлайн оплата"),
  510.      *          @OA\Property(property="cash", type="boolean", description="Оплата наличными"),
  511.      *          @OA\Property(property="terminal", type="boolean", description="Оплата картой"),
  512.      *          @OA\Property(property="slivkiPay", type="boolean", description="Оплата через Slivki Pay"),
  513.      *        ),
  514.      *        @OA\Property(property="isLast", type="boolean", description="последняя ли страница"),
  515.      *        @OA\Property(property="offersCount", type="integer", description="количество акций"),
  516.      *        @OA\Property(property="showItemsCatalog", type="boolean", description="выводить ли в категории товарный каталог"),
  517.      *        @OA\Property(property="userBalance", type="string", description="баланс пользователя"),
  518.      *        @OA\Property(property="currency", type="object", nullable="true", description="Курс для перевода в валюту",
  519.      *            @OA\Property(property="code", type="string", description="Название валюты (GEL)"),
  520.      *            @OA\Property(property="rate", type="number", description="Курс конверсии в бел рубль"),
  521.      *        ),
  522.      *        type="object",
  523.      *     )
  524.      * )
  525.      * @OA\RequestBody(
  526.      *     required=true,
  527.      *     @OA\MediaType(
  528.      *         mediaType="application/json",
  529.      *         @OA\Schema(
  530.      *              required={"categoryID", "longitude", "latitude", "pageNumber", "sortBy"},
  531.      *              @OA\Property(property="categoryID", description="ID категории", type="integer"),
  532.      *              @OA\Property(property="longitude", description="долгота", type="float"),
  533.      *              @OA\Property(property="latitude", description="широта", type="float"),
  534.      *              @OA\Property(property="pageNumber", description="номер страницы", type="integer"),
  535.      *              @OA\Property(property="sortBy", description="сортировка", type="integer"),
  536.      *              @OA\Property(
  537.      *                  property="tireFilter",
  538.      *                  description="Фильтр онлайн заказа",
  539.      *                  type="object",
  540.      *                  nullable=true,
  541.      *                  @OA\Property(property="weekday", type="integer", description="День недели 0 - воскресенье", example="0"),
  542.      *                  @OA\Property(property="time", type="string", description="Время в формате HH:MM", example="12:30"),
  543.      *              ),
  544.      *              @OA\Property(
  545.      *                  property="giftCertificateFilter",
  546.      *                  type="object",
  547.      *                  nullable=true,
  548.      *                  description="Фильтр по сертификатам",
  549.      *                  @OA\Property(
  550.      *                      property="price",
  551.      *                      nullable=true,
  552.      *                      type="object",
  553.      *                      description="Фильтр по цене",
  554.      *                      ref=@Model(type=PriceFilterDto::class),
  555.      *                  ),
  556.      *                  @OA\Property(
  557.      *                      property="paginator",
  558.      *                      type="object",
  559.      *                      description="Пагинация",
  560.      *                      ref=@Model(type=PaginatorFilterDto::class),
  561.      *                  ),
  562.      *                  @OA\Property(
  563.      *                      property="sort",
  564.      *                      type="object",
  565.      *                      description="Сортировка",
  566.      *                      ref=@Model(type=SortFilterDto::class),
  567.      *                  ),
  568.      *                  @OA\Property(property="giftCertificateCategoryId", type="integer", description="Время в формате HH:MM"),
  569.      *              ),
  570.      *         ),
  571.      *     ),
  572.      * ),
  573.      * @OA\Parameter(
  574.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  575.      *     in = "header",
  576.      *     description = "Токен юзера",
  577.      *     required = true,
  578.      *     @OA\Schema(type="string"),
  579.      *)
  580.      * @OA\Tag(name="Offer")
  581.      */
  582.     public function getOffersSortedAction(
  583.         Request $request,
  584.         OfferCacheService $offerCacheService,
  585.         MobApiCacheService $mobApiCacheService,
  586.         TireFilterDaoInterface $tireFilterDao,
  587.         OfferIdsByGiftCertificateFilterPaginatorInterface $offerIdsByGiftCertificateFilterPaginator,
  588.         CategoryDeliveryFoodOffersCustomSorter $categoryDeliveryFoodOffersCustomSorter,
  589.         AppCategoryBannerDaoInterface $appCategoryBannerDao,
  590.         MobApiOfferResponseUpdater $mobApiOfferResponseUpdater
  591.     ): JsonResponse {
  592.         $user $this->getUser();
  593.         $data json_decode($request->getContent());
  594.         $categoryID $data->categoryID;
  595.         $pageNumber $data->pageNumber;
  596.         $sortBy $data->sortBy;
  597.         $location = [];
  598.         if (OfferApiSort::DISTANCE === $data->sortBy) {
  599.             $location['latitude'] = $data->latitude ?: DefaultCoordinates::LATITUDE;
  600.             $location['longitude'] = $data->longitude ?: DefaultCoordinates::LONGITUDE;
  601.         }
  602.         $tireFilter $data->tireFilter ?? null;
  603.         $giftCertificateFilter $data->giftCertificateFilter ?? null;
  604.         if (null !== $tireFilter) {
  605.             $offersData $tireFilterDao->getOffersByTireFilter(new GetTireOffersQuery($categoryID$tireFilter->weekday$tireFilter->time));
  606.             $offers $offerCacheService->getOffers(array_keys($offersData), truetrue);
  607.             $result = [
  608.                 'offers' => $mobApiCacheService->createOfferResponse($offers),
  609.                 'isLast' => true,
  610.                 'offersCount' => count($offers),
  611.                 'offerAvailableAddress' => $offersData
  612.             ];
  613.         } elseif (null !== $giftCertificateFilter) {
  614.             $offerIds array_map(
  615.                 static fn (array $offer): int => (int) $offer['id'],
  616.                 (array) $offerIdsByGiftCertificateFilterPaginator->findOfferIdsByGiftCertificateFilter(new FilterDto(
  617.                     true,
  618.                     new SortFilterDto($giftCertificateFilter->sort->by$giftCertificateFilter->sort->direction),
  619.                     new PaginatorFilterDto($giftCertificateFilter->paginator->page$giftCertificateFilter->paginator->perPage),
  620.                     $categoryID,
  621.                     $giftCertificateFilter->giftCertificateCategoryId,
  622.                     property_exists($giftCertificateFilter'price')
  623.                         ? new PriceFilterDto($giftCertificateFilter->price->minPrice$giftCertificateFilter->price->maxPrice)
  624.                         : null,
  625.                     null !== $location['latitude'] && null !== $location['longitude']
  626.                         ? new Coordinate($location['latitude'], $location['longitude'])
  627.                         : null,
  628.                 ))->getItems(),
  629.             );
  630.             $offers $offerCacheService->getOffers($offerIdstruetrue);
  631.             $offersCount count($offers);
  632.             $result = [
  633.                 'offers' => $mobApiCacheService->createOfferResponse($offers),
  634.                 'isLast' =>  $offersCount $giftCertificateFilter->paginator->perPage,
  635.                 'offersCount' => $offersCount,
  636.             ];
  637.         } elseif ($sortBy !== OfferApiSort::TIMETABLE && $categoryDeliveryFoodOffersCustomSorter->support($categoryID)) {
  638.             $offers $mobApiCacheService->getAllOffersByCategoryIdCached($categoryID);
  639.             $offset = ($pageNumber 1) * MobApiCacheService::OFFERS_PER_PAGE;
  640.             $offerCount count($offers);
  641.             $result = [
  642.                 'isLast' => $offset MobApiCacheService::OFFERS_PER_PAGE >= $offerCount,
  643.                 'offersCount' => $offerCount,
  644.                 'offers' => array_slice(
  645.                     $categoryDeliveryFoodOffersCustomSorter->sort($offers),
  646.                     $offset,
  647.                     MobApiCacheService::OFFERS_PER_PAGE,
  648.                 ),
  649.             ];
  650.         } else {
  651.             $result $mobApiCacheService->getOffersByCategoryIDCached($categoryID$pageNumber$sortBy$location);
  652.         }
  653.         $category $mobApiCacheService->findCachedCategory($categoryID);
  654.         $result['showItemsCatalog'] = null !== $category && $category->getShowItemsCatalog();
  655.         foreach ($result['offers'] as $key => $offerArray) {
  656.             $result['offers'][$key] = $mobApiOfferResponseUpdater->updateOfferResponse($result['offers'][$key], $user$category);
  657.         }
  658.         $result['banners'] = $appCategoryBannerDao->getActiveByCategoryId($categoryID);
  659.         return $this->getResponse($resultResponse::HTTP_OK);
  660.     }
  661.     /**
  662.      * Акция
  663.      * @Route("/mobile/api/v2/offer", methods={"POST"});
  664.      * @OA\Response(
  665.      *     response=Response::HTTP_OK,
  666.      *     description="Описание акции",
  667.      *     content={
  668.      *         @OA\MediaType(
  669.      *             mediaType="application/json",
  670.      *             @OA\Schema(
  671.      *                  @OA\Property(property="ID", type="integer", description="ID акции"),
  672.      *                  @OA\Property(property="name", type="string", description="название акции"),
  673.      *                  @OA\Property(property="cityId", type="integer", description="ID города"),
  674.      *                  @OA\Property(property="saleCount", type="integer", description="количество проданных кодов"),
  675.      *                  @OA\Property(property="lastMonthSaleCount", type="integer", description="количество проданных кодов за последний месяц"),
  676.      *                  @OA\Property(property="visitCount", type="integer", description="Количество посещений"),
  677.      *                  @OA\Property(property="rating", type="float", description="рейтинг акции"),
  678.      *                  @OA\Property(property="active", type="boolean", description="акция активна"),
  679.      *                  @OA\Property(property="isFreeCode", type="boolean", description="платный/бесплатный код"),
  680.      *                  @OA\Property(property="codeCost", type="number", description="цена за промокод"),
  681.      *                  @OA\Property(property="phoneNumbersWithoutLocation", type="object", description="номера телефонов без локаций",
  682.      *                      @OA\Property(property="phoneNumber", type="string", description="номер телефона"),
  683.      *                      @OA\Property(property="label", type="string", description="примечание"),
  684.      *                  ),
  685.      *                  @OA\Property(property="offerUrl", type="string", description="ссылка на акцию"),
  686.      *                  @OA\Property(property="offerType", type="integer", description="тип оффера"),
  687.      *                  @OA\Property(property="isWithoutCodes", type="boolean", description="Акция без промокодов"),
  688.      *                  @OA\Property(property="allowedOnlineOrderTypes", type="array", description="Типы онлайн заказа",
  689.      *                      @OA\Items(type="integer", description="1 - Food, 2 - Gift-certificate, 3 - tire"),
  690.      *                  ),
  691.      *                  @OA\Property(property="activeTill", type="datetime", description="дата окончания акции"),
  692.      *                  @OA\Property(property="conditions", type="string", description="условия акции"),
  693.      *                  @OA\Property(property="description", type="string", description="описание акции"),
  694.      *                  @OA\Property(property="features", type="string", description="особенности"),
  695.      *                  @OA\Property(property="workingHours", type="text", description="Время работы"),
  696.      *                  @OA\Property(property="locations", type="object", description="адреса акции",
  697.      *                      @OA\Property(property="address", type="string", description="адрес. г. Минск, ул. Захарова, 40"),
  698.      *                      @OA\Property(property="workingHours", type="string", description="часы работы заведения"),
  699.      *                      @OA\Property(property="phoneNumbers", type="object", description="номера телефонов в акции",
  700.      *                          @OA\Property(property="phoneNumber", type="string", description="номер телефона"),
  701.      *                          @OA\Property(property="label", type="string", description="примечание")
  702.      *                      ),
  703.      *                      @OA\Property(property="description", type="string", description="описание"),
  704.      *                      @OA\Property(property="geoLocation", type="string", description="координаты заведения"),
  705.      *                  ),
  706.      *                  @OA\Property(property="discountPercent", type="string", description="процент скидки"),
  707.      *                  @OA\Property(
  708.      *                      property="images",
  709.      *                      description="Тизеры акциии",
  710.      *                      type="array",
  711.      *                      @OA\Items(ref=@Model(type=ImageWithTagDto::class)),
  712.      *                  ),
  713.      *                  @OA\Property(property="shopImages", type="array", description="Изображения заведения",
  714.      *                      @OA\Items(type="string", description="URL изображения"),
  715.      *                  ),
  716.      *                  @OA\Property(property="galleryVideos", type="array", description="Видео в галерее акции",
  717.      *                      @OA\Items(type="object", description="Видео",
  718.      *                          @OA\Property(property="title", type="string", description="Заголовок видео"),
  719.      *                          @OA\Property(property="url", type="string", description="URL видео"),
  720.      *                      ),
  721.      *                  ),
  722.      *                  @OA\Property(property="galleryVideoPackage", type="object", description="Видео пакет в галерее акции",
  723.      *                      @OA\Property(property="title", type="string", description="Заголовок пакета"),
  724.      *                      @OA\Property(property="previewImageUrl", type="string", description="URL превью изображения"),
  725.      *                  ),
  726.      *                  @OA\Property(property="regularPrice", type="string", description="обычная цена (может быть строка от 50 руб.)"),
  727.      *                  @OA\Property(property="offerPrice", type="string", description="цена со скидкой"),
  728.      *                  @OA\Property(property="companyID", type="integer", description="ID компании"),
  729.      *                  @OA\Property(property="companyLogoImage", type="object", description="URL лого компании"),
  730.      *                  @OA\Property(property="buyButtonLabel", type="string", description="текст кнопки покупки кода"),
  731.      *                  @OA\Property(property="openOnlineOrderButtonLabel", type="string", description="текст кнопки онлайн заказа"),
  732.      *                  @OA\Property(property="conversion", type="number", description="Конверсия"),
  733.      *                  @OA\Property(property="numberOfPromocodesPerDay", type="number", description="Количество покупок за сутки"),
  734.      *                  @OA\Property(property="onlineAutoOpened", type="boolean", description="Открывать онлайн в апп автоматически"),
  735.      *                  @OA\Property(property="onlineOrderGiftEnabled", type="boolean", description="Заказ в подарок"),
  736.      *                  @OA\Property(property="userBalance", type="string", description="баланс пользователя"),
  737.      *                  @OA\Property(property="availableOnFood", type="boolean", description="Доступна в еде"),
  738.      *                  @OA\Property(property="workExamplesCount", type="integer", description="Количество примеров работ"),
  739.      *                  @OA\Property(property="beautyMasterCount", type="integer", description="количество мастеров"),
  740.      *                  @OA\Property(property="usersWithOfferInFavouritesCount", type="integer", description="Количество пользователей у которых акция в избранных"),
  741.      *                  @OA\Property(property="titleFontColor", type="string", description="Цвет шрифта названия акции"),
  742.      *                  @OA\Property(property="separateTabForCertificatesInApp", type="string", description="Вывод сертификатов в отдельном табе"),
  743.      *                  @OA\Property(property="telegram", type="string", description="Ник в телеграм через @"),
  744.      *                  @OA\Property(property="viber", type="string", description="Номер телефона в вайбер"),
  745.      *                  @OA\Property(property="companyName", type="string", description="Название компании", nullable=true),
  746.      *                  @OA\Property(property="foodOfferDeliveryZones", type="array", description="Зоны доставки еды акции",
  747.      *                      @OA\Items(type="object", description="Зона",
  748.      *                          @OA\Property(property="position", type="integer", description="Позиция зоны"),
  749.      *                          @OA\Property(property="name", type="string", description="Название зоны"),
  750.      *                      ),
  751.      *                  ),
  752.      *                  @OA\Property(property="messengerCallBack", type="string", description="Обратный звонок (телеграм/вайбер)"),
  753.      *                  @OA\Property(property="commentBanners", type="array", description="Баннеры в комментах",
  754.      *                      @OA\Items(type="object", description="Баннеры в комментах: 100% и 50%",
  755.      *                          @OA\Property(property="id", type="integer", description="Id баннера"),
  756.      *                          @OA\Property(property="type", type="integer", description="Тип баннера"),
  757.      *                          @OA\Property(property="title", type="string", description="Название баннера"),
  758.      *                          @OA\Property(property="url", type="string", description="Внешняя ссылка"),
  759.      *                          @OA\Property(property="filePath", type="string", description="Путь к картинке"),
  760.      *                          @OA\Property(property="position", type="integer", description="Позиция для отображения в списке"),
  761.      *                      ),
  762.      *                  ),
  763.      *                  @OA\Property(property = "paymentMethodsForOnlineOrder", type = "object", description = "Способы оплаты",
  764.      *                      @OA\Property(property = "delivery", type = "object", description = "Доставка",
  765.      *                      @OA\Property(property = "online", type = "boolean", description = "Онлайн оплата"),
  766.      *                      @OA\Property(property = "cache", type = "boolean", description = "Оплата наличными"),
  767.      *                      @OA\Property(property = "terminal", type = "boolean", description = "Оплата картой"),
  768.      *                      @OA\Property(property="slivki-pay", type="boolean", description="Оплата через Slivki Pay"),
  769.      *                  ),
  770.      *                  @OA\Property(property = "giftPaymentMethodsForOnlineOrder", type = "object", description = "Способы оплаты",
  771.      *                       @OA\Property(property="online", type="boolean", description="Онлайн оплата"),
  772.      *                       @OA\Property(property="cash", type="boolean", description="Оплата наличными"),
  773.      *                       @OA\Property(property="terminal", type="boolean", description="Оплата картой"),
  774.      *                       @OA\Property(property="slivkiPay", type="boolean", description="Оплата через Slivki Pay"),
  775.      *                   ),
  776.      *                  @OA\Property(property = "pickup", type = "object", description = "Самовывоз",
  777.      *                     @OA\Property(property = "online", type = "boolean", description = "Онлайн оплата"),
  778.      *                     @OA\Property(property = "cache", type = "boolean", description = "Оплата наличными"),
  779.      *                     @OA\Property(property = "terminal", type = "boolean", description = "Оплата картой"),
  780.      *                  )),
  781.      *                  type="object",
  782.      *              ),
  783.      *          ),
  784.      *      }
  785.      *   )
  786.      * )
  787.      * @OA\Response(
  788.      *     response = Response::HTTP_NOT_FOUND,
  789.      *     description = "Акция не найдена"
  790.      * )
  791.      * @OA\RequestBody(
  792.      *     required=true,
  793.      *       @OA\MediaType(
  794.      *           mediaType="application/json",
  795.      *           @OA\Schema(
  796.      *               type="object",
  797.      *               @OA\Property(
  798.      *                  property = "longitude",
  799.      *                  description = "долгота",
  800.      *                  type="number",
  801.      *                  example="27.557008",
  802.      *               ),
  803.      *               @OA\Property(
  804.      *                  property = "latitude",
  805.      *                  description = "широта",
  806.      *                  type="number",
  807.      *                  example="53.911724",
  808.      *               ),
  809.      *               @OA\Property(
  810.      *                  property = "offerID",
  811.      *                  description = "id акции",
  812.      *                  type="integer",
  813.      *                  example="1",
  814.      *               ),
  815.      *               @OA\Property(
  816.      *                  property = "language",
  817.      *                  description = "язык контента",
  818.      *                  type="string",
  819.      *                  example="ru",
  820.      *               ),
  821.      *           ),
  822.      *       ),
  823.      * ),
  824.      * @OA\Parameter(
  825.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  826.      *     in = "header",
  827.      *     description = "Токен юзера",
  828.      *     required = true,
  829.      *     @OA\Schema(type="string"),
  830.      *)
  831.      * @OA\Tag(name="Offer")
  832.      */
  833.     public function offerDetailsAction(
  834.         Request $request,
  835.         OfferResponseCacheService $offerResponseCacheService
  836.     ): JsonResponse {
  837.         $data json_decode($request->getContent());
  838.         $language $data->language ?? null;
  839.         $user $this->getUser();
  840.         $offerID = (int) $data->offerID;
  841.         $isPartner $request->headers->get(User::HTTP_PARTNER_TOKEN_HEADER_NAME) === User::OPLATI_PARTNER_TOKEN;
  842.         $offerResponse $offerResponseCacheService->getOfferResponseByOfferId($offerID$isPartner$language$user);
  843.         $this->addVisit($offerIDVisit::TYPE_OFFERDeviceTypeEnum::APP$user$request->getClientIp());
  844.         return $this->getResponse($offerResponse->toArray(), Response::HTTP_OK);
  845.     }
  846.     /**
  847.      * Список локаций акции по категории
  848.      * @Route("/mobile/api/v2/offer/locations",methods={"POST"})
  849.      * @OA\Response(
  850.      *     response = 200,
  851.      *     description = "Список локаций акции",
  852.      *     @OA\JsonContent(
  853.      *        @OA\Property(property = "offerID", type = "integer", description = "ID акции"),
  854.      *        @OA\Property(property = "name", type = "varchar", description = "название акции"),
  855.      *        @OA\Property(property = "label", type = "varchar", description = "метка"),
  856.      *        @OA\Property(property = "imageURL", type = "varchar", description = "тизер акции"),
  857.      *        @OA\Property(property = "locations", type = "object", description = "местоположение",
  858.      *          @OA\Property(property = "latitude", type = "varchar", description = "широта"),
  859.      *          @OA\Property(property = "longitude", type = "varchar", description = "долгота"),
  860.      *        ),
  861.      *        type="object",
  862.      *     )
  863.      * )
  864.      * @OA\RequestBody(
  865.      *     required=true,
  866.      *     @OA\JsonContent(
  867.      *         @OA\Schema (
  868.      *              type="object",
  869.      *              @OA\Property(
  870.      *                  property="categoryID",
  871.      *                  required=true,
  872.      *                  description="id категори",
  873.      *                  @OA\Schema(type="integer"),
  874.      *              ),
  875.      *         )
  876.      *     )
  877.      * )
  878.      * @OA\Parameter(
  879.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  880.      *     in = "header",
  881.      *     description = "Токен юзера",
  882.      *     required = true,
  883.      *     @OA\Schema(type="string"),
  884.      *)
  885.      * @OA\Tag(name="Offer")
  886.      */
  887.     public function offerLocationsAction(Request $requestImageService $imageService) {
  888.         ini_set('memory_limit''8g');
  889.         $data json_decode($request->getContent());
  890.         $softCache = new SoftCache('mobapi-7');
  891.         $cacheKey 'locations-3-' $data->categoryID;
  892.         $result $softCache->get($cacheKey);
  893.         if ($result) {
  894.             return $this->getResponse($result200);
  895.         }
  896.         $entityManager $this->getDoctrine()->getManager();
  897.         if ($data->categoryID == 0) {
  898.             $offerList $entityManager->getRepository(Offer::class)->getAllActiveOffersCached();
  899.         } else {
  900.             $offerList $entityManager->getRepository(Offer::class)->getActiveOffersByCategoryID($data->categoryID);
  901.         }
  902.         $result = [];
  903.         /** @var Offer $offer */
  904.         foreach ($offerList as $offer) {
  905.             if (!$offer || $offer->isHideInApp()) {
  906.                 continue;
  907.             }
  908.             $item = [
  909.                 'offerID' => $offer->getID(),
  910.                 'name' => $offer->getTitle(),
  911.                 'label' => $offer->getCompanyName(),
  912.                 'imageURL' => 'https://www.slivki.by' $imageService->getImageURLCached($offer->getTeaserMedia() ?: null500324)
  913.             ];
  914.             /** @var GeoLocation $location */
  915.             foreach ($offer->getGeoLocations() as $location) {
  916.                 $item['locations'][] = [
  917.                     'latitude' => $location->getLatitude(),
  918.                     'longitude' => $location->getLongitude()
  919.                 ];
  920.             }
  921.             $result[] = $item;
  922.         }
  923.         $softCache->set($cacheKey$result12 60 60);
  924.         return $this->getResponseWithoutUser($result200);
  925.     }
  926.     /**
  927.      * Геолокация активных акций
  928.      * @Route("/mobile/api/offers/geo-locations", methods={"GET"});
  929.      * @OA\Response(
  930.      *     response=200,
  931.      *     description = "Геолокация активных акций",
  932.      *     @OA\Schema(
  933.      *        @OA\Property(property = "type", type = "varchar", description = "тип колекции"),
  934.      *        @OA\Property(property = "features", type = "object", description = "содержание коллекции",
  935.      *          @OA\Property(property = "type", type = "varchar", description = "тип локации"),
  936.      *          @OA\Property(property = "id", type = "integer", description = "ID локации"),
  937.      *          @OA\Property(property = "geometry", type = "object", description = "местоположение",
  938.      *              @OA\Property(property = "type", type = "varchar", description = "тип маркера"),
  939.      *              @OA\Property(property = "coordinates", type = "object", description = "массив координат  [53.914176,27.589859]"),
  940.      *          ),
  941.      *          @OA\Property(property = "properties", type = "object", description = "свойства",
  942.      *              @OA\Property(property = "iconClass", type = "varchar", description = "класс маркера"),
  943.      *              @OA\Property(property = "iconContent", type = "varchar", description = "содержание маркера"),
  944.      *              @OA\Property(property = "locationID", type = "integer", description = "ID локации"),
  945.      *              @OA\Property(property = "offerID", type = "integer", description = "ID акции"),
  946.      *              @OA\Property(property = "offerTitle", type = "varchar", description = "название акции"),
  947.      *              @OA\Property(property = "teaserURL", type = "varchar", description = "тизер акции"),
  948.      *              @OA\Property(property = "url", type = "varchar", description = "url акции"),
  949.      *              @OA\Property(property = "hintContent", type = "varchar", description = "текст маркера"),
  950.      *          ),
  951.      *        ),
  952.      *        type="object",
  953.      *   )
  954.      * )
  955.      * @OA\Tag(name="Offer")
  956.      */
  957.     public function getOffersGeoLocations(ImageService $imageService) {
  958.         ini_set('memory_limit''8g');
  959.         $softCache = new SoftCache('mobapi-7');
  960.         $cacheKey 'geo-locations';
  961.         $result $softCache->get($cacheKey);
  962.         if ($result) {
  963.             return $this->getResponse($result200);
  964.         }
  965.         $entityManager $this->getDoctrine()->getManager();
  966.         $result = [];
  967.         $offerList $entityManager->getRepository(Offer::class)->getAllActiveOffersNoCache();
  968.         $offerRepository $this->getOfferRepository();
  969.         foreach ($offerList as $key=>$offer) {
  970.             if (!$offer->isHideInApp()) {
  971.                 $result[$key] = $offerRepository->getOfferGeoLocationData($offer, [], $imageServicefalse$this->getParameter('base_url'));
  972.             }
  973.         }
  974.         $softCache->set($cacheKey$result12 60 60);
  975.         return $this->getResponseWithoutUser($result200);
  976.     }
  977.     /**
  978.      * Геолокация активных акций по категории
  979.      * @Route("/mobile/api/offers/geo-locations/{categoryID}", methods={"GET"});
  980.      * @OA\Response(
  981.      *     response=200,
  982.      *     description = "Геолокация активных акций",
  983.      *     @OA\Schema(
  984.      *        @OA\Property(property = "type", type = "varchar", description = "тип колекции"),
  985.      *        @OA\Property(property = "features", type = "object", description = "содержание коллекции",
  986.      *          @OA\Property(property = "type", type = "varchar", description = "тип локации"),
  987.      *          @OA\Property(property = "id", type = "integer", description = "ID локации"),
  988.      *          @OA\Property(property = "geometry", type = "object", description = "местоположение",
  989.      *              @OA\Property(property = "type", type = "varchar", description = "тип маркера"),
  990.      *              @OA\Property(property = "coordinates", type = "object", description = "массив координат  [53.914176,27.589859]"),
  991.      *          ),
  992.      *          @OA\Property(property = "properties", type = "object", description = "свойства",
  993.      *              @OA\Property(property = "iconClass", type = "varchar", description = "класс маркера"),
  994.      *              @OA\Property(property = "iconContent", type = "varchar", description = "содержание маркера"),
  995.      *              @OA\Property(property = "locationID", type = "integer", description = "ID локации"),
  996.      *              @OA\Property(property = "offerID", type = "integer", description = "ID акции"),
  997.      *              @OA\Property(property = "offerTitle", type = "varchar", description = "название акции"),
  998.      *              @OA\Property(property = "teaserURL", type = "varchar", description = "тизер акции"),
  999.      *              @OA\Property(property = "offerType", type = "integer", description = "тип оффера. 0 обычный, 1 - онлайн ордер, 2 оноайл ордер без возможности купить код отдельно, 3 трайпл, 4 шм, 5 сертификаты"),
  1000.      *              @OA\Property(property = "url", type = "varchar", description = "url акции"),
  1001.      *              @OA\Property(property = "hintContent", type = "varchar", description = "текст маркера"),
  1002.      *          ),
  1003.      *        ),
  1004.      *        type="object",
  1005.      *   )
  1006.      * )
  1007.      * @OA\Tag(name="Offer")
  1008.      */
  1009.     public function getOffersGeoLocationsByCategory(
  1010.         $categoryID,
  1011.         ImageService $imageService,
  1012.         CacheService $cacheService
  1013.     ): JsonResponse {
  1014.         ini_set('memory_limit''8g');
  1015.         $softCache = new SoftCache('mobapi-7');
  1016.         $cacheKey 'geo-locations-by-category' $categoryID;
  1017.         $result $softCache->get($cacheKey);
  1018.         if ($result) {
  1019.             return $this->getResponseWithoutUser($result200);
  1020.         }
  1021.         $entityManager $this->getDoctrine()->getManager();
  1022.         $result = [];
  1023.         $offerList $entityManager->getRepository(Offer::class)->getActiveOffersByCategoryID($categoryID);
  1024.         $offerRepository $this->getOfferRepository();
  1025.         foreach ($offerList as $key=>$offer) {
  1026.             if (!$offer->isHideInApp()) {
  1027.                 $offer->setGeoLocation($cacheService->getGeoLocations(GeoLocation::TYPEGeoLocation::class, $offer->getID()));
  1028.                 $result[$key] = $offerRepository->getOfferGeoLocationData($offer, [], $imageServicefalse$this->getParameter('base_url'));
  1029.             }
  1030.         }
  1031.         $softCache->set($cacheKey$result12 60 60);
  1032.         return $this->getResponseWithoutUser($result200);
  1033.     }
  1034.     /**
  1035.      * Получение бесплатного кода трайпл
  1036.      * @Route("/mobile/api/azs-triple/get-code", methods={"POST"});
  1037.      * @OA\Response(
  1038.      *     response=200,
  1039.      *     description = "Получение бесплатного кода трайпл",
  1040.      *     @OA\Schema(
  1041.      *        @OA\Property(property = "code", type = "varchar", description = "Промокод"),
  1042.      *        @OA\Property(property = "enoughBalance", type = "boolean", description = "достаточно ли средств на счету для покупки еще одного кода"),
  1043.      *        @OA\Property(property = "moreFree", type = "bool", description = "Доступен ли еще халявный код"),
  1044.      *        type="object",
  1045.      *        example = { "message": "Ваш промокод: <span>52-467</span><br>", "enoughBalance": true, "moreFree": true }
  1046.      *   )
  1047.      * )
  1048.      * @OA\Response(
  1049.      *     response = 400,
  1050.      *     description = "Wrong data"
  1051.      * )
  1052.      * @OA\Response(
  1053.      *     response = 403,
  1054.      *     description = "Лимит бесплатных кодов исчерпан"
  1055.      * )
  1056.      * @OA\Response(
  1057.      *     response = 404,
  1058.      *     description = "Юзер не найден"
  1059.      * )
  1060.      * @OA\RequestBody(
  1061.      *     required=true,
  1062.      *     @OA\JsonContent(
  1063.      *         @OA\Schema (
  1064.      *              type="object",
  1065.      *              @OA\Property(
  1066.      *                  property="carNumber",
  1067.      *                  required=true,
  1068.      *                  description="Номер авто",
  1069.      *                  @OA\Schema(type="string"),
  1070.      *              ),
  1071.      *              @OA\Property(
  1072.      *                  property="phoneNumber",
  1073.      *                  required=true,
  1074.      *                  description="Номер телефона",
  1075.      *                  @OA\Schema(type="string"),
  1076.      *              ),
  1077.      *         ),
  1078.      *     ),
  1079.      * ),
  1080.      * @OA\Parameter(
  1081.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  1082.      *     in = "header",
  1083.      *     description = "Токен юзера",
  1084.      *     required = true,
  1085.      *     @OA\Schema(type="string"),
  1086.      *)
  1087.      * @OA\Tag(name="Offer")
  1088.      */
  1089.     public function getPetrolCodeAction(
  1090.         Request $request,
  1091.         OfferCacheService $offerCacheService,
  1092.         PaymentService $paymentService,
  1093.         UserGetter $userGetter
  1094.     ) {
  1095.         $user $userGetter->get();
  1096.         $data json_decode($request->getContent());
  1097.         $carNumber $data->carNumber;
  1098.         $phoneNumber $data->phoneNumber;
  1099.         $codesCount 1;
  1100.         $result = [];
  1101.         if (mb_strlen($carNumber) != 4) {
  1102.             $result['error'] = true;
  1103.             $result['message'] = 'Введите номер авто';
  1104.             return $this->getResponse($result400$result['message']);
  1105.         }
  1106.         if (mb_strlen($phoneNumber) != 12) {
  1107.             $result['error'] = true;
  1108.             $result['message'] = 'Введите номер телефона';
  1109.             return $this->getResponse($result400$result['message']);
  1110.         }
  1111.         $offer $offerCacheService->getOffer(Offer::PETROL_OFFER_ID);
  1112.         $entityManager $this->getDoctrine()->getManager();
  1113.         $offerRepository $entityManager->getRepository(Offer::class);
  1114.         if (!$offerRepository->isOfferFreeForUser($offer$user)) {
  1115.             return $this->getResponse(['error' => true'message' => 'Вы не можете получить еще один бесплатный код сегодня.'], 403'Вы не можете получить еще один бесплатный код сегодня.');
  1116.         }
  1117.         $offerOrder $this->createNewOfferOrder(
  1118.             $request,
  1119.             Offer::PETROL_OFFER_ID,
  1120.             $codesCount,
  1121.             $user
  1122.         );
  1123.         if (!$offerOrder) {
  1124.             return $this->getResponse([
  1125.                 'error' => true,
  1126.                 'message' => 'Акция не действительна.'
  1127.             ], 403'Акция не действительна.');
  1128.         }
  1129.         $orderOptionList = [
  1130.             ['name' => EntityOption::OPTION_CAR_NUMBER'value' => $carNumber],
  1131.             ['name' => EntityOption::OPTION_PHONE_NUMBER'value' => $phoneNumber]
  1132.         ];
  1133.         foreach ($orderOptionList as $item) {
  1134.             $orderOption = new EntityOption();
  1135.             $orderOption->setEntityTypeID(EntityOption::ORDER_ENTITY_TYPE);
  1136.             $orderOption->setEntityID($offerOrder->getID());
  1137.             $orderOption->setName($item['name']);
  1138.             $orderOption->setValue($item['value']);
  1139.             $entityManager->persist($orderOption);
  1140.         }
  1141.         $entityManager->flush();
  1142.         $codeCost $offerRepository->getCodeCost($offer);
  1143.         $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1144.         if ($offerFreeForUser || $user->getFullBalance() >= $codeCost $codesCount) {
  1145.             $codeList $paymentService->createCode($offerOrder$codesCounttrue);
  1146.             if(empty($codeList)) {
  1147.                 return $this->getResponse([], 404);
  1148.             }
  1149.             $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1150.             return $this->getResponse([
  1151.                 'code' => $codeList[0],
  1152.                 'enoughBalance' => $user->getFullBalance() >= $codeCost,
  1153.                 'moreFree' => $offerFreeForUser
  1154.             ], 200);
  1155.         }
  1156.         return $this->getResponse([], 403);
  1157.     }
  1158.     /**
  1159.      * Покупка кода трайпл через баланс
  1160.      * @Route("/mobile/api/azs-triple/buy/code/balance", methods={"POST"});
  1161.      * @OA\Response(
  1162.      *     response=200,
  1163.      *     description = "Покупка кода трайпл",
  1164.      *     @OA\Schema(
  1165.      *        @OA\Property(property = "code", type = "varchar", description = "Промокод"),
  1166.      *        @OA\Property(property = "enoughBalance", type = "boolean", description = "достаточно ли средств на счету для покупки еще одного кода"),
  1167.      *        @OA\Property(property = "moreFree", type = "bool", description = "Доступен ли еще халявный код"),
  1168.      *        type="object",
  1169.      *        example = { "message": "Ваш промокод: <span>52-467</span><br>", "enoughBalance": true, "moreFree": true }
  1170.      *   )
  1171.      * )
  1172.      * @OA\Response(
  1173.      *     response = 400,
  1174.      *     description = "Wrong data"
  1175.      * )
  1176.      * @OA\Response(
  1177.      *     response = 403,
  1178.      *     description = "Недостаточно денег на балансе"
  1179.      * )
  1180.      * @OA\Response(
  1181.      *     response = 404,
  1182.      *     description = "Юзер не найден"
  1183.      * )
  1184.      * @OA\RequestBody(
  1185.      *     required=true,
  1186.      *     @OA\JsonContent(
  1187.      *         @OA\Schema (
  1188.      *              type="object",
  1189.      *              @OA\Property(
  1190.      *                  property="carNumber",
  1191.      *                  required=true,
  1192.      *                  description="Номер авто",
  1193.      *                  @OA\Schema(type="string"),
  1194.      *              ),
  1195.      *              @OA\Property(
  1196.      *                  property="phoneNumber",
  1197.      *                  required=true,
  1198.      *                  description="Номер телефона",
  1199.      *                  @OA\Schema(type="string"),
  1200.      *              ),
  1201.      *         ),
  1202.      *     ),
  1203.      * ),
  1204.      * @OA\Parameter(
  1205.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  1206.      *     in = "header",
  1207.      *     description = "Токен юзера",
  1208.      *     required = true,
  1209.      *     @OA\Schema(type="string"),
  1210.      *)
  1211.      * @OA\Tag(name="Offer")
  1212.      */
  1213.     public function buyPetrolCodeBalanceAction(
  1214.         Request $request,
  1215.         OfferCacheService $offerCacheService,
  1216.         PaymentService $paymentService,
  1217.         SubscriptionService $subscriptionService,
  1218.         UserGetter $userGetter
  1219.     ) {
  1220.         $user $userGetter->get();
  1221.         $data json_decode($request->getContent());
  1222.         $carNumber $data->carNumber;
  1223.         $phoneNumber $data->phoneNumber;
  1224.         $codesCount 1;
  1225.         $result = [];
  1226.         if (mb_strlen($carNumber) != 4) {
  1227.             $result['error'] = true;
  1228.             $result['message'] = 'Введите номер авто';
  1229.             return $this->getResponse($result400$result['message']);
  1230.         }
  1231.         if (mb_strlen($phoneNumber) != 12) {
  1232.             $result['error'] = true;
  1233.             $result['message'] = 'Введите номер телефона';
  1234.             return $this->getResponse($result400$result['message']);
  1235.         }
  1236.         $offer $offerCacheService->getOffer(Offer::PETROL_OFFER_ID);
  1237.         $entityManager $this->getDoctrine()->getManager();
  1238.         $offerRepository $entityManager->getRepository(Offer::class);
  1239.         $codeCost $offerRepository->getCodeCost($offer);
  1240.         $amount $codeCost $codesCount;
  1241.         $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1242.         $offerOrder $this->createNewOfferOrder(
  1243.             $request,
  1244.             Offer::PETROL_OFFER_ID,
  1245.             $codesCount,
  1246.             $user,
  1247.             SiteController::DEVICE_TYPE_MOBILE_APP,
  1248.             OfferOrder::METHOD_BALANCE
  1249.         );
  1250.         if (!$offerOrder) {
  1251.             return $this->getResponse([
  1252.                 'error' => true,
  1253.                 'message' => 'Акция не действительна.'
  1254.             ], 403'Акция не действительна.');
  1255.         }
  1256.         $isSubscriber $subscriptionService->isSubscriber($user);
  1257.         if (!$offerFreeForUser && !$isSubscriber && $amount $user->getFullBalance()) {
  1258.             return $this->getResponse([
  1259.                 'error' => true,
  1260.                 'message' => 'Недостаточно денег на балансе.'
  1261.             ], 403'Недостаточно денег на балансе.');
  1262.         }
  1263.         $orderOptionList = [
  1264.             ['name' => EntityOption::OPTION_CAR_NUMBER'value' => $carNumber],
  1265.             ['name' => EntityOption::OPTION_PHONE_NUMBER'value' => $phoneNumber]
  1266.         ];
  1267.         foreach ($orderOptionList as $item) {
  1268.             $orderOption = new EntityOption();
  1269.             $orderOption->setEntityTypeID(EntityOption::ORDER_ENTITY_TYPE);
  1270.             $orderOption->setEntityID($offerOrder->getID());
  1271.             $orderOption->setName($item['name']);
  1272.             $orderOption->setValue($item['value']);
  1273.             $entityManager->persist($orderOption);
  1274.         }
  1275.         $entityManager->flush();
  1276.         $codeList $paymentService->createCode($offerOrder$codesCounttrue);
  1277.         if (empty($codeList)) {
  1278.             return $this->getResponse([], 405);
  1279.         }
  1280.         $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1281.         return $this->getResponse([
  1282.             'code' => $codeList[0],
  1283.             'enoughBalance' => $user->getFullBalance() >= $codeCost,
  1284.             'moreFree' => $offerFreeForUser
  1285.         ], 200);
  1286.     }
  1287.     /**
  1288.      * Покупка кода трайпл через bepaid
  1289.      * @Route("/mobile/api/azs-triple/buy/code/card", methods={"POST"});
  1290.      * @OA\Response(
  1291.      *     response=200,
  1292.      *     description = "Покупка кода трайпл",
  1293.      *     @OA\Schema(
  1294.      *        @OA\Property(property = "code", type = "varchar", description = "Промокод"),
  1295.      *        @OA\Property(property = "enoughBalance", type = "boolean", description = "достаточно ли средств на счету для покупки еще одного кода"),
  1296.      *        @OA\Property(property = "moreFree", type = "bool", description = "Доступен ли еще халявный код"),
  1297.      *        type="object",
  1298.      *        example = { "message": "Ваш промокод: <span>52-467</span><br>", "enoughBalance": true, "moreFree": true }
  1299.      *   )
  1300.      * )
  1301.      * @OA\Response(
  1302.      *     response = 400,
  1303.      *     description = "Wrong data"
  1304.      * )
  1305.      * @OA\Response(
  1306.      *     response = 401,
  1307.      *     description = "Ошибка оплаты"
  1308.      * )
  1309.      * @OA\Response(
  1310.      *     response = 402,
  1311.      *     description = "Карта не найдена"
  1312.      * )
  1313.      * @OA\Response(
  1314.      *     response = 403,
  1315.      *     description = "Акция не действительна"
  1316.      * )
  1317.      * @OA\Response(
  1318.      *     response = 404,
  1319.      *     description = "Юзер не найден"
  1320.      * )
  1321.      * @OA\RequestBody(
  1322.      *     required=true,
  1323.      *     @OA\JsonContent(
  1324.      *         @OA\Schema (
  1325.      *              type="object",
  1326.      *              @OA\Property(
  1327.      *                  property="carNumber",
  1328.      *                  required=true,
  1329.      *                  description="Номер авто",
  1330.      *                  @OA\Schema(type="string"),
  1331.      *              ),
  1332.      *              @OA\Property(
  1333.      *                  property="phoneNumber",
  1334.      *                  required=true,
  1335.      *                  description="Номер телефона",
  1336.      *                  @OA\Schema(type="string"),
  1337.      *              ),
  1338.      *         ),
  1339.      *     ),
  1340.      * ),
  1341.      * @OA\Parameter(
  1342.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  1343.      *     in = "header",
  1344.      *     description = "Токен юзера",
  1345.      *     required = true,
  1346.      *     @OA\Schema(type="string"),
  1347.      *)
  1348.      * @OA\Tag(name="Offer")
  1349.      */
  1350.     public function buyPetrolCodeCardAction(
  1351.         Request $request,
  1352.         OfferCacheService $offerCacheService,
  1353.         BePaidService $bePaidService,
  1354.         PaymentService $paymentService,
  1355.         UserGetter $userGetter
  1356.     ) {
  1357.         $user $userGetter->get();
  1358.         $data json_decode($request->getContent());
  1359.         $carNumber $data->carNumber;
  1360.         $phoneNumber $data->phoneNumber;
  1361.         $codesCount 1;
  1362.         $result = [];
  1363.         if (mb_strlen($carNumber) != 4) {
  1364.             $result['error'] = true;
  1365.             $result['message'] = 'Введите номер авто';
  1366.             return $this->getResponse($result400$result['message']);
  1367.         }
  1368.         if (mb_strlen($phoneNumber) != 12) {
  1369.             $result['error'] = true;
  1370.             $result['message'] = 'Введите номер телефона';
  1371.             return $this->getResponse($result400$result['message']);
  1372.         }
  1373.         $offer $offerCacheService->getOffer(Offer::PETROL_OFFER_ID);
  1374.         $entityManager $this->getDoctrine()->getManager();
  1375.         $offerRepository $entityManager->getRepository(Offer::class);
  1376.         $codeCost $offerRepository->getCodeCost($offer);
  1377.         $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1378.         $offerOrder $this->createNewOfferOrder(
  1379.             $request,
  1380.             Offer::PETROL_OFFER_ID,
  1381.             $codesCount,
  1382.             $user,
  1383.             SiteController::DEVICE_TYPE_MOBILE_APP
  1384.         );
  1385.         if (!$offerOrder) {
  1386.             return $this->getResponse(['error' => true'message' => 'Акция не действительна.'], 403'Акция не действительна.');
  1387.         }
  1388.         $orderOptionList = [
  1389.             ['name' => EntityOption::OPTION_CAR_NUMBER'value' => $carNumber],
  1390.             ['name' => EntityOption::OPTION_PHONE_NUMBER'value' => $phoneNumber]
  1391.         ];
  1392.         foreach ($orderOptionList as $item) {
  1393.             $orderOption = new EntityOption();
  1394.             $orderOption->setEntityTypeID(EntityOption::ORDER_ENTITY_TYPE);
  1395.             $orderOption->setEntityID($offerOrder->getID());
  1396.             $orderOption->setName($item['name']);
  1397.             $orderOption->setValue($item['value']);
  1398.             $entityManager->persist($orderOption);
  1399.         }
  1400.         $entityManager->flush();
  1401.         if (!$offerFreeForUser) {
  1402.             $creditCard $entityManager->find(CreditCard::class, $data->cardID);
  1403.             if (!$creditCard || !$creditCard->isOwner($user->getID())) {
  1404.                 return $this->getResponse([],402);
  1405.             }
  1406.             $result $bePaidService->checkoutByToken($offerOrder$creditCard->getID());
  1407.             if (!$result) {
  1408.                 return $this->getResponse([],401);
  1409.             }
  1410.             $bePaidService->createBePaidPaiment($offerOrder$result);
  1411.             $entityManager->flush();
  1412.         }
  1413.         $codeList $paymentService->createCode($offerOrder$codesCount,false);
  1414.         if (empty($codeList)) {
  1415.             return $this->getResponse([], 405);
  1416.         }
  1417.         $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1418.         return $this->getResponse([
  1419.             'code' => $codeList[0],
  1420.             'enoughBalance' => $user->getFullBalance() >= $codeCost,
  1421.             'moreFree' => $offerFreeForUser
  1422.         ], 200);
  1423.     }
  1424.     /**
  1425.      * Бесплатный ли код
  1426.      * @Route("/mobile/api/offer/is-free", methods={"POST"});
  1427.      * @OA\Response(
  1428.      *     response=200,
  1429.      *     description = "Бесплатный ли код для юзера",
  1430.      *     @OA\Schema(
  1431.      *        @OA\Property(property = "isOfferFree", type = "boolean", description = "Бесплатный ли код для юзера"),
  1432.      *        type="object",
  1433.      *   )
  1434.      * )
  1435.      * @OA\Response(
  1436.      *     response = 404,
  1437.      *     description = "Юзер не найден"
  1438.      * )
  1439.      * @OA\RequestBody(
  1440.      *     required=true,
  1441.      *     @OA\JsonContent(
  1442.      *         @OA\Schema (
  1443.      *              type="object",
  1444.      *              @OA\Property(
  1445.      *                  property="offerID",
  1446.      *                  required=true,
  1447.      *                  description="id акции",
  1448.      *                  @OA\Schema(type="integer"),
  1449.      *              ),
  1450.      *         ),
  1451.      *     ),
  1452.      * ),
  1453.      * @OA\Parameter(
  1454.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  1455.      *     in = "header",
  1456.      *     description = "Токен юзера",
  1457.      *     required = true,
  1458.      *     @OA\Schema(type="string"),
  1459.      *)
  1460.      * @OA\Tag(name="Offer")
  1461.      */
  1462.     public function isOfferFreeAction(
  1463.         Request $request,
  1464.         OfferCacheService $offerCacheService,
  1465.         UserGetter $userGetter
  1466.     ): JsonResponse {
  1467.         $user $userGetter->get();
  1468.         $data json_decode($request->getContent());
  1469.         $offerID $data->offerID;
  1470.         $offer $offerCacheService->getOffer($offerID);
  1471.         $entityManager $this->getDoctrine()->getManager();
  1472.         $offerRepository $entityManager->getRepository(Offer::class);
  1473.         $offerFreeForUser $offerRepository->isOfferFreeForUser($offer$user);
  1474.         return $this->getResponse(['isOfferFree' => $offerFreeForUser], 200);
  1475.     }
  1476.     /**
  1477.      * Список адресов
  1478.      * @Route("/mobile/api/v2/geo-locations/{offerID}", methods={"GET"});
  1479.      * @OA\Tag(name="Geo locations")
  1480.      * @OA\Response(
  1481.      *     response=200,
  1482.      *     description = "Список адресов",
  1483.      *     @OA\Schema(
  1484.      *        @OA\Property(property = "places", type = "object", description = "Список адресов",
  1485.      *          @OA\Property(property = "description", type = "string", description = "Описание"),
  1486.      *          @OA\Property(property = "city", type = "string", description = "Город"),
  1487.      *          @OA\Property(property = "street", type = "string", description = "Улица"),
  1488.      *          @OA\Property(property = "house", type = "string", description = "Дом"),
  1489.      *          @OA\Property(property = "latitude", type = "string", description = "Широта"),
  1490.      *          @OA\Property(property = "longitude", type = "string", description = "Долгота"),
  1491.      *          @OA\Property(property = "workingHours", type = "string", description = "Время работы"),
  1492.      *          @OA\Property(property = "id", type = "iteger", description = "ID"),
  1493.      *         ),
  1494.      *     )
  1495.      * )
  1496.      * @OA\Parameter(
  1497.      *     name="offerID",
  1498.      *     in="path",
  1499.      *     description="ID акции",
  1500.      *     @OA\Schema(type="string"),
  1501.      * )
  1502.      */
  1503.     public function getGeoLocationsInfoAction(
  1504.         OfferCacheService $offerCacheService,
  1505.         GeoLocationService $geoLocationService,
  1506.         PhoneService $phoneService,
  1507.         $offerID
  1508.     ) {
  1509.         $entityManager $this->getDoctrine()->getManager();
  1510.         $offer $offerCacheService->getOffer($offerIDfalsetrue);
  1511.         if (!$offer) {
  1512.             $offer $entityManager->find(Offer::class, $offerID);
  1513.         }
  1514.         return $this->getResponseWithoutUser([
  1515.             'places' => $geoLocationService->getPlace($offer),
  1516.         ], 200);
  1517.     }
  1518.     /**
  1519.      * Список телефонов
  1520.      * @Route("/mobile/api/v2/phone-numbers/{offerID}", methods={"GET"});
  1521.      * @OA\Tag(name="Phone numbers")
  1522.      * @OA\Response(
  1523.      *     response=200,
  1524.      *     description = "Список телефонов",
  1525.      *     @OA\Schema(
  1526.      *        @OA\Property(property = "phones", type = "object", description = "Список телефонов",
  1527.      *          @OA\Property(property = "number", type = "string", description = "Номер"),
  1528.      *          @OA\Property(property = "link", type = "string", description = "Линк"),
  1529.      *          @OA\Property(property = "label", type = "string", description = "Ярлык"),
  1530.      *          @OA\Property(property = "id", type = "integer", description = "ID"),
  1531.      *          @OA\Property(property = "geoLocationId", type = "integer", description = "geo location id"),
  1532.      *         ),
  1533.      *     )
  1534.      * )
  1535.      * @OA\Parameter(
  1536.      *     name="offerID",
  1537.      *     in="path",
  1538.      *     description="ID акции",
  1539.      *     @OA\Schema(type="string"),
  1540.      * )
  1541.      */
  1542.     public function getPhoneNumbersInfoAction(
  1543.         OfferCacheService $offerCacheService,
  1544.         PhoneService $phoneService,
  1545.         $offerID
  1546.     ) {
  1547.         $entityManager $this->getDoctrine()->getManager();
  1548.         $offer $offerCacheService->getOffer($offerIDfalsetrue);
  1549.         if (!$offer) {
  1550.             $offer $entityManager->find(Offer::class, $offerID);
  1551.         }
  1552.         return $this->getResponseWithoutUser([
  1553.             'phones' => $phoneService->getPhone($offer),
  1554.         ], 200);
  1555.     }
  1556.     /**
  1557.      * Список акций по категории
  1558.      * @Route("/mobile/api/v2/oplati/category/{categoryID}/{pageNumber}", methods={"GET"});
  1559.      * @OA\Response(
  1560.      *     response = 200,
  1561.      *     description = "Список акций по категории",
  1562.      *     @OA\Schema(
  1563.      *        @OA\Property(property = "offers", type = "object", description = "акции",
  1564.      *          @OA\Property(property = "ID", type = "integer", description = "ID акции"),
  1565.      *          @OA\Property(property = "name", type = "varchar", description = "название акции"),
  1566.      *          @OA\Property(property = "regularPrice", type = "decimal", description = "обычная цена"),
  1567.      *          @OA\Property(property = "offerPrice", type = "decimal", description = "цена со скидкой"),
  1568.      *          @OA\Property(property = "discountPercent", type = "varchar", description = "процент скидки"),
  1569.      *          @OA\Property(property = "saleCount", type = "integer", description = "количество проданных кодов"),
  1570.      *          @OA\Property(property = "imageURL", type = "varchar", description = "URL изображения для тизера"),
  1571.      *          @OA\Property(property = "verticalImageUrl", type = "varchar", description = "URL вертикального изображения для тизера"),
  1572.      *          @OA\Property(property = "activeTill", type = "datetime", description = "дата окончания акции"),
  1573.      *          @OA\Property(property = "rating", type = "decimal", description = "рейтинг акции"),
  1574.      *          @OA\Property(property = "codeCost", type = "varchar", description = "цена кода"),
  1575.      *          @OA\Property(property = "phoneNumbersWithoutLocation", type = "object", description = "номера телефонов без местоположения",
  1576.      *              @OA\Property(property = "phoneNumber", type = "varchar", description = "номер телефона"),
  1577.      *              @OA\Property(property = "label", type = "varchar", description = "метка"),
  1578.      *          ),
  1579.      *          @OA\Property(property = "locations", type = "object", description = "местоположение",
  1580.      *              @OA\Property(property = "address", type = "varchar", description = "адрес"),
  1581.      *              @OA\Property(property = "workingHours", type = "text", description = "время роботы"),
  1582.      *              @OA\Property(property = "phoneNumbers", type = "object", description = "номера телефонов",
  1583.      *                  @OA\Property(property = "phoneNumber", type = "varchar", description = "номер телефона"),
  1584.      *                  @OA\Property(property = "label", type = "varchar", description = "метка"),
  1585.      *              ),
  1586.      *              @OA\Property(property = "description", type = "varchar", description = "описание"),
  1587.      *              @OA\Property(property = "geoLocation", type = "varchar", description = "массив координат"),
  1588.      *          ),
  1589.      *          @OA\Property(property = "visitCount", type = "integer", description = "количество посещений"),
  1590.      *          @OA\Property(property = "address", type = "varchar", description = "адрес"),
  1591.      *          @OA\Property(property = "offerType", type = "integer", description = "тип акции"),
  1592.      *          @OA\Property(property = "isFreeCode", type = "boolean", description = "бесплатный ли код"),
  1593.      *          @OA\Property(property = "companyLogoImage", type = "object", description = "URL лого компании"),
  1594.      *        ),
  1595.      *        @OA\Property(property = "isLast", type = "boolean", description = "последняя ли страница"),
  1596.      *        @OA\Property(property = "offersCount", type = "integer", description = "количество акций"),
  1597.      *        @OA\Property(property = "userBalance", type = "varchar", description = "баланс пользователя"),
  1598.      *        type="object",
  1599.      *     )
  1600.      * )
  1601.      * @OA\Parameter(
  1602.      *     name = "categoryID",
  1603.      *     in = "path",
  1604.      *     description = "ID категории",
  1605.      *     required = true,
  1606.      *     @OA\Schema(type="integer"),
  1607.      *)
  1608.      * @OA\Parameter(
  1609.      *     name = "pageNumber",
  1610.      *     in = "path",
  1611.      *     description = "номер страницы",
  1612.      *     required = true,
  1613.      *     @OA\Schema(type="integer"),
  1614.      *)
  1615.      * @OA\Parameter(
  1616.      *     name = "HTTP-SLIVKi-USER-TOKEN",
  1617.      *     in = "header",
  1618.      *     description = "Токен юзера",
  1619.      *     required = true,
  1620.      *     @OA\Schema(type="string"),
  1621.      *)
  1622.      * @OA\Tag(name="Category")
  1623.      */
  1624.     public function getOffersForOplatiAction(
  1625.         OfferCacheService $offerCacheService,
  1626.         MobApiCacheService $mobApiCacheService,
  1627.         $categoryID,
  1628.         $pageNumber
  1629.     ) {
  1630.         ini_set('memory_limit''4g');
  1631.         $offers = [];
  1632.         $tmpPageNumber 1;
  1633.         do {
  1634.             $result $mobApiCacheService->getOffersByCategoryIDCached($categoryID$tmpPageNumber);
  1635.             $offers array_merge($offers$result['offers']);
  1636.             $tmpPageNumber++;
  1637.         } while (!$result['isLast']);
  1638.         $offerRepository $this->getDoctrine()->getRepository(Offer::class);
  1639.         foreach ($offers as $key => &$offerArray) {
  1640.             $offer $offerCacheService->getOffer($offerArray['ID']);
  1641.             if ($offer) {
  1642.                 if ($offer->isBuyCodeDisable() || $offer->isHideInApp() || !$offer->isInActivePeriod()) {
  1643.                     unset($offers[$key]);
  1644.                     continue;
  1645.                 }
  1646.                 $isFreeCode $offerRepository->isOfferFreeForUser($offer$this->getUser());
  1647.                 if ($isFreeCode) {
  1648.                     unset($offers[$key]);
  1649.                     continue;
  1650.                 }
  1651.                 $offerArray['isFreeCode'] = $isFreeCode;
  1652.             }
  1653.         }
  1654.         $offersCount count($offers);
  1655.         $offset = ($pageNumber 1) * MobApiCacheService::OFFERS_PER_PAGE;
  1656.         $offersSlice array_slice($offers$offsetMobApiCacheService::OFFERS_PER_PAGE);
  1657.         return $this->getResponse([
  1658.             'offersCount' => $offersCount,
  1659.             'isLast' => ($offset MobApiCacheService::OFFERS_PER_PAGE) >= $offersCount,
  1660.             'offers' => $offersSlice
  1661.         ], 200);
  1662.     }
  1663. }