src/Controller/MobileApi/OfferController.php line 239

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