src/Controller/MobileApi/OfferController.php line 231

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