src/AtechNet/CoreBundle/Repository/ProductRepository.php line 72

Open in your IDE?
  1. <?php
  2. namespace App\AtechNet\CoreBundle\Repository;
  3. use App\AtechNet\CoreBundle\Entity\Album;
  4. use App\AtechNet\CoreBundle\Entity\Attribute;
  5. use App\AtechNet\CoreBundle\Entity\AttributeOption;
  6. use App\AtechNet\CoreBundle\Entity\Basket;
  7. use App\AtechNet\CoreBundle\Entity\BasketItem;
  8. use App\AtechNet\CoreBundle\Entity\CategoryPage;
  9. use App\AtechNet\CoreBundle\Entity\V2Attribute;
  10. use App\AtechNet\CoreBundle\Entity\Domain;
  11. use App\AtechNet\CoreBundle\Entity\ErrorCode;
  12. use App\AtechNet\CoreBundle\Entity\Notification;
  13. use App\AtechNet\CoreBundle\Entity\NotificationConfig;
  14. use App\AtechNet\CoreBundle\Entity\Order;
  15. use App\AtechNet\CoreBundle\Entity\OrderComment;
  16. use App\AtechNet\CoreBundle\Entity\Photo;
  17. use App\AtechNet\CoreBundle\Entity\Product;
  18. use App\AtechNet\CoreBundle\Entity\ProductAttribute;
  19. use App\AtechNet\CoreBundle\Entity\ProductCommission;
  20. use App\AtechNet\CoreBundle\Entity\ProductContainer;
  21. use App\AtechNet\CoreBundle\Entity\ProductContainerMeta;
  22. use App\AtechNet\CoreBundle\Entity\ProductMeta;
  23. use App\AtechNet\CoreBundle\Entity\ProductModel;
  24. use App\AtechNet\CoreBundle\Entity\ProductPriceLog;
  25. use App\AtechNet\CoreBundle\Entity\ProductTag;
  26. use App\AtechNet\CoreBundle\Entity\ProductType;
  27. use App\AtechNet\CoreBundle\Entity\StockItem;
  28. use App\AtechNet\CoreBundle\Entity\StockMovement;
  29. use App\AtechNet\CoreBundle\Entity\StockMovementItem;
  30. use App\AtechNet\CoreBundle\Entity\StockPosition;
  31. use App\AtechNet\CoreBundle\Entity\User;
  32. use App\AtechNet\CoreBundle\Entity\Vendor;
  33. use App\AtechNet\CoreBundle\Helper\Furbify\MultiStore;
  34. use App\AtechNet\CoreBundle\Helper\Furbify\ProductHelper;
  35. use App\AtechNet\CoreBundle\Helper\Furbify\SeoUrlHelper;
  36. use DateTime;
  37. use Doctrine\Common\Collections\ArrayCollection;
  38. use Doctrine\DBAL\Exception;
  39. use Doctrine\DBAL\ParameterType;
  40. use Doctrine\ORM\Exception\NotSupported;
  41. use Doctrine\ORM\Exception\ORMException;
  42. use Doctrine\ORM\NonUniqueResultException;
  43. use Doctrine\ORM\NoResultException;
  44. use Doctrine\ORM\OptimisticLockException;
  45. use Doctrine\ORM\Query;
  46. use Doctrine\ORM\Query\ResultSetMappingBuilder;
  47. use Doctrine\ORM\QueryBuilder;
  48. use Doctrine\ORM\Tools\Pagination\Paginator;
  49. use Doctrine\ORM\TransactionRequiredException;
  50. use Doctrine\Persistence\ManagerRegistry;
  51. use Generator;
  52. use Psr\Cache\CacheItemPoolInterface;
  53. use Psr\SimpleCache\InvalidArgumentException;
  54. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  55. use Symfony\Component\HttpFoundation\Request;
  56. use Symfony\Component\Routing\Exception\InvalidParameterException;
  57. use Symfony\Component\Security\Core\Security;
  58. use Symfony\Contracts\Translation\TranslatorInterface;
  59. /**
  60.  * @extends AbstractRepository<Product>
  61.  *
  62.  * @method Product|null find($id, $lockMode = null, $lockVersion = null)
  63.  * @method Product|null findOneBy(array $criteria, array $orderBy = null)
  64.  * @method Product[]    findAll()
  65.  * @method Product[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
  66.  */
  67. class ProductRepository extends AbstractRepository
  68. {
  69.     public function __construct(
  70.         ManagerRegistry                $registry,
  71.         Security                       $security,
  72.         TranslatorInterface            $translator,
  73.         ParameterBagInterface          $parameterBag,
  74.         private CacheItemPoolInterface $cache
  75.     )
  76.     {
  77.         parent::__construct($registry, Product::class);
  78.         $this->security = $security;
  79.         $this->translator = $translator;
  80.         $this->parameterBag = $parameterBag;
  81.     }
  82.     public const LAST_AVG_PRICE_CACHE_KEY = 'lastAndAveragePrices.v2';
  83.     /**
  84.      * TEST_GROUP -g ProductRepositoryTest_getStockItems
  85.      * @param Product $product
  86.      * @param array $filter
  87.      * @return array
  88.      */
  89.     public function getStockItems(Product $product, array $filter = []): array
  90.     {
  91.         if (!isset($filter['inStock'])) {
  92.             $filter['inStock'] = null;
  93.         }
  94.         if (!isset($filter['available'])) {
  95.             $filter['available'] = null;
  96.         }
  97.         if (!empty($filter['sortBy'])) {
  98.             if ($filter['sortBy'] == 'created-asc') {
  99.                 $orderBy = " ORDER BY si.created ASC, sm.created ASC";
  100.             } else {
  101.                 $orderBy = " ORDER BY si.created DESC, sm.created ASC";
  102.             }
  103.         } else {
  104.             $orderBy = " ORDER BY si.created DESC, sm.created ASC";
  105.         }
  106.         // Entity Manager
  107.         $entityManager = $this->getEntityManager();
  108.         //⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
  109.         //⢻⣿⡗⢶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣄
  110.         //⠀⢻⣇⠀⠈⠙⠳⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⠶⠛⠋⣹⣿⡿
  111.         //⠀⠀⠹⣆⠀⠀⠀⠀⠙⢷⣄⣀⣀⣀⣤⣤⣤⣄⣀⣴⠞⠋⠉⠀⠀⠀⢀⣿⡟⠁
  112.         //⠀⠀⠀⠙⢷⡀⠀⠀⠀⠀⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀
  113.         //⠀⠀⠀⠀⠈⠻⡶⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣠⡾⠋⠀⠀⠀⠀
  114.         //⠀⠀⠀⠀⠀⣼⠃⠀⢠⠒⣆⠀⠀⠀⠀⠀⠀⢠⢲⣄⠀⠀⠀⢻⣆⠀⠀⠀⠀⠀
  115.         //⠀⠀⠀⠀⢰⡏⠀⠀⠈⠛⠋⠀⢀⣀⡀⠀⠀⠘⠛⠃⠀⠀⠀⠈⣿⡀⠀⠀⠀⠀
  116.         //⠀⠀⠀⠀⣾⡟⠛⢳⠀⠀⠀⠀⠀⣉⣀⠀⠀⠀⠀⣰⢛⠙⣶⠀⢹⣇⠀⠀⠀⠀
  117.         //⠀⠀⠀⠀⢿⡗⠛⠋⠀⠀⠀⠀⣾⠋⠀⢱⠀⠀⠀⠘⠲⠗⠋⠀⠈⣿⠀⠀⠀⠀
  118.         //⠀⠀⠀⠀⠘⢷⡀⠀⠀⠀⠀⠀⠈⠓⠒⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀⠀⠀
  119.         //⠀⠀⠀⠀⠀⠈⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣧⠀⠀⠀
  120.         // mikor meglattam
  121.         //-> Result Mapping
  122.         $rsm = new ResultSetMappingBuilder($entityManager, ResultSetMappingBuilder::COLUMN_RENAMING_CUSTOM);
  123.         //-> Stock Item ->
  124.         // Alias
  125.         $alias = 'si';
  126.         // Entity Name
  127.         $entityName = 'App\AtechNet\CoreBundle\Entity\StockItem';
  128.         // Add Entity
  129.         $rsm->addEntityResult($entityName, $alias);
  130.         // Meta Data of Entity
  131.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  132.         // Fields of Entity
  133.         $fields = $entityMetaData->getFieldNames();
  134.         foreach ($fields as $field) {
  135.             // Column
  136.             $column = $entityMetaData->getColumnName($field);
  137.             // Add Field
  138.             $rsm->addFieldResult($alias, $column, $field, $entityName);
  139.         }
  140.         //<- Stock Item
  141.         //-> Photo Album ->
  142.         // Alias
  143.         $alias = 'album';
  144.         // Entity Name
  145.         $entityName = 'App\AtechNet\CoreBundle\Entity\Album';
  146.         // Add Joined Entity
  147.         $rsm->addJoinedEntityResult($entityName, $alias, 'si', 'album');
  148.         // Meta Data of Entity
  149.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  150.         // Fields of Entity
  151.         $fields = $entityMetaData->getFieldNames();
  152.         foreach ($fields as $field) {
  153.             // Column
  154.             $column = $entityMetaData->getColumnName($field);
  155.             // Add Field
  156.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  157.         }
  158.         //<- Photo Album
  159.         //-> Tester ->
  160.         // Alias
  161.         $alias = 't';
  162.         // Entity Name
  163.         $entityName = 'App\AtechNet\CoreBundle\Entity\User';
  164.         // Add Joined Entity
  165.         $rsm->addJoinedEntityResult($entityName, $alias, 'si', 'tester');
  166.         // Meta Data of Entity
  167.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  168.         // Fields of Entity
  169.         $fields = $entityMetaData->getFieldNames();
  170.         foreach ($fields as $field) {
  171.             // Column
  172.             $column = $entityMetaData->getColumnName($field);
  173.             // Add Field
  174.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  175.         }
  176.         //<- Tester
  177.         //-> Stock Movement Items ->
  178.         // Alias
  179.         $alias = 'smi';
  180.         // Entity Name
  181.         $entityName = StockMovementItem::class;
  182.         // Add Joined Entity
  183.         $rsm->addJoinedEntityResult($entityName, $alias, 'si', 'movementItems');
  184.         // Meta Data of Entity
  185.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  186.         // Fields of Entity
  187.         $fields = $entityMetaData->getFieldNames();
  188.         foreach ($fields as $field) {
  189.             // Column
  190.             $column = $entityMetaData->getColumnName($field);
  191.             // Add Field
  192.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  193.         }
  194.         //Stock Movement Items
  195.         //-> Shipment Item ->
  196.         // Alias
  197.         $alias = 'shi';
  198.         // Entity Name
  199.         $entityName = 'App\AtechNet\CoreBundle\Entity\AikenShipmentItem';
  200.         // Add Joined Entity
  201.         $rsm->addJoinedEntityResult($entityName, $alias, 'si', 'aikenShipmentItem');
  202.         // Meta Data of Entity
  203.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  204.         // Fields of Entity
  205.         $fields = $entityMetaData->getFieldNames();
  206.         foreach ($fields as $field) {
  207.             // Column
  208.             $column = $entityMetaData->getColumnName($field);
  209.             // Add Field
  210.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  211.         }
  212.         //<- Shipment Item
  213.         //-> Stock Movement ->
  214.         // Alias
  215.         $alias = 'sm';
  216.         // Entity Name
  217.         $entityName = 'App\AtechNet\CoreBundle\Entity\StockMovement';
  218.         // Add Joined Entity
  219.         $rsm->addJoinedEntityResult($entityName, $alias, 'smi', 'movement');
  220.         // Meta Data of Entity
  221.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  222.         // Fields of Entity
  223.         $fields = $entityMetaData->getFieldNames();
  224.         foreach ($fields as $field) {
  225.             // Column
  226.             $column = $entityMetaData->getColumnName($field);
  227.             // Add Field
  228.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  229.         }
  230.         //<- Stock Movement
  231.         //-> Product ->
  232.         // Alias
  233.         $alias = 'p';
  234.         // Entity Name
  235.         $entityName = Product::class;
  236.         // Add Joined Entity
  237.         $rsm->addJoinedEntityResult($entityName, $alias, 'smi', 'product');
  238.         // Meta Data of Entity
  239.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  240.         // Fields of Entity
  241.         $fields = $entityMetaData->getFieldNames();
  242.         foreach ($fields as $field) {
  243.             // Column
  244.             $column = $entityMetaData->getColumnName($field);
  245.             // Add Field
  246.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  247.         }
  248.         $rsm->addMetaResult($alias, 'p_product_model_id', 'product_model_id', false, 'integer');
  249.         // <- Product
  250.         //-> Stock ->
  251.         // Alias
  252.         $alias = 's';
  253.         // Entity Name
  254.         $entityName = 'App\AtechNet\CoreBundle\Entity\Stock';
  255.         // Add Joined Entity
  256.         $rsm->addJoinedEntityResult($entityName, $alias, 'sm', 'stock');
  257.         // Meta Data of Entity
  258.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  259.         // Fields of Entity
  260.         $fields = $entityMetaData->getFieldNames();
  261.         foreach ($fields as $field) {
  262.             // Column
  263.             $column = $entityMetaData->getColumnName($field);
  264.             // Add Field
  265.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  266.         }
  267.         //<- Stock
  268.         //-> Customer ->
  269.         // Alias
  270.         $alias = 'c';
  271.         // Entity Name
  272.         $entityName = 'App\AtechNet\CoreBundle\Entity\Customer';
  273.         // Add Joined Entity
  274.         $rsm->addJoinedEntityResult($entityName, $alias, 'sm', 'merchant');
  275.         // Meta Data of Entity
  276.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  277.         // Fields of Entity
  278.         $fields = $entityMetaData->getFieldNames();
  279.         foreach ($fields as $field) {
  280.             // Column
  281.             $column = $entityMetaData->getColumnName($field);
  282.             // Add Field
  283.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  284.         }
  285.         //<- Customer
  286.         //-> Domain ->
  287.         // Alias
  288.         $alias = 'd';
  289.         // Entity Name
  290.         $entityName = Domain::class;
  291.         // Add Joined Entity
  292.         $rsm->addJoinedEntityResult($entityName, $alias, 'c', 'domain');
  293.         // Meta Data of Entity
  294.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  295.         // Fields of Entity
  296.         $fields = $entityMetaData->getFieldNames();
  297.         foreach ($fields as $field) {
  298.             // Column
  299.             $column = $entityMetaData->getColumnName($field);
  300.             // Add Field
  301.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  302.         }
  303.         //<- Domain
  304.         //-> Created By ->
  305.         // Alias
  306.         $alias = 'cr';
  307.         // Entity Name
  308.         $entityName = 'App\AtechNet\CoreBundle\Entity\User';
  309.         // Add Joined Entity
  310.         $rsm->addJoinedEntityResult($entityName, $alias, 'sm', 'createdBy');
  311.         // Meta Data of Entity
  312.         $entityMetaData = $entityManager->getClassMetadata($entityName);
  313.         // Fields of Entity
  314.         $fields = $entityMetaData->getFieldNames();
  315.         foreach ($fields as $field) {
  316.             // Column
  317.             $column = $entityMetaData->getColumnName($field);
  318.             // Add Field
  319.             $rsm->addFieldResult($alias, $alias . '_' . $column, $field, $entityName);
  320.         }
  321.         //<- Created By
  322.         //<- Result Mapping
  323.         // Select Expression
  324.         $select = $rsm->__toString();
  325.         // SQL
  326.         $sql = "SELECT $select 
  327.             FROM stock_items AS si 
  328.                 LEFT JOIN albums AS album ON (album.id = si.photo_album_id) 
  329.                 LEFT JOIN users AS t ON ( t.id = si.tester_id ) 
  330.                 INNER JOIN stock_movement_items_stock_items AS smisi ON ( smisi.stockitem_id = si.id ) 
  331.                 INNER JOIN stock_movement_item AS smi ON ( smi.id = smisi.stockmovementitem_id ) 
  332.                 LEFT JOIN aiken_shipment_item AS shi ON ( shi.id = si.aiken_shipment_item_id ) 
  333.                 INNER JOIN stock_movements AS sm ON ( sm.id = smi.stock_movement_id ) 
  334.                 LEFT JOIN stocks AS s ON ( s.id = sm.stock_id ) 
  335.                 LEFT JOIN customers AS c ON ( c.id = sm.merchant_id ) 
  336.                 LEFT JOIN domains AS d ON ( d.id = c.domain_id ) 
  337.                 LEFT JOIN users AS cr ON ( cr.id = sm.created_by ) 
  338.                 LEFT JOIN products AS p ON ( p.id = smi.product_id )
  339.             WHERE smi.product_id = :product";
  340.         // Where Conditions
  341.         $where = $parameters = [];
  342.         if (!empty($filter['filterEsetCommentSerials'])) {
  343.             $sql .= " AND si.serial_number NOT IN(:usedEsetSerials)";
  344.             $parameters['usedEsetSerials'] = $filter['filterEsetCommentSerials'];
  345.         }
  346.         if (!empty($filter['filterSerialNumber'])) {
  347.             $where[] = "si.serial_number LIKE :serialNumber";
  348.             $parameters['serialNumber'] = $filter['filterSerialNumber'];
  349.         }
  350.         if (!is_null($filter['inStock'])) {
  351.             $where[] = "si.in_stock = :inStock";
  352.             $parameters['inStock'] = ($filter['inStock'] ? 1 : 0);
  353.         }
  354.         if (!is_null($filter['available'])) {
  355.             if ($filter['available']) {
  356.                 $where[] = "( si.in_stock = 1 AND si.available = 1 )";
  357.             } else {
  358.                 $where[] = "( si.in_stock = 0 OR si.available = 0 )";
  359.             }
  360.         }
  361.         if ( !empty($filter['filterBacklit']) && $filter['filterBacklit'] === true ) {
  362.             $where[] = "si.keyboard_backlight = :filterBacklit";
  363.             $parameters['filterBacklit'] = 1;
  364.         }
  365.         if ($filter['inStock'] === true) {
  366.             $where[] = "( SELECT COUNT(_sm.id) FROM stock_movements AS _sm WHERE _sm.id = ( SELECT sm2.id FROM stock_items AS si2 INNER JOIN stock_movement_items_stock_items AS smisi2 ON ( smisi2.stockitem_id = si2.id ) INNER JOIN stock_movement_item AS smi2 ON ( smi2.id = smisi2.stockmovementitem_id AND smi2.product_id = :product ) INNER JOIN stock_movements AS sm2 ON ( sm2.id = smi2.stock_movement_id ) WHERE si2.id = si.id ORDER BY sm2.created DESC LIMIT 1 ) AND _sm.type = " . StockMovement::TYPE_OUT . " ) = 0";
  367.         }
  368.         if (!empty($where)) {
  369.             $sql .= ' AND ' . implode(' AND ', $where);
  370.         }
  371.         // Order By
  372.         $sql .= $orderBy;
  373.         // Query
  374.         $query = $entityManager->createNativeQuery($sql, $rsm);
  375.         // Quer Parameters
  376.         $parameters['product'] = $product->getId();
  377.         $query->setParameters($parameters);
  378.         // Results
  379.         $results = $query->getResult();
  380.         if (!empty($filter['sortBy'])) {
  381.             switch ($filter['sortBy']) {
  382.                 case 'purchase-price-desc':
  383.                     // Sort Order
  384.                     $sortOrder = [];
  385.                     foreach ($results as $index => $result) {
  386.                         $lastInMoveItem = null;
  387.                         foreach ($result->getMovementItems() as $movementItem) {
  388.                             if ($movementItem->getMovement()->getType() == StockMovement::TYPE_IN) {
  389.                                 $lastInMoveItem = $movementItem;
  390.                                 break;
  391.                             }
  392.                         }
  393.                         $purchasePrice = $lastInMoveItem->getTotalPricePerItem() / $lastInMoveItem->getMovement()->getMerchant()->getDomain()->getPriceFactor();
  394.                         $sortOrder[$index] = $purchasePrice;
  395.                     }
  396.                     arsort($sortOrder);
  397.                     $_results = [];
  398.                     foreach ($sortOrder as $index => $value) {
  399.                         $_results[] = $results[$index];
  400.                     }
  401.                     $results = $_results;
  402.                     break;
  403.                 case 'purchase-price-asc':
  404.                     // Sort Order
  405.                     $sortOrder = [];
  406.                     foreach ($results as $index => $result) {
  407.                         $lastInMoveItem = null;
  408.                         foreach ($result->getMovementItems() as $movementItem) {
  409.                             if ($movementItem->getMovement()->getType() == StockMovement::TYPE_IN) {
  410.                                 $lastInMoveItem = $movementItem;
  411.                                 break;
  412.                             }
  413.                         }
  414.                         $purchasePrice = $lastInMoveItem->getTotalPricePerItem() / $lastInMoveItem->getMovement()->getMerchant()->getDomain()->getPriceFactor();
  415.                         $sortOrder[$index] = $purchasePrice;
  416.                     }
  417.                     asort($sortOrder);
  418.                     $_results = [];
  419.                     foreach ($sortOrder as $index => $value) {
  420.                         $_results[] = $results[$index];
  421.                     }
  422.                     $results = $_results;
  423.                     break;
  424.                 case 'selling-price-desc':
  425.                     // Sort Order
  426.                     $sortOrder = [];
  427.                     foreach ($results as $index => $result) {
  428.                         $lastMoveItem = $result->getFirstMovementItem();
  429.                         if ($lastMoveItem->getMovement()->getType() == StockMovement::TYPE_OUT) {
  430.                             $sellingPrice = $lastMoveItem->getPrice() / $lastMoveItem->getQuantity() / $lastMoveItem->getMovement()->getMerchant()->getDomain()->getPriceFactor();
  431.                         } else {
  432.                             $sellingPrice = 0;
  433.                         }
  434.                         $sortOrder[$index] = $sellingPrice;
  435.                     }
  436.                     arsort($sortOrder);
  437.                     $_results = [];
  438.                     foreach ($sortOrder as $index => $value) {
  439.                         $_results[] = $results[$index];
  440.                     }
  441.                     $results = $_results;
  442.                     break;
  443.                 case 'selling-price-asc':
  444.                     // Sort Order
  445.                     $sortOrder = [];
  446.                     foreach ($results as $index => $result) {
  447.                         $lastMoveItem = $result->getFirstMovementItem();
  448.                         if ($lastMoveItem->getMovement()->getType() == StockMovement::TYPE_OUT) {
  449.                             $sellingPrice = $lastMoveItem->getPrice() / $lastMoveItem->getQuantity() / $lastMoveItem->getMovement()->getMerchant()->getDomain()->getPriceFactor();
  450.                         } else {
  451.                             $sellingPrice = 0;
  452.                         }
  453.                         $sortOrder[$index] = $sellingPrice;
  454.                     }
  455.                     asort($sortOrder);
  456.                     $_results = [];
  457.                     foreach ($sortOrder as $index => $value) {
  458.                         $_results[] = $results[$index];
  459.                     }
  460.                     $results = $_results;
  461.                     break;
  462.             }
  463.         }
  464.         // Limit
  465.         if (isset($filter['limit']) && $filter['limit'] > 0) {
  466.             $results = array_slice($results, $filter['offset'], $filter['limit']);
  467.         }
  468.         return $results;
  469.     }
  470.     /**
  471.      * @param int $productId
  472.      * @param bool $isContainer
  473.      * @return bool
  474.      */
  475.     public function hasStockItemAlbum(int $productId, bool $isContainer = false): bool
  476.     {
  477.         return count($this->getResultForStockItemAlbums($productId, $isContainer)) > 0;
  478.     }
  479.     /**
  480.      * @param int $productId
  481.      * @param bool $isContainer
  482.      * @return null|array
  483.      */
  484.     public function getStockItemAlbums(int $productId, bool $isContainer = false): ?array
  485.     {
  486.         $stockItems = $this->getResultForStockItemAlbums($productId, $isContainer);
  487.         $albums = [];
  488.         if (count($stockItems) > 0) {
  489.             foreach ($stockItems as $si) {
  490.                 $albums[$si->getId()]['albumId'] = $si->getAlbum()->getId();
  491.                 $albums[$si->getId()]['aikenCode'] = $si->getAikenCode();
  492.             }
  493.         }
  494.         return $albums;
  495.     }
  496.     /**
  497.      * TEST_GROUP -g ProductRepositoryTest_getResultForStockItemAlbums
  498.      * @param int $productId
  499.      * @param bool $isContainer
  500.      * @return array
  501.      */
  502.     private function getResultForStockItemAlbums(int $productId, bool $isContainer = false): array
  503.     {
  504.         if ($isContainer) {
  505.             $containerProducts = $this->getEntityManager()->getConnection()->fetchAllAssociative("SELECT child_id FROM product_container WHERE product_id = ?", [$productId], [ParameterType::INTEGER]);
  506.             if ($containerProducts) {
  507.                 $subQuery_1 = $subQuery_2 = "IDENTITY(mi.product) IN (" . implode(',', array_map(function ($product) {
  508.                         return (int)$product['child_id'];
  509.                     }, $containerProducts)) . ")";
  510.             } else {
  511.                 $subQuery_1 = "IDENTITY(mi.product) IN ( SELECT IDENTITY(pc1.child) FROM AtechNetCoreBundle:ProductContainer pc1 WHERE pc1.product = $productId )";
  512.                 $subQuery_2 = "IDENTITY(mi.product) IN ( SELECT IDENTITY(pc2.child) FROM AtechNetCoreBundle:ProductContainer pc2 WHERE pc2.product = $productId )";
  513.             }
  514.         } else {
  515.             $subQuery_1 = $subQuery_2 = "IDENTITY(mi.product) = $productId";
  516.         }
  517.         $query = $this->getEntityManager()
  518.             ->createQuery(
  519.                 "SELECT si
  520.                     FROM AtechNetCoreBundle:StockItem si
  521.                     INNER JOIN si.movementItems mi
  522.                     INNER JOIN mi.movement sm WITH $subQuery_1 
  523.                     INNER JOIN AtechNetCoreBundle:Photo photo WITH photo.album = si.album
  524.                     WHERE
  525.                         si.inStock = 1 AND
  526.                         si.available = 1 AND
  527.                         si.album > 0 AND 
  528.                         $subQuery_2 AND
  529.                         si.aikenCode NOT IN (
  530.                             SELECT s.aikenCode
  531.                             FROM AtechNetCoreBundle:StockItem s
  532.                             INNER JOIN AtechNetCoreBundle:OrderComment oc WITH oc.text LIKE '%" . StockItem::PREFIX_FOR_SELECTED_ITEMS . "%'
  533.                             INNER JOIN oc.order o WITH o.status <= " . Order::STATUS_WAITING_FOR_ANSWER . "
  534.                             WHERE
  535.                                 IDENTITY(s.album) > 0 AND
  536.                                 oc.text LIKE CONCAT('%', s.aikenCode, '%') AND
  537.                                 oc.type = " . OrderComment::TYPE_FOR_PREPARATION . " AND
  538.                                 IDENTITY(oc.createdBy) = " . User::CENTRALIS_USER_ID . "
  539.                             )
  540.                 GROUP BY si.id"
  541.             );
  542.         return $query->getResult();
  543.     }
  544.     /**
  545.      * @param StockItem $stockItem
  546.      *
  547.      * @return array
  548.      */
  549.     public function getVisibleErrorCodes(StockItem $stockItem): array
  550.     {
  551.         return $this->getEntityManager()->getRepository(ErrorCode::class)
  552.             ->createQueryBuilder('ec')
  553.             ->where('ec.visibleOnWeb = 1')
  554.             ->andWhere('ec.code IN (:errorCodes)')
  555.             ->setParameter('errorCodes', explode(' ', $stockItem->getErrorCodes()))
  556.             ->getQuery()
  557.             ->getResult();
  558.     }
  559.     /**
  560.      * TEST_GROUP -g ProductRepositoryTest_getTotalStockItems
  561.      * @param Product $product
  562.      * @param array $filter
  563.      * @return int
  564.      * @throws Exception
  565.      * @throws \Doctrine\DBAL\Driver\Exception
  566.      */
  567.     public function getTotalStockItems(Product $product, array $filter = []): int
  568.     {
  569.         if (!isset($filter['inStock'])) {
  570.             $filter['inStock'] = null;
  571.         }
  572.         if (!isset($filter['available'])) {
  573.             $filter['available'] = null;
  574.         }
  575.         $entityManager = $this->getEntityManager();
  576.         $connection = $entityManager->getConnection();
  577.         $sql = "SELECT COUNT(DISTINCT si.id) AS total FROM stock_items AS si INNER JOIN stock_movement_items_stock_items AS smisi ON ( smisi.stockitem_id = si.id ) INNER JOIN stock_movement_item AS smi ON ( smi.id = smisi.stockmovementitem_id ) INNER JOIN stock_movements AS sm ON ( sm.id = smi.stock_movement_id )";
  578.         // Where Conditions
  579.         $where = $parameters = [];
  580.         $where[] = "smi.product_id = ?";
  581.         $parameters[] = $product->getId();
  582.         if (!empty($filter['filterSerialNumber'])) {
  583.             $where[] = "si.serial_number LIKE ?";
  584.             $parameters[] = $filter['filterSerialNumber'];
  585.         }
  586.         if (!is_null($filter['inStock'])) {
  587.             $where[] = "si.in_stock = ?";
  588.             $parameters[] = ($filter['inStock'] ? 1 : 0);
  589.         }
  590.         if (!is_null($filter['available'])) {
  591.             if ($filter['available']) {
  592.                 $where[] = "( si.in_stock = 1 AND si.available = 1 )";
  593.             } else {
  594.                 $where[] = "( si.in_stock = 0 OR si.available = 0 )";
  595.             }
  596.         }
  597.         if ($filter['inStock'] === true) {
  598.             $where[] = "( SELECT COUNT(_sm.id) FROM stock_movements AS _sm WHERE _sm.id = ( SELECT sm2.id FROM stock_items AS si2 INNER JOIN stock_movement_items_stock_items AS smisi2 ON ( smisi2.stockitem_id = si2.id ) INNER JOIN stock_movement_item AS smi2 ON ( smi2.id = smisi2.stockmovementitem_id AND smi2.product_id = ? ) INNER JOIN stock_movements AS sm2 ON ( sm2.id = smi2.stock_movement_id ) WHERE si2.id = si.id ORDER BY sm2.created DESC LIMIT 1 ) AND _sm.type = " . StockMovement::TYPE_OUT . " ) = 0";
  599.             $parameters[] = $product->getId();
  600.         }
  601.         if (!empty($where)) {
  602.             $sql .= ' WHERE ' . implode(' AND ', $where);
  603.         }
  604.         $query = $connection->executeQuery($sql, $parameters);
  605.         $results = $query->fetchAllAssociative();
  606.         $result = array_pop($results);
  607.         return !empty($result['total']) ? (int)$result['total'] : 0;
  608.     }
  609.     /**
  610.      * TEST_GROUP -g ProductRepositoryTest_getStockItemsQuantity
  611.      * @param Product $product
  612.      * @param null|bool $inStock
  613.      * @param null|bool $available
  614.      * @param null $all
  615.      * @return int|array
  616.      */
  617.     public function getStockItemsQuantity(Product $product, bool $inStock = null, bool $available = null, $all = null): int|array
  618.     {
  619.         $allInQuantity = 0;
  620.         $allOutQuantity = 0;
  621.         $allNeutralQuantity = 0;
  622.         $queryArray = [
  623.             'In' => StockMovement::TYPE_IN,
  624.             'Out' => StockMovement::TYPE_OUT,
  625.             'Neutral' => StockMovement::TYPE_NEUTRAL . ' AND sm.subType != ' . StockMovement::SUB_TYPE_NEUTRAL_IN_STOCK,
  626.         ];
  627.         foreach ($queryArray as $type => $queryWhere) {
  628.             $quantityQuery = 'SELECT SUM(mi.quantity) as stockItemQuantity
  629.             FROM AtechNetCoreBundle:StockMovementItem mi
  630.             JOIN mi.movement sm
  631.             WHERE mi.product = :product
  632.             AND sm.type = ' . $queryWhere;
  633.             $results = $this->getEntityManager()
  634.                 ->createQuery($quantityQuery)
  635.                 ->setParameter('product', $product->getId())
  636.                 ->getResult();
  637.             ${'all' . $type . 'Quantity'} = $results['0']['stockItemQuantity'];
  638.             if (is_null(${'all' . $type . 'Quantity'})) {
  639.                 ${'all' . $type . 'Quantity'} = 0;
  640.             }
  641.         }
  642.         if ($all) {
  643.             return [
  644.                 'in' => $allInQuantity,
  645.                 'out' => $allOutQuantity,
  646.                 'neutral' => $allNeutralQuantity,
  647.             ];
  648.         }
  649.         if (!is_null($inStock) || !is_null($available)) {
  650.             if (!is_null($inStock) && $inStock && is_null($available)) { // in stock stockItems count
  651.                 return $allInQuantity - $allOutQuantity;
  652.             }
  653.             if (!is_null($inStock) && !$inStock && is_null($available)) { // out of stock stockItems count
  654.                 return $allOutQuantity;
  655.             }
  656.             if (is_null($inStock) && !is_null($available) && $available) { // available stockItems count
  657.                 return $allInQuantity - $allOutQuantity - $allNeutralQuantity;
  658.             }
  659.             if (is_null($inStock) && !is_null($available) && !$available) { // unavailable stockItems count
  660.                 return $allInQuantity + $allNeutralQuantity;
  661.             }
  662.         }
  663.         return $allInQuantity; // all stockItems count
  664.     }
  665.     /**
  666.      * @param Product $product
  667.      * @return array
  668.      */
  669.     public function getInStockStockItems(Product $product): array
  670.     {
  671.         return $this->getStockItems($product, [
  672.             'inStock' => true,
  673.         ]);
  674.     }
  675.     /**
  676.      * @param Product $product
  677.      * @return array
  678.      */
  679.     public function getOutOfStockStockItems(Product $product): array
  680.     {
  681.         return $this->getStockItems($product, [
  682.             'inStock' => false,
  683.         ]);
  684.     }
  685.     /**
  686.      * @param Product $product
  687.      * @return array
  688.      */
  689.     public function getAvailableStockItems(Product $product): array
  690.     {
  691.         return $this->getStockItems($product, [
  692.             'available' => true,
  693.         ]);
  694.     }
  695.     /**
  696.      * @param Product $product
  697.      * @return array
  698.      */
  699.     public function getUnavailableStockItems(Product $product): array
  700.     {
  701.         return $this->getStockItems($product, [
  702.             'available' => false,
  703.         ]);
  704.     }
  705.     /**
  706.      * @param Product $product
  707.      * @return array
  708.      */
  709.     public function getInStockStockItemsQuantity(Product $product)
  710.     {
  711.         if ($product->isContainer()) {
  712.             return $this->getMinAvailableOrInStock($product->getId(), 'in_stock');
  713.         }
  714.         return $this->getStockItemsQuantity($product, true);
  715.     }
  716.     /**
  717.      * @param Product $product
  718.      * @return array
  719.      */
  720.     public function getOutOfStockStockItemsQuantity(Product $product)
  721.     {
  722.         return $this->getStockItemsQuantity($product, false, null);
  723.     }
  724.     /**
  725.      * @param Product $product
  726.      * @return array
  727.      */
  728.     public function getAvailableStockItemsQuantity(Product $product)
  729.     {
  730.         if ($product->isContainer()) {
  731.             return $this->getMinAvailableOrInStock($product->getId(), 'available');
  732.         }
  733.         return $this->getStockItemsQuantity($product, null, true);
  734.     }
  735.     /**
  736.      * @param $prodId
  737.      * @param $col
  738.      * @return mixed
  739.      */
  740.     private function getMinAvailableOrInStock($prodId, $col)
  741.     {
  742.         $query = "SELECT MIN(p2.$col DIV pc.multiplier) AS min_val
  743.                 FROM products p
  744.                 JOIN product_container pc ON p.id = pc.product_id
  745.                 JOIN products p2 ON p2.id = pc.child_id
  746.                 WHERE pc.multiplier > 0 AND p.id = $prodId";
  747.         $connection = $this->getEntityManager()->getConnection();
  748.         $result = $connection->fetchAllAssociative($query);
  749.         return $result[0]['min_val'];
  750.     }
  751.     /**
  752.      * @param Product $product
  753.      * @return array
  754.      */
  755.     public function getUnavailableStockItemsQuantity(Product $product)
  756.     {
  757.         return $this->getStockItemsQuantity($product, null, false);
  758.     }
  759.     /**
  760.      * TEST_GROUP -g ProductRepositoryTest_getStockMovements
  761.      * @param Product $product
  762.      * @param array $filter
  763.      * @return array
  764.      */
  765.     public function getStockMovements(Product $product, array $filter = []): array
  766.     {
  767.         $where = [];
  768.         if ($filter['type'] > 0) {
  769.             $where[] = 'sm.type = ' . $filter['type'];
  770.         }
  771.         if ($filter['subType'] > 0) {
  772.             $where[] = 'sm.subType = ' . $filter['subType'];
  773.         }
  774.         if (isset($filter['domain']) && (int)$filter['domain'] > 0) {
  775.             $where[] = 'm.domain = ' . (int)$filter['domain'];
  776.         }
  777.         $where[] = 'p = :product';
  778.         $where = implode(" AND ", $where);
  779.         $query = $this->getEntityManager()
  780.             ->createQuery('SELECT sm, mi, PARTIAL d.{id,code}
  781.                 FROM AtechNetCoreBundle:StockMovement sm
  782.                 JOIN sm.movementItems mi
  783.                 JOIN mi.product p 
  784.                 JOIN sm.merchant m 
  785.                 LEFT JOIN sm.documents d
  786.                 WHERE ' . $where . '
  787.                 ORDER BY sm.created DESC')
  788.             ->setParameter('product', $product)
  789.             ->setMaxResults($filter['limit'])
  790.             ->setFirstResult($filter['offset']);
  791.         return iterator_to_array((new Paginator($query, true)));
  792.     }
  793.     /**
  794.      * TEST_GROUP -g ProductRepositoryTest_getTotalStockMovements
  795.      * @param Product $product
  796.      * @param array $filter
  797.      * @return int
  798.      * @throws NoResultException
  799.      * @throws NonUniqueResultException
  800.      */
  801.     public function getTotalStockMovements(Product $product, array $filter = []): int
  802.     {
  803.         $where = [];
  804.         if ((int)$filter['type'] > 0) {
  805.             $where[] = 'sm.type = ' . (int)$filter['type'];
  806.         }
  807.         if ((int)$filter['subType'] > 0) {
  808.             $where[] = 'sm.subType = ' . (int)$filter['subType'];
  809.         }
  810.         if (isset($filter['domain']) && (int)$filter['domain'] > 0) {
  811.             $where[] = 'm.domain = ' . (int)$filter['domain'];
  812.         }
  813.         $where[] = 'p = :product';
  814.         $where = implode(" AND ", $where);
  815.         return $this->getEntityManager()
  816.             ->createQuery(
  817.                 "SELECT COUNT(DISTINCT sm.id) 
  818.                 FROM AtechNetCoreBundle:StockMovement sm 
  819.                 JOIN sm.movementItems mi 
  820.                 JOIN mi.product p 
  821.                 JOIN sm.merchant m 
  822.                 WHERE $where"
  823.             )
  824.             ->setParameter('product', $product)
  825.             ->getSingleScalarResult();
  826.     }
  827.     /**
  828.      * TEST_GROUP -g ProductRepositoryTest_getLastPrices
  829.      * @param array $productIds
  830.      * @return array
  831.      * @throws Exception
  832.      */
  833.     public function getLastPrices(array $productIds = []): array
  834.     {
  835.         // Unset product ids for shipping related products
  836.         if (count($productIds) > 0) {
  837.             unset($productIds[Product::SHIPPING_ID]);
  838.             unset($productIds[Product::CASH_ON_DELIVERY_PRODUCT_ID]);
  839.             unset($productIds[Product::MPL_PACKAGE_FEE_PRODUCT_ID]);
  840.         }
  841.         $productCondition = count($productIds) > 0 ? ' AND smi.product_id IN (' . implode(',', $productIds) . ')' : '';
  842.         $connection = $this->getEntityManager()->getConnection();
  843.         // Last price - select only last in movement which has the correct product id
  844.         $data = $connection->executeQuery('
  845.             SELECT l.product_id AS id, l.last_price FROM (
  846.                 SELECT smi.product_id, (sm.extra_price/sm.quantity + smi.price/smi.quantity) / sm.exchange_rate AS last_price
  847.                 FROM stock_movement_item smi
  848.                 LEFT JOIN stock_movements sm ON sm.id = smi.stock_movement_id
  849.                 WHERE sm.type = ' . StockMovement::TYPE_IN . '
  850.                     AND (sm.extra_price > 0 OR smi.price > 0)
  851.                     ' . $productCondition . '
  852.                     ORDER BY sm.created DESC
  853.             ) l')->fetchAllAssociative();
  854.         $result = [];
  855.         foreach ($productIds as $productId) {
  856.             $result[$productId] = [
  857.                 'last_price' => 0,
  858.             ];
  859.         }
  860.         foreach ($data as $r) {
  861.             $result[$r['id']] = [
  862.                 'last_price' => $r['last_price'],
  863.             ];
  864.         }
  865.         return $result;
  866.     }
  867.     /**
  868.      * TEST_GROUP -g ProductRepositoryTest_getLastPrice
  869.      * @param int $productId
  870.      * @return array
  871.      * @throws Exception
  872.      */
  873.     public function getLastPrice(int $productId): array
  874.     {
  875.         return $this->getLastPrices([$productId]);
  876.     }
  877.     /**
  878.      * TEST_GROUP -g ProductRepositoryTest_getProductTypeAndDateSpecificAveragePrices
  879.      * @param int $productTypeId
  880.      * @param DateTime $datetime
  881.      * @param $productSavedAveragePrice
  882.      * @return int|mixed
  883.      */
  884.     public function getProductTypeAndDateSpecificAveragePrices(int $productTypeId, DateTime $datetime, $productSavedAveragePrice)
  885.     {
  886.         $eligibleProductTypes = $this->getProductTypesWithDifferentAveragePricesByDate();
  887.         if (isset($eligibleProductTypes[$productTypeId]) && $eligibleProductTypes[$productTypeId]['averagePriceDateChange'] < (int)$datetime->format('YmdHi')) {
  888.             return $eligibleProductTypes[$productTypeId]['averagePrice'];
  889.         } else {
  890.             return $productSavedAveragePrice;
  891.         }
  892.     }
  893.     /**
  894.      * TEST_GROUP -g ProductRepositoryTest_getProductTypeAndDateSpecificAveragePrices
  895.      * @return array[]
  896.      *
  897.      * DATETIME FORMAT: YmdHi
  898.      * PRICES IN EUR WITHOUT VAT
  899.      */
  900.     public function getProductTypesWithDifferentAveragePricesByDate(): array
  901.     {
  902.         return [
  903.             ProductType::PRODUCT_TYPE_ID_POWER_ADAPTER => [
  904.                 'averagePriceDateChange' => 202101010000,
  905.                 'averagePrice' => 0.1,
  906.             ],
  907.             ProductType::PRODUCT_TYPE_ID_SERVICE => [
  908.                 'averagePriceDateChange' => 202505061557,
  909.                 'averagePrice' => 0.0,
  910.             ],
  911.         ];
  912.     }
  913.     /**
  914.      * TEST_GROUP -g ProductRepositoryTest_getAvailableAndInStockCounts
  915.      * @param array $productIds
  916.      * @return array
  917.      * @throws Exception
  918.      */
  919.     public function getAvailableAndInStockCounts(array $productIds = []): array
  920.     {
  921.         $counts = $this->getEntityManager()->getConnection()->fetchAllAssociative("SELECT p.id, p.available,t3.available AS available, p.in_stock, t3.instock AS instock FROM products p JOIN (
  922.             select id, SUM(case when instock='+' then total when instock='-' then -total else 0 end) instock,
  923.             SUM(case when available='+' then total when available='-' then -total else 0 end) available
  924.             FROM (
  925.                 select id, SUM(quantity) total, LEFT(instock_available,1) instock, RIGHT(instock_available,1) available FROM (
  926.                     SELECT p.id, smi.quantity,
  927.                         case
  928.                             when sm.type = " . StockMovement::TYPE_IN . " then '++'
  929.                             when sm.type = " . StockMovement::TYPE_OUT . " then '--'
  930.                             when sm.type = " . StockMovement::TYPE_NEUTRAL . " then '0-'
  931.                             else '00'
  932.                         end AS instock_available
  933.                     FROM products p
  934.                     JOIN stock_movement_item smi ON p.id = smi.product_id
  935.                     JOIN stock_movements sm ON sm.id = smi.stock_movement_id" . (count($productIds) > 0 ? " WHERE p.id IN(" . implode(',', $productIds) . ")" : "") . "
  936.                     ) t group by id,instock_available
  937.                 ) t2 group by id
  938.             ) t3 ON t3.id = p.id
  939.             WHERE p.type_id <> " . ProductType::PRODUCT_TYPE_ID_SERVICE);
  940.         $result = [];
  941.         foreach ($counts as $r) {
  942.             $result[$r['id']] = [
  943.                 'available' => $r['available'],
  944.                 'in_stock' => $r['instock'],
  945.             ];
  946.         }
  947.         return $result;
  948.     }
  949.     /**
  950.      * TEST_GROUP -g ProductRepositoryTest_updateProductInStockAndAvailable
  951.      * @param Product $product
  952.      * @param int $count
  953.      * @return Product|null
  954.      * @throws NotSupported
  955.      * @throws ORMException
  956.      * @throws OptimisticLockException
  957.      * @throws TransactionRequiredException
  958.      * @throws \Doctrine\ORM\ORMException
  959.      */
  960.     public function updateProductInStockAndAvailable(Product $product, int $count = 0): ?Product
  961.     {
  962.         // Product over the stock movement limit are updated with command once a day in the evening
  963.         if (in_array($product->getId(), Product::getExcludededProductIdsFromUpdate())
  964.             || in_array($product->getType()->getId(), Product::getExcludededProductTypeIdsFromUpdate())
  965.             || $product->getStockMovementItems()->count() >= Product::PRODUCT_IN_STOCK_AVAILABLE_STOCK_MOVEMENT_QTY_REFRESH_LIMIT
  966.         ) {
  967.             return $product;
  968.         }
  969.         $this->getEntityManager()->refresh($product);
  970.         if ($this->updateProductInStock($product, false, $count) && $this->updateProductAvailable($product, false, $count)) {
  971.             if (!$product->isContainer()) {
  972.                 $this->updateLinkedContainerProducts($product);
  973.             }
  974.             return $product;
  975.         }
  976.         return null;
  977.     }
  978.     /**
  979.      * TEST_GROUP -g ProductRepositoryTest_updateLastAndAvgPrices
  980.      * @param ArrayCollection<Product> $products
  981.      * @return bool
  982.      * @throws InvalidArgumentException
  983.      * @throws ORMException
  984.      * @throws OptimisticLockException
  985.      * @throws \Doctrine\ORM\ORMException
  986.      */
  987.     public function updateLastAndAvgPrices(ArrayCollection $products): bool
  988.     {
  989.         if ($products->count() > 0) {
  990.             $entityManager = $this->getEntityManager();
  991.             $productIds = [];
  992.             /** @var Product $product */
  993.             foreach ($products as $product) {
  994.                 if (in_array($product->getId(), Product::getExcludededProductIdsFromUpdate())) {
  995.                     continue;
  996.                 }
  997.                 if (in_array($product->getType()->getId(), Product::getExcludededProductTypeIdsFromUpdate())) {
  998.                     continue;
  999.                 }
  1000.                 $productIds[] = $product->getId();
  1001.             }
  1002.             if (count($productIds) > 0) {
  1003.                 $lastPrices = $this->updateLastProductPricesInCache($productIds);
  1004.                 /** @var Product $product */
  1005.                 foreach ($products as $product) {
  1006.                     if (in_array($product->getId(), Product::getExcludededProductIdsFromUpdate())) {
  1007.                         continue;
  1008.                     }
  1009.                     $lastPrice = isset($lastPrices[$product->getId()]) && !is_null($lastPrices[$product->getId()]['last_price']) ? $lastPrices[$product->getId()]['last_price'] : 0;
  1010.                     if ($product->isScannableWithType()) {
  1011.                         $avgPrice = $this->getAdjustedAverageBuyingPrice($product);
  1012.                     } else {
  1013.                         $avgPrice = $this->getNotScannableAverageBuyingPrice($product);
  1014.                     }
  1015.                     $product->setLastPrice(number_format($lastPrice, 2, '.', ''));
  1016.                     $product->setAvgPrice(number_format($avgPrice, 2, '.', ''));
  1017.                 }
  1018.                 $entityManager->flush();
  1019.             }
  1020.             return false;
  1021.         }
  1022.         return false;
  1023.     }
  1024.     /**
  1025.      * TEST_GROUP -g ProductRepositoryTest_updateProductInStockAndAvailable
  1026.      * @param Product $product
  1027.      * @return void
  1028.      * @throws ORMException
  1029.      * @throws OptimisticLockException
  1030.      * @throws TransactionRequiredException
  1031.      * @throws \Doctrine\ORM\ORMException
  1032.      */
  1033.     private function updateLinkedContainerProducts(Product $product)
  1034.     {
  1035.         $entityManager = $this->getEntityManager();
  1036.         $linkedProds = $entityManager->createQuery('SELECT p
  1037.                 FROM AtechNetCoreBundle:Product p
  1038.                 JOIN p.productContainers pc
  1039.                 WHERE pc.child = :prod AND p.archived = 0')
  1040.             ->setParameter('prod', $product)
  1041.             ->getResult();
  1042.         if ($linkedProds) {
  1043.             // newest data needed in DB
  1044.             $entityManager->flush();
  1045.             foreach ($linkedProds as $lp) {
  1046.                 $entityManager->refresh($lp);
  1047.                 $this->updateProductInStock($lp, false);
  1048.                 $this->updateProductAvailable($lp, false);
  1049.             }
  1050.         }
  1051.     }
  1052.     /**
  1053.      * TEST_GROUP -g ProductRepositoryTest_updateProductInStockAndAvailable
  1054.      * @param Product $product
  1055.      * @param bool $refreshEntity
  1056.      * @param int $count
  1057.      * @return bool
  1058.      */
  1059.     public function updateProductInStock(Product $product, bool $refreshEntity = true, int $count = 0): bool
  1060.     {
  1061. //        return true;
  1062.         if ($refreshEntity) {
  1063.             $this->getEntityManager()->refresh($product);
  1064.         }
  1065.         $productIsService = $product->getType()->getId() === ProductType::PRODUCT_TYPE_ID_SERVICE;
  1066.         $inStock = $productIsService ? 999 : ($count !== 0 ? $product->getInStock() + $count : $this->getInStockStockItemsQuantity($product));
  1067.         $product->setInStock($inStock);
  1068.         if ($inStock <= 0 && $product->getStockPosition() != '') {
  1069.             $product->setStockPosition(null);
  1070.         }
  1071.         return true;
  1072.     }
  1073.     /**
  1074.      * TEST_GROUP -g ProductRepositoryTest_updateProductInStockAndAvailable
  1075.      * @param Product $product
  1076.      * @param bool $refreshEntity
  1077.      * @param int $count
  1078.      * @return bool
  1079.      * @throws NotSupported
  1080.      */
  1081.     public function updateProductAvailable(Product $product, bool $refreshEntity = true, int $count = 0): bool
  1082.     {
  1083. //        return true;
  1084.         if ($refreshEntity) {
  1085.             $this->getEntityManager()->refresh($product);
  1086.         }
  1087.         $productIsService = $product->getType()->getId() === ProductType::PRODUCT_TYPE_ID_SERVICE;
  1088.         $available = $productIsService ? 999 : ($count !== 0 ? $product->getAvailable() + $count : $this->getAvailableStockItemsQuantity($product));
  1089.         $product->setAvailable($available);
  1090.         if (!$product->isAvailable()) {
  1091.             foreach ($product->getMetas() as $meta) {
  1092.                 $meta->setOrderable(false);
  1093.                 $meta->setListed(false);
  1094.             }
  1095.         }
  1096.         $this->createNotifications($product);
  1097.         return true;
  1098.     }
  1099.     /**
  1100.      * @param Product $product
  1101.      * @throws NotSupported
  1102.      */
  1103.     private function createNotifications(Product $product)
  1104.     {
  1105.         $notifRepo = $this->getEntityManager()->getRepository(Notification::class);
  1106.         $ntfConfs = $notifRepo->getNotificationConfigs(NotificationConfig::TYPE_PRODUCT_AVAILABLE_LOW, $product->getId());
  1107.         $text = $this->translator->trans('Low availability of') . ' ' . $product->getName();
  1108.         foreach ($ntfConfs as $nc) {
  1109.             if ($nc->getIntParam() >= $product->getAvailable()) {
  1110.                 $notifRepo->createNotifications($nc, $text);
  1111.             }
  1112.         }
  1113.     }
  1114.     /**
  1115.      * @param bool $skipContainer
  1116.      * @return array
  1117.      */
  1118.     public function getProductsForSelect(bool $skipContainer = false): array
  1119.     {
  1120.         return $this->getEntityManager()->createQuery('SELECT PARTIAL p.{id, code, name, cacheDescription, inStock, available}, PARTIAL v.{id, name}, PARTIAL t.{id,title}
  1121.             FROM AtechNetCoreBundle:Product p
  1122.             LEFT JOIN p.type t
  1123.             LEFT JOIN p.vendor v
  1124.             WHERE p.archived = 0'
  1125.             . ($skipContainer ? ' AND p.container = 0' : ''))->getResult();
  1126.     }
  1127.     /**
  1128.      * TEST_GROUP -g ProductRepository_getProductsForStockReviewSelect
  1129.      * @param string $searchText
  1130.      * @param int $limitResult
  1131.      * @return array
  1132.      */
  1133.     public function getProductsForStockReviewSelect(string $searchText, int $limitResult): array
  1134.     {
  1135.         $query = $this->getEntityManager()->createQuery('SELECT PARTIAL p.{id, code, name, cacheDescription, inStock, available}, PARTIAL v.{id, name}, PARTIAL t.{id,title}
  1136.         FROM AtechNetCoreBundle:Product p
  1137.         LEFT JOIN p.type t
  1138.         LEFT JOIN p.vendor v
  1139.         WHERE p.archived = 0 AND p.inStock > 0'
  1140.             . ($searchText ? " AND CONCAT(p.code, ' ', t.title, ' ', v.name, ' ', p.name, ' ', p.cacheDescription) LIKE '%" . $searchText . "%'" : ''));
  1141.         if ($limitResult) {
  1142.             $query->setMaxResults($limitResult);
  1143.         }
  1144.         return $query->getResult();
  1145.     }
  1146.     /**
  1147.      * TEST_GROUP -g ProductRepository_getProductsForCouponSelect
  1148.      * @param string|null $searchText
  1149.      * @param int $limitResult
  1150.      * @return array
  1151.      */
  1152.     public function getProductsForCouponSelect(?string $searchText = null, int $limitResult = 0): array
  1153.     {
  1154.         $query = $this->getEntityManager()->createQuery(
  1155.             "SELECT PARTIAL p.{id, code, name, cacheDescription, inStock, available}, PARTIAL v.{id, name}, PARTIAL t.{id,title}
  1156.             FROM AtechNetCoreBundle:Product p
  1157.             LEFT JOIN p.type t
  1158.             LEFT JOIN p.vendor v
  1159.             WHERE p.archived = 0 AND p.inStock > 0"
  1160.             . (!is_null($searchText) ? " AND CONCAT(p.code, ' ', t.title, ' ', v.name, ' ', p.name, ' ', p.cacheDescription) LIKE '%" . $searchText . "%'" : "")
  1161.         );
  1162.         if ($limitResult > 0) {
  1163.             $query->setMaxResults($limitResult);
  1164.         }
  1165.         return $query->getResult();
  1166.     }
  1167.     /**
  1168.      * TEST_GROUP -g ProductRepository_getProductsForAjaxSelect
  1169.      * @param bool $count
  1170.      * @param string $search
  1171.      * @param string $limit
  1172.      * @param string $offset
  1173.      * @param array $filter
  1174.      * @return int|array
  1175.      * @throws NoResultException
  1176.      * @throws NonUniqueResultException
  1177.      */
  1178.     public function getProductsForAjaxSelect(bool $count, string $search = '', string $limit = '', string $offset = '', array $filter = [])
  1179.     {
  1180.         $products = $this->getEntityManager()->createQueryBuilder()
  1181.             ->select($count ? 'count(p)' : 'p')
  1182.             ->from(Product::class, 'p')
  1183.             ->join('p.type', 't')
  1184.             ->join('p.vendor', 'v')
  1185.             ->where('p.archived = 0');
  1186.         if ($search) {
  1187.             $expressionWhere = '';
  1188.             $expressions = $hits = [];
  1189.             // Split search string
  1190.             $expression = strtok($search, ' ');
  1191.             while ($expression !== false) {
  1192.                 $expressions[] = trim($expression);
  1193.                 $expression = strtok(' ');
  1194.             }
  1195.             if (count($expressions) > 1) {
  1196.                 foreach ($expressions as $expression) {
  1197.                     $hits[] = "( CASE WHEN ( p.cacheDescription LIKE '%$expression%' OR p.name LIKE '%$expression%' OR v.name LIKE '%$expression%' OR t.title LIKE '%$expression%' ) THEN 1 ELSE 0 END )";
  1198.                 }
  1199.                 $expressionWhere = " OR ( " . implode(' + ', $hits) . " ) >= " . count($expressions);
  1200.             }
  1201.             $products->andWhere("p.code LIKE '%" . $search . "%' OR t.title LIKE '%" . $search . "%' OR v.name LIKE'%" . $search . "%' OR p.name LIKE '%" . $search . "%' OR p.cacheDescription LIKE '%" . $search . "%' OR CONCAT(v.name, ' ', t.title, ' ', p.name) LIKE '%" . $search . "%' OR CONCAT(v.name, ' ', p.name, ' ', t.title) LIKE '%" . $search . "%'" . $expressionWhere);
  1202.         }
  1203.         if (isset($filter['filterOnlyAvailable']) && (int)$filter['filterOnlyAvailable'] == 1) {
  1204.             $products->andWhere("p.available > 0");
  1205.         }
  1206.         if (!empty($filter['filterProductType'])) {
  1207.             $products->andWhere('t.id = :filterProductType')
  1208.                 ->setParameter('filterProductType', (int)$filter['filterProductType']);
  1209.         }
  1210.         if ($limit && !$count) {
  1211.             $products->setMaxResults($limit);
  1212.         }
  1213.         if ($offset && !$count) {
  1214.             $products->setFirstResult($offset);
  1215.         }
  1216.         return $count ? $products->getQuery()->getSingleScalarResult() : $products->getQuery()->getResult();
  1217.     }
  1218.     /**
  1219.      * @param bool $count
  1220.      * @param string $search
  1221.      * @param string $limit
  1222.      * @param string $offset
  1223.      * @param array $filter
  1224.      * @return int|array
  1225.      * @throws NoResultException
  1226.      * @throws NonUniqueResultException
  1227.      */
  1228.     public function getProductsV2ForAjaxSelect(bool $count, string $search = '', string $limit = '', string $offset = '', array $filter = [])
  1229.     {
  1230.         $products = $this->getEntityManager()->createQueryBuilder()
  1231.             ->select($count ? 'count(p)' : 'p')
  1232.             ->from(Product::class, 'p')
  1233.             ->join('p.productModel', 'pm')
  1234.             ->join('pm.type', 't')
  1235.             ->join('pm.vendor', 'v')
  1236.             ->where('p.archived = 0');
  1237.         if ($search) {
  1238.             $expressionWhere = '';
  1239.             $expressions = $hits = [];
  1240.             // Split search string
  1241.             $expression = strtok($search, ' ');
  1242.             while ($expression !== false) {
  1243.                 $expressions[] = trim($expression);
  1244.                 $expression = strtok(' ');
  1245.             }
  1246.             if (count($expressions) > 1) {
  1247.                 foreach ($expressions as $expression) {
  1248.                     $hits[] = "( CASE WHEN ( p.cacheDescription LIKE '%$expression%' OR p.name LIKE '%$expression%' OR v.name LIKE '%$expression%' OR t.title LIKE '%$expression%' ) THEN 1 ELSE 0 END )";
  1249.                 }
  1250.                 $expressionWhere = " OR ( " . implode(' + ', $hits) . " ) >= " . count($expressions);
  1251.             }
  1252.             $products->andWhere("p.code LIKE '%" . $search . "%' OR t.title LIKE '%" . $search . "%' OR v.name LIKE'%" . $search . "%' OR p.name LIKE '%" . $search . "%' OR p.cacheDescription LIKE '%" . $search . "%' OR CONCAT(v.name, ' ', t.title, ' ', p.name) LIKE '%" . $search . "%' OR CONCAT(v.name, ' ', p.name, ' ', t.title) LIKE '%" . $search . "%'" . $expressionWhere);
  1253.         }
  1254.         if (isset($filter['filterOnlyAvailable']) && (int)$filter['filterOnlyAvailable'] == 1) {
  1255.             $products->andWhere("p.available > 0");
  1256.         }
  1257.         if (!empty($filter['filterProductModelId'])) {
  1258.             $products->andWhere('pm.id = :filterProductModelId')
  1259.                 ->setParameter('filterProductModelId', (int)$filter['filterProductModelId']);
  1260.         }
  1261.         if ($limit && !$count) {
  1262.             $products->setMaxResults($limit);
  1263.         }
  1264.         if ($offset && !$count) {
  1265.             $products->setFirstResult($offset);
  1266.         }
  1267.         return $count ? $products->getQuery()->getSingleScalarResult() : $products->getQuery()->getResult();
  1268.     }
  1269.     /**
  1270.      * @param ProductType $productType
  1271.      * @param int|null $quality
  1272.      * @param DateTime $dateFrom
  1273.      * @param DateTime $dateTo
  1274.      * @return mixed
  1275.      */
  1276.     public function getSumOfSoldProductsByProductTypeAndQuality(ProductType $productType, DateTime $dateFrom, DateTime $dateTo, $quality = null)
  1277.     {
  1278.         $params = [
  1279.             'dateFrom' => $dateFrom,
  1280.             'dateTo' => $dateTo,
  1281.             'type' => $productType,
  1282.             'outMovementBasic' => StockMovement::SUB_TYPE_OUT_BASIC,
  1283.         ];
  1284.         $additionalWhere = '';
  1285.         if (!is_null($quality) && is_numeric($quality)) {
  1286.             $additionalWhere = 'AND p.qualityStatus = :quality';
  1287.             $params['quality'] = $quality;
  1288.         }
  1289.         return $this->getEntityManager()->createQuery("SELECT SUM(smi.quantity) AS sumOfProducts
  1290.                     FROM AtechNetCoreBundle:StockMovement sm
  1291.                     LEFT JOIN sm.movementItems smi
  1292.                     LEFT JOIN smi.product p
  1293.                     LEFT JOIN p.type pt
  1294.                     WHERE sm.created > :dateFrom
  1295.                       AND sm.created < :dateTo
  1296.                       AND sm.subType = :outMovementBasic
  1297.                       AND pt = :type
  1298.                       " . $additionalWhere)
  1299.             ->setParameters($params)
  1300.             ->setMaxResults(1)
  1301.             ->getResult()[0]['sumOfProducts'];
  1302.     }
  1303.     /**
  1304.      * @param ProductType $productType
  1305.      * @param int|null $quality
  1306.      * @return mixed
  1307.      */
  1308.     public function getSumOfInStockProductsByProductTypeAndQuality(ProductType $productType, int $quality = null)
  1309.     {
  1310.         $params = [
  1311.             'type' => $productType,
  1312.         ];
  1313.         $additionalWhere = '';
  1314.         if (!is_null($quality)) {
  1315.             $additionalWhere = 'AND p.qualityStatus = :quality';
  1316.             $params['quality'] = $quality;
  1317.         }
  1318.         return $this->getEntityManager()->createQuery('SELECT SUM(p.inStock) AS sumOfProducts
  1319.                     FROM AtechNetCoreBundle:Product p
  1320.                     LEFT JOIN p.type pt
  1321.                     WHERE pt = :type AND p.inStock > 0
  1322.                       ' . $additionalWhere)
  1323.             ->setParameters($params)
  1324.             ->setMaxResults(1)
  1325.             ->getResult()[0]['sumOfProducts'];
  1326.     }
  1327.     /**
  1328.      * @param ProductMeta $productMeta
  1329.      * @return float
  1330.      */
  1331.     public function calculateSuggestedPrice(ProductMeta $productMeta)
  1332.     {
  1333.         $product = $productMeta->getProduct();
  1334.         $productTypeId = $product->getType()->getId();
  1335.         $sellingPrice = $productMeta->isDiscounted() ? $productMeta->getDiscount() : $productMeta->getPrice();
  1336.         if ($productTypeId == 52 || $productTypeId == 60) { // PC or NB
  1337.             $suggestedPrice = $sellingPrice < 150 ? $sellingPrice + 40 : $sellingPrice + 50;
  1338.         } elseif ($productTypeId == 44) { // Monitor
  1339.             $suggestedPrice = $sellingPrice < 60 ? $sellingPrice + 10 : $sellingPrice + 15;
  1340.         } else {
  1341.             $suggestedPrice = $sellingPrice < 10 ? $sellingPrice + 10 : $sellingPrice + 15;
  1342.         }
  1343.         return $suggestedPrice;
  1344.     }
  1345.     /**
  1346.      * @param Product $product
  1347.      * @return int|string
  1348.      * @throws Exception
  1349.      */
  1350.     public function getAdjustedAverageBuyingPrice(Product $product)
  1351.     {
  1352.         $averageBuyingPrice = 0;
  1353.         $itemCount = 0;
  1354.         $itemPrices = 0;
  1355.         if ($product->isScannable()) {
  1356.             /**
  1357.              * @var StockItem $stockItem
  1358.              */
  1359.             $stockItems = $this->getAvailableStockItems($product);
  1360.             if (count($stockItems) > 0) {
  1361.                 foreach ($stockItems as $stockItem) {
  1362.                     $itemPrices += $stockItem->getPurchasePriceCalculated();
  1363.                     $itemCount++;
  1364.                 }
  1365.                 if ($itemPrices > 0 && $itemCount > 0) {
  1366.                     $averageBuyingPrice = number_format($itemPrices / $itemCount, 2, '.', '');
  1367.                 }
  1368.             } else {
  1369.                 $lastBuyingPrice = $this->getLastPrice($product->getId());
  1370.                 if (isset($lastBuyingPrice[$product->getId()]['last_price'])) {
  1371.                     $averageBuyingPrice = $lastBuyingPrice[$product->getId()]['last_price'];
  1372.                 }
  1373.             }
  1374.         }
  1375.         return $averageBuyingPrice;
  1376.     }
  1377.     /**
  1378.      * @param Product $product
  1379.      * @return float|int
  1380.      * @throws Exception
  1381.      */
  1382.     public function getNotScannableAverageBuyingPrice(Product $product)
  1383.     {
  1384.         if ($product->isScannableWithType()) {
  1385.             return 0;
  1386.         }
  1387.         $totalValue = $totalQuantity = 0;
  1388.         $availableQuantity = $product->getAvailable();
  1389.         if ($availableQuantity > 0) {
  1390.             // ponytail: scalar stream instead of $product->getStockMovementItems() — that hydrated the
  1391.             // product's whole movement history (100k+ rows for cables/RAM/SSD, plus one StockMovement
  1392.             // entity each) only to read the newest few IN rows. Streams rows, breaks early, no entities.
  1393.             $rows = $this->getEntityManager()
  1394.                 ->createQuery(
  1395.                     'SELECT smi.quantity AS quantity, smi.price AS price, sm.quantity AS movementQuantity,
  1396.                             sm.extraPrice AS extraPrice, sm.exchangeRate AS exchangeRate
  1397.                      FROM AtechNetCoreBundle:StockMovementItem smi
  1398.                      JOIN smi.movement sm
  1399.                      WHERE smi.product = :product AND sm.type = :typeIn
  1400.                      ORDER BY smi.created DESC'
  1401.                 )
  1402.                 ->setParameter('product', $product)
  1403.                 ->setParameter('typeIn', StockMovement::TYPE_IN)
  1404.                 ->toIterable([], Query::HYDRATE_SCALAR);
  1405.             foreach ($rows as $row) {
  1406.                 $currentQuantity = (int)$row['quantity'];
  1407.                 if ($currentQuantity <= $availableQuantity) {
  1408.                     $quantityForPriceCalculation = $currentQuantity;
  1409.                 } else {
  1410.                     $quantityForPriceCalculation = $availableQuantity;
  1411.                 }
  1412.                 $totalQuantity = $totalQuantity + $quantityForPriceCalculation;
  1413.                 $exchangeRate = $row['exchangeRate'];
  1414.                 $pricePerItem = StockMovementItem::calculateTotalPricePerItem(
  1415.                     (float)$row['price'],
  1416.                     $currentQuantity,
  1417.                     (float)$row['extraPrice'],
  1418.                     (int)$row['movementQuantity']
  1419.                 );
  1420.                 if ($exchangeRate != 1 && $pricePerItem > 0) {
  1421.                     $pricePerItem = (float)number_format($pricePerItem / $exchangeRate, 2, '.', '');
  1422.                 }
  1423.                 $totalValue = $totalValue + ($quantityForPriceCalculation * $pricePerItem);
  1424.                 $availableQuantity = $availableQuantity - $quantityForPriceCalculation;
  1425.                 if ($availableQuantity <= 0) {
  1426.                     break;
  1427.                 }
  1428.             }
  1429.         } else {
  1430.             if ($product->getLastPrice() > 0) {
  1431.                 return $product->getLastPrice();
  1432.             } else {
  1433.                 return $this->getLastPrice($product->getId())[$product->getId()]['last_price'];
  1434.             }
  1435.         }
  1436.         if ($totalValue == 0 || $totalQuantity == 0) {
  1437.             return 0;
  1438.         }
  1439.         return (float)number_format($totalValue / $totalQuantity, 2, '.', '');
  1440.     }
  1441.     /**
  1442.      * @param Product $product
  1443.      * @return string
  1444.      */
  1445.     public function getProductKeyboardLanguageSetting(Product $product): string
  1446.     {
  1447.         $keyboardLanguage = '-';
  1448.         if ($product->getType()->getId() == 52) {
  1449.             $entityManager = $this->getEntityManager();
  1450.             $keyboardLanguageProductAttribute = $entityManager->createQuery("SELECT pa
  1451.                     FROM AtechNetCoreBundle:ProductAttribute pa
  1452.                     LEFT JOIN pa.option o
  1453.                     LEFT JOIN o.attribute a
  1454.                     WHERE a.id = :keyboardLanguageAttributeId AND pa.product = :product")
  1455.                 ->setParameters([
  1456.                     'keyboardLanguageAttributeId' => 94,
  1457.                     'product' => $product,
  1458.                 ])
  1459.                 ->setMaxResults(1)
  1460.                 ->getResult();
  1461.             if (isset($keyboardLanguageProductAttribute[0])) {
  1462.                 $keyboardLanguage = $keyboardLanguageProductAttribute[0]->getOption()->getTitle();
  1463.             } else {
  1464.                 foreach ($this->getAvailableStockItems($product) as $si) {
  1465.                     if ($keyboardLanguage != '-') {
  1466.                         continue;
  1467.                     }
  1468.                     if (!is_null($si->getKeyboardLanguage()) && $si->getKeyboardLanguage() != '') {
  1469.                         $keyboardLanguage = $si->getKeyboardLanguage();
  1470.                     }
  1471.                 }
  1472.             }
  1473.         }
  1474.         return $keyboardLanguage;
  1475.     }
  1476.     /**
  1477.      * @param Product $product
  1478.      * @param ProductType $productType
  1479.      * @param User $creator
  1480.      * @param false $asContainer
  1481.      * @param bool $preserveProductSettings
  1482.      * @param bool $duplicatePhotos
  1483.      * @param bool $withConnectedProducts
  1484.      * @return Product
  1485.      * @throws NotSupported
  1486.      */
  1487.     public function duplicateProduct(Product $product, ProductType $productType, User $creator, bool $asContainer = false, bool $preserveProductSettings = false, bool $duplicatePhotos = true, bool $withConnectedProducts = false)
  1488.     {
  1489.         $entityManager = $this->getEntityManager();
  1490.         $album = new Album();
  1491.         $entityManager->persist($album);
  1492.         $entityManager->flush();
  1493.         if ($duplicatePhotos) {
  1494.             $photos = $product?->getAlbum()?->getPhotos() ?? [];
  1495.             $i = 1;
  1496.             foreach ($photos as $photo) {
  1497.                 $newPhoto = new Photo();
  1498.                 $newPhoto->setAlbum($album);
  1499.                 $newPhoto->setSortIndex($i);
  1500.                 $newPhoto->setOriginalName($photo->getOriginalName());
  1501.                 $newPhoto->setContexts($photo->getContexts());
  1502.                 $newPhoto->setMimeType($photo->getMimeType());
  1503.                 $entityManager->persist($newPhoto);
  1504.                 $entityManager->flush();
  1505.                 if (file_exists($photo->getWebPath())) {
  1506.                     copy($photo->getWebPath(), $newPhoto->getWebPath());
  1507.                     $thumb_ends = ['eshop'];
  1508.                     foreach ($thumb_ends as $thumb_end) {
  1509.                         copy(
  1510.                             $photo->getUd() . 'gen/' . $photo->buildFn($thumb_end),
  1511.                             $newPhoto->getUd() . 'gen/' . $newPhoto->buildFn($thumb_end)
  1512.                         );
  1513.                     }
  1514.                 } else {
  1515.                     // Cloudflare
  1516.                     $cloudflare = $this->getCloudflareImage();
  1517.                     // Download
  1518.                     $cloudflare->downloadPhoto($photo, $newPhoto);
  1519.                 }
  1520.                 $i++;
  1521.                 // Skip Missing Photo
  1522.                 if (!file_exists($newPhoto->getAbsolutePath())) {
  1523.                     continue;
  1524.                 }
  1525.                 // Upload Photo to Cloudflare
  1526.                 $cloudflare = $this->getCloudflareImage();
  1527.                 // JPEG
  1528.                 $cloudflare->uploadPhoto($newPhoto);
  1529.             }
  1530.             $entityManager->flush();
  1531.         }
  1532.         $product_duplicate = new Product();
  1533.         $product_duplicate->setVendor($product->getVendor());
  1534.         $product_duplicate->setType($productType);
  1535.         $product_duplicate->setAlbum($album);
  1536.         $product_duplicate->setCreatedBy($creator);
  1537.         $product_duplicate->setUpdatedBy($creator);
  1538.         $product_duplicate->setCode($this->generateProductCode($productType));
  1539.         $product_duplicate->setName(str_replace(' - NOT SCANNABLE', '', $product->getName()));
  1540.         $product_duplicate->setWarranty($product->getWarranty());
  1541.         $product_duplicate->setPartNumber($product->getPartNumber());
  1542.         $product_duplicate->setEan('');
  1543.         $product_duplicate->setGtin('');
  1544.         $product_duplicate->setAmazonCode('');
  1545.         $product_duplicate->setUnit($product->getUnit());
  1546.         $product_duplicate->setLength($product->getLength());
  1547.         $product_duplicate->setWidth($product->getWidth());
  1548.         $product_duplicate->setHeight($product->getHeight());
  1549.         $product_duplicate->setWeight($product->getWeight());
  1550.         $product_duplicate->setBadges($product->getBadges());
  1551.         $product_duplicate->setDetail($product->getDetail());
  1552.         $product_duplicate->setOutOfStockStatus($product->getOutOfStockStatus());
  1553.         $product_duplicate->setQualityStatus($product->getQualityStatus());
  1554.         $product_duplicate->setArchived(false);
  1555.         $product_duplicate->setScannable($product->isScannable());
  1556.         $product_duplicate->setMarginLevel(Product::MARGIN_LEVEL_E);
  1557.         if ($withConnectedProducts) {
  1558.             $connectedProductModels = $product->getConnectedProductModels();
  1559.             foreach ($connectedProductModels as $connectedProductModel) {
  1560.                 $connectedProductModel->addConnectedProduct($product_duplicate);
  1561.             }
  1562.         }
  1563.         if ($preserveProductSettings) {
  1564.             $product_duplicate->setStockPosition($product->getStockPosition());
  1565.         } else {
  1566.             $product_duplicate->setStockPosition(null);
  1567.         }
  1568.         $entityManager->persist($product_duplicate);
  1569.         $entityManager->flush();
  1570.         /**
  1571.          * Copy ProductAttributes
  1572.          */
  1573.         $product_attributes = $product->getProductAttributes();
  1574.         foreach ($product_attributes as $pa) {
  1575.             $pa_copy = new ProductAttribute();
  1576.             $pa_copy->setProduct($product_duplicate);
  1577.             $pa_copy->setOption($pa->getOption());
  1578.             $pa_copy->setUsed($pa->isUsed());
  1579.             $pa_copy->setMultiplier($pa->getMultiplier());
  1580.             $pa_copy->setAlways($pa->isAlways());
  1581.             $entityManager->persist($pa_copy);
  1582.         }
  1583.         $entityManager->flush();
  1584.         /**
  1585.          * Copy Product Metas
  1586.          */
  1587.         $product_metas = $product->getMetas();
  1588.         foreach ($product_metas as $pm) {
  1589.             $pm_copy = new ProductMeta();
  1590.             $pm_copy->setDomain($pm->getDomain());
  1591.             $pm_copy->setProduct($product_duplicate);
  1592.             if ($preserveProductSettings) {
  1593.                 $pm_copy->setPrice($pm->getPrice());
  1594.                 $pm_copy->setDiscount($pm->getDiscount());
  1595.                 $pm_copy->setSuggestedPrice($pm->getSuggestedPrice());
  1596.                 $pm_copy->setActive($pm->isActive());
  1597.                 $pm_copy->setListed($pm->isListed());
  1598.                 $pm_copy->setDiscounted($pm->isDiscounted());
  1599.             } else {
  1600.                 $pm_copy->setPrice(0);
  1601.                 $pm_copy->setDiscount(0);
  1602.                 $pm_copy->setSuggestedPrice(0);
  1603.                 $pm_copy->setActive(false);
  1604.                 $pm_copy->setListed(false);
  1605.                 $pm_copy->setDiscounted(false);
  1606.             }
  1607.             $pm_copy->setVat($pm->getVat());
  1608.             $pm_copy->setDetail(null);
  1609.             $pm_copy->setWarranty($pm->getWarranty());
  1610.             $pm_copy->setBazaar($pm->isBazaar());
  1611.             $pm_copy->setBestSeller($pm->isBestSeller());
  1612.             $pm_copy->setReLenovo($pm->isReLenovo());
  1613.             $pm_copy->setHpRenew($pm->isHpRenew());
  1614.             $pm_copy->setOrderable($pm->isOrderable());
  1615.             $pm_copy->setSpecialPromotion($pm->isSpecialPromotion());
  1616.             $entityManager->persist($pm_copy);
  1617.         }
  1618.         /**
  1619.          * Copy Product Containers
  1620.          */
  1621.         if ($asContainer) {
  1622.             $product_duplicate->setContainer(true);
  1623.             $product_duplicate->setScannable(false);
  1624.             foreach ($product->getProductContainers() as $pc) {
  1625.                 $pc_copy = new ProductContainer();
  1626.                 $pc_copy->setProduct($product_duplicate);
  1627.                 $pc_copy->setChild($pc->getChild());
  1628.                 $pc_copy->setProductTag($pc->getProductTag());
  1629.                 $pc_copy->setMultiplier($pc->getMultiplier());
  1630.                 $entityManager->persist($pc_copy);
  1631.                 $product_duplicate->getProductContainers()->add($pc_copy);
  1632.                 foreach ($pc->getMetas() as $pcm) {
  1633.                     $pcm_copy = new ProductContainerMeta();
  1634.                     $pcm_copy->setPrice($pcm->getPrice());
  1635.                     $pcm_copy->setDiscount($pcm->getDiscount());
  1636.                     $pcm_copy->setDomain($pcm->getDomain());
  1637.                     $pcm_copy->setProductContainer($pc_copy);
  1638.                     $entityManager->persist($pcm_copy);
  1639.                     $pc_copy->getMetas()->add($pcm_copy);
  1640.                 }
  1641.             }
  1642.         }
  1643.         $entityManager->flush();
  1644.         return $product_duplicate;
  1645.     }
  1646.     /**
  1647.      * @param ProductType $productType
  1648.      * @return string
  1649.      * @throws NotSupported
  1650.      */
  1651.     public function generateProductCode(ProductType $productType): string
  1652.     {
  1653.         $products = $this->getEntityManager()->getRepository(Product::class)->findBy(
  1654.             [
  1655.                 'type' => $productType,
  1656.             ],
  1657.             [
  1658.                 'created' => 'DESC',
  1659.                 'code' => 'DESC',
  1660.             ],
  1661.             1
  1662.         );
  1663.         if (count($products) > 0) {
  1664.             /**
  1665.              * @var Product $product
  1666.              */
  1667.             $product = $products[array_keys($products)[0]];
  1668.             $codeWithoutTypeCode = (int)substr($product->getCode(), strlen($product->getType()->getCode())) + 1;
  1669.         } else {
  1670.             $codeWithoutTypeCode = 1;
  1671.         }
  1672.         return $productType->getCode() . sprintf("%'.04d", ((int)$codeWithoutTypeCode));
  1673.     }
  1674.     /**
  1675.      * @param ProductType $productType
  1676.      *
  1677.      * @return string
  1678.      * @throws NonUniqueResultException
  1679.      */
  1680.     public function generateProductCodeForV2Models(ProductType $productType): string
  1681.     {
  1682.         $entityManager = $this->getEntityManager();
  1683.         $qb = $entityManager->createQueryBuilder();
  1684.         $qb->select('p.code')
  1685.             ->from(Product::class, 'p')
  1686.             ->join('p.productModel', 'pm')
  1687.             ->where(
  1688.                 $qb->expr()->orX(
  1689.                     'pm.type = :type',
  1690.                     'p.type = :type'
  1691.                 )
  1692.             )->setParameter('type', $productType)
  1693.             ->orderBy('p.created', 'DESC')
  1694.             ->addOrderBy('p.id', 'DESC')
  1695.             ->setMaxResults(1);
  1696.         $productCode = $qb->getQuery()->getOneOrNullResult();
  1697.         if ($productCode) {
  1698.             $codeWithoutTypeCode = (int)substr($productCode['code'], strlen($productType->getCode())) + 1;
  1699.         } else {
  1700.             $codeWithoutTypeCode = 1;
  1701.         }
  1702.         return $productType->getCode() . str_pad($codeWithoutTypeCode, 4, '0', STR_PAD_LEFT);
  1703.     }
  1704.     /**
  1705.      * @param Product $product
  1706.      * @param false $flushEntityManager
  1707.      * @return bool
  1708.      * @throws Exception
  1709.      */
  1710.     public function updateProductStockPosition(Product $product, bool $flushEntityManager = false): bool
  1711.     {
  1712.         if (!$product->isScannableWithType()) {
  1713.             return true;
  1714.         }
  1715.         $entityManager = $this->getEntityManager();
  1716.         $connection = $entityManager->getConnection();
  1717.         $originalStockPositions = explode(',', (string)$product->getStockPosition());
  1718.         $stockItems = $connection->executeQuery(
  1719.             "SELECT si.id, 
  1720.                 COALESCE((
  1721.                     SELECT sip.stock_position_id 
  1722.                     FROM stock_items_position sip 
  1723.                     WHERE sip.stock_item_id = si.id 
  1724.                     ORDER BY sip.created DESC, sip.id DESC 
  1725.                     LIMIT 1
  1726.                 ), '') AS position
  1727.             FROM stock_movement_item smi 
  1728.             INNER JOIN stock_movement_items_stock_items smisi ON smisi.stockmovementitem_id = smi.id 
  1729.             INNER JOIN stock_items si ON si.id = smisi.stockitem_id 
  1730.             WHERE smi.product_id = ? 
  1731.                 AND si.in_stock = 1 
  1732.                 AND smi.id = (
  1733.                     SELECT smi2.id
  1734.                     FROM stock_movement_item smi2
  1735.                     INNER JOIN stock_movement_items_stock_items smisi2 ON smisi2.stockmovementitem_id = smi2.id
  1736.                     WHERE smisi2.stockitem_id = si.id
  1737.                     ORDER BY smi2.created DESC, smi2.id DESC
  1738.                     LIMIT 1
  1739.                 )", 
  1740.             [ 
  1741.                 $product->getId() 
  1742.             ]
  1743.         );
  1744.         $positions = [];
  1745.         foreach ($stockItems->fetchAllAssociative() as $row) {
  1746.             if (!in_array($row['position'], $positions) && trim($row['position']) != '') {
  1747.                 $positions[] = $row['position'];
  1748.             }
  1749.         }
  1750.         // Sort
  1751.         asort($positions, SORT_NUMERIC);
  1752.         asort($originalStockPositions, SORT_NUMERIC);
  1753.         // Compare
  1754.         $newStockPositions = implode(',', $positions);
  1755.         $originalStockPositions = implode(',', $originalStockPositions);
  1756.         if ($originalStockPositions != $newStockPositions) {
  1757.             $product->setStockPosition($newStockPositions);
  1758.             if ($flushEntityManager) {
  1759.                 $entityManager->flush();
  1760.             }
  1761.         }
  1762.         return true;
  1763.     }
  1764.     /**
  1765.      * @param array $categoryPageIds
  1766.      * @return string
  1767.      * @throws Exception
  1768.      * @throws \Doctrine\DBAL\Driver\Exception
  1769.      */
  1770.     public function getProductTypeIds(array $categoryPageIds): string
  1771.     {
  1772.         $whereIn = implode(',', $categoryPageIds);
  1773.         $connection = $this->getEntityManager()->getConnection();
  1774.         $sql = '
  1775.             SELECT cppt.product_type_id AS productTypeId
  1776.             FROM category_pages_product_types AS cppt
  1777.             WHERE cppt.category_page_id IN(' . $whereIn . ')
  1778.             GROUP BY cppt.product_type_id';
  1779.         $stmt = $connection->prepare($sql);
  1780.         $result = $stmt->execute();
  1781.         $productTypeIds = $result->fetchAllAssociative();
  1782.         $ret = [];
  1783.         foreach ($productTypeIds as $key => $value) {
  1784.             array_push($ret, $value['productTypeId']);
  1785.         }
  1786.         return implode(',', $ret);
  1787.     }
  1788.     /**
  1789.      * @param int $cacheLifeTime
  1790.      * @param bool $forceRegereration
  1791.      * @return array
  1792.      * @throws \Psr\Cache\InvalidArgumentException
  1793.      */
  1794.     public function getLastPricesFromCache(int $cacheLifeTime = 172800, bool $forceRegereration = false): array
  1795.     {
  1796.         $lastPricesCacheKey = self::LAST_AVG_PRICE_CACHE_KEY;
  1797.         $cacheItem = $this->cache->getItem($lastPricesCacheKey);
  1798.         if ($cacheItem->isHit() && !$forceRegereration) {
  1799.             $lastPrices = $cacheItem->get();
  1800.         } else {
  1801.             $lastPrices = [];
  1802.             $cacheItem->set($lastPrices);
  1803.             $cacheItem->expiresAfter($cacheLifeTime);
  1804.             $this->cache->save($cacheItem);
  1805.         }
  1806.         return $lastPrices;
  1807.     }
  1808.     /**
  1809.      * @param array $productIds
  1810.      * @return array|null
  1811.      * @throws Exception
  1812.      * @throws \Psr\Cache\InvalidArgumentException
  1813.      */
  1814.     public function updateLastProductPricesInCache(array $productIds = []): ?array
  1815.     {
  1816.         if (empty($productIds)) {
  1817.             return null;
  1818.         }
  1819.         $lastPrices = $this->getLastPricesFromCache();
  1820.         foreach ($this->getLastPrices($productIds) as $productId => $productPrices) {
  1821.             $lastPrices[$productId] = $productPrices;
  1822.         }
  1823.         $cacheItem = $this->cache->getItem(self::LAST_AVG_PRICE_CACHE_KEY);
  1824.         $cacheItem->set($lastPrices);
  1825.         $cacheItem->expiresAfter(345600);
  1826.         $this->cache->save($cacheItem);
  1827.         return $lastPrices;
  1828.     }
  1829.     /**
  1830.      * @param ProductMeta $productMeta
  1831.      * @param float $priceOld
  1832.      * @param float $priceNew
  1833.      * @param User|null $user
  1834.      * @return void
  1835.      * @throws NotSupported
  1836.      * @throws ORMException
  1837.      * @throws \Doctrine\ORM\ORMException
  1838.      */
  1839.     public function addPriceLogEntry(ProductMeta $productMeta, float $priceOld, float $priceNew, User $user = null): void
  1840.     {
  1841.         $entityManager = $this->getEntityManager();
  1842.         $changelog = new ProductPriceLog();
  1843.         $changelog->setDomain($productMeta->getDomain());
  1844.         $changelog->setProduct($productMeta->getProduct());
  1845.         if ($user) {
  1846.             $changelog->setCreatedBy($user);
  1847.         } else {
  1848.             $centralisUser = $entityManager->getRepository(User::class)->find(User::CENTRALIS_USER_ID);
  1849.             $changelog->setCreatedBy($centralisUser);
  1850.         }
  1851.         $changelog->setOldPrice($priceOld);
  1852.         $changelog->setNewPrice($priceNew);
  1853.         $entityManager->persist($changelog);
  1854.     }
  1855.     /**
  1856.      * @var Product $product
  1857.      * @var Domain $domain
  1858.      * @var DateTime $dateTimeValue
  1859.      */
  1860.     public function getPriceFromPriceLogsByGivenDate(Product $product, Domain $domain, DateTime $dateTimeValue)
  1861.     {
  1862.         /**
  1863.          * @var ProductPriceLog $firstPriceLogBeforeGivenDate
  1864.          */
  1865.         $firstPriceLogBeforeGivenDate = $product->getPriceLogs()->filter(
  1866.             function (ProductPriceLog $ppl) use ($domain, $dateTimeValue) {
  1867.                 return $ppl->getDomain() == $domain && $ppl->getCreated() < $dateTimeValue;
  1868.             }
  1869.         )->last();
  1870.         return $firstPriceLogBeforeGivenDate ? $firstPriceLogBeforeGivenDate->getNewPrice() : 0;
  1871.     }
  1872.     /**
  1873.      * Entity Product->getCommissions() with custom query
  1874.      * @param int $productId
  1875.      * @param int $limit
  1876.      * @return float|int|mixed|string
  1877.      */
  1878.     public function getLimitedCommissions(int $productId, int $limit = 0)
  1879.     {
  1880.         $entityManager = $this->getEntityManager();
  1881.         $results = $entityManager->createQuery(
  1882.             "SELECT cm
  1883.             FROM AtechNetCoreBundle:ProductCommission cm
  1884.             LEFT JOIN cm.product pr
  1885.             WHERE pr.id = $productId AND cm.status IN (" . ProductCommission::Status_AtPartner . ", " . ProductCommission::Status_Sold . ") AND (cm.invoiceNumber = '' OR cm.invoiceNumber IS NULL)"
  1886.         );
  1887.         if ($limit > 0) {
  1888.             $results->setMaxResults($limit);
  1889.         }
  1890.         return $results->getResult();
  1891.     }
  1892.     /**
  1893.      * Entity Product->getReservations() with custom query
  1894.      * @param int $productId
  1895.      * @param int $limit
  1896.      * @return array|\mixed[][]
  1897.      * @throws Exception
  1898.      * @throws \Doctrine\DBAL\Driver\Exception
  1899.      */
  1900.     public function getLimitedReservations(int $productId, int $limit = 0)
  1901.     {
  1902.         $connection = $this->getEntityManager()->getConnection();
  1903.         $stockMovementType = StockMovement::TYPE_OUT;
  1904.         $query = "SELECT o.id, oi.quantity
  1905.             FROM orders__items oi
  1906.             LEFT JOIN orders o ON oi.order_id = o.id
  1907.             WHERE oi.product_id = $productId AND (o.invoice_code IS NULL OR o.invoice_code = '' OR o.invoice_code = 0)
  1908.             AND (SELECT sm.type
  1909.             FROM stock_movements sm
  1910.             WHERE sm.order_id = o.id
  1911.             ORDER BY sm.created_by DESC
  1912.             LIMIT 1) = $stockMovementType
  1913.             ORDER BY o.created DESC
  1914.         ";
  1915.         if ($limit > 0) {
  1916.             $query .= " LIMIT $limit";
  1917.         }
  1918.         $orders = $connection->executeQuery($query);
  1919.         return $orders->fetchAllAssociative();
  1920.     }
  1921.     /**
  1922.      * Entity Product->getReservationsCount() with custom query
  1923.      * @param int $productId
  1924.      * @return int|null
  1925.      * @throws Exception
  1926.      * @throws \Doctrine\DBAL\Driver\Exception
  1927.      */
  1928.     public function getReservationsCount(int $productId): ?int
  1929.     {
  1930.         $connection = $this->getEntityManager()->getConnection();
  1931.         $stockMovementType = StockMovement::TYPE_OUT;
  1932.         $query = "SELECT SUM(oi.quantity) as total
  1933.             FROM orders__items oi
  1934.             LEFT JOIN orders o ON oi.order_id = o.id
  1935.             WHERE oi.product_id = $productId AND o.invoice_code IN('', null)
  1936.             AND (SELECT sm.type
  1937.             FROM stock_movements sm
  1938.             WHERE sm.order_id = o.id
  1939.             ORDER BY sm.created_by DESC
  1940.             LIMIT 1) = $stockMovementType
  1941.         ";
  1942.         $orders = $connection->executeQuery($query);
  1943.         return $orders->fetchAllAssociative()[0]['total'];
  1944.     }
  1945.     /**
  1946.      * Entity Product->getSold() with custom query
  1947.      * @param int $productId
  1948.      * @param int $limit
  1949.      * @return mixed[]|\mixed[][]
  1950.      * @throws Exception
  1951.      */
  1952.     public function getLimitedSold(int $productId, int $limit = 0): array
  1953.     {
  1954.         $connection = $this->getEntityManager()->getConnection();
  1955.         $stockMovementType = StockMovement::TYPE_OUT;
  1956.         $query = "SELECT o.*
  1957.             FROM orders__items oi
  1958.             LEFT JOIN orders o ON oi.order_id = o.id
  1959.             WHERE oi.product_id = $productId
  1960.             AND (SELECT sm.type
  1961.             FROM stock_movements sm
  1962.             WHERE sm.order_id = o.id
  1963.             ORDER BY sm.created_by DESC
  1964.             LIMIT 1) = $stockMovementType
  1965.             ORDER BY o.created DESC
  1966.         ";
  1967.         if ($limit > 0) {
  1968.             $query .= " LIMIT $limit";
  1969.         }
  1970.         $orders = $connection->executeQuery($query);
  1971.         return $orders->fetchAllAssociative();
  1972.     }
  1973.     /**
  1974.      * Entity Product->getSoldCount() with custom query
  1975.      * @param int $productId
  1976.      * @return int
  1977.      * @throws Exception
  1978.      */
  1979.     public function getSoldCount(int $productId): ?int
  1980.     {
  1981.         $connection = $this->getEntityManager()->getConnection();
  1982.         $stockMovementType = StockMovement::TYPE_OUT;
  1983.         $query = "SELECT SUM(oi.quantity) as total
  1984.             FROM orders__items oi
  1985.             LEFT JOIN orders o ON oi.order_id = o.id
  1986.             WHERE oi.product_id = $productId
  1987.             AND (SELECT sm.type
  1988.             FROM stock_movements sm
  1989.             WHERE sm.order_id = o.id
  1990.             ORDER BY sm.created_by DESC
  1991.             LIMIT 1) = $stockMovementType
  1992.         ";
  1993.         $orders = $connection->executeQuery($query);
  1994.         return $orders->fetchAllAssociative()[0]['total'];
  1995.     }
  1996.     /**
  1997.      * @return array
  1998.      */
  1999.     public function incomingProductsTable(): array
  2000.     {
  2001.         $entityManager = $this->getEntityManager();
  2002.         $thisYear = (new DateTime())->format('Y');
  2003.         return $entityManager->createQuery("SELECT 
  2004.                 pt.title AS productType,
  2005.                 sm.id AS stockMovementId,
  2006.                 d.shortTitle AS domain,
  2007.                 m.businessName AS Merchant,
  2008.                 mi.quantity AS quantityInMovement,
  2009.                 p.inStock AS allInStock,
  2010.                 (mi.price / mi.quantity) AS pricePerItem,
  2011.                 mi.price AS totalPrice,
  2012.                 sm.shipmentCode, 
  2013.                 sm.note
  2014.             FROM AtechNetCoreBundle:StockMovementItem mi
  2015.             LEFT JOIN mi.movement sm
  2016.             LEFT JOIN mi.product p
  2017.             LEFT JOIN p.type pt
  2018.             LEFT JOIN sm.stock s
  2019.             LEFT JOIN sm.merchant m
  2020.             LEFT JOIN m.domain d
  2021.             WHERE p.name LIKE '%INCOMING @$thisYear%'
  2022.             GROUP BY sm")
  2023.             ->getResult();
  2024.     }
  2025.     /**
  2026.      * @param int $attributeId
  2027.      * @return array
  2028.      */
  2029.     public function getProductTypeListForAttribute(int $attributeId): ?array
  2030.     {
  2031.         $entityManager = $this->getEntityManager();
  2032.         return $entityManager->createQuery("SELECT pt
  2033.             FROM AtechNetCoreBundle:ProductType pt
  2034.             INNER JOIN AtechNetCoreBundle:Product p WITH p.type = pt
  2035.             INNER JOIN AtechNetCoreBundle:ProductAttribute pa WITH pa.product = p AND
  2036.             IDENTITY(pa.option) IN(
  2037.                 SELECT ao.id
  2038.                 FROM AtechNetCoreBundle:AttributeOption ao
  2039.                 WHERE IDENTITY(ao.attribute) = $attributeId
  2040.             )
  2041.             GROUP BY pa.product
  2042.         ")
  2043.             ->getResult();
  2044.     }
  2045.     /**
  2046.      * @param array $priceMonitorData
  2047.      * @return array
  2048.      */
  2049.     public function getPriceMonitorListForProduct(array $priceMonitorData): ?array
  2050.     {
  2051.         $entityManager = $this->getEntityManager();
  2052.         $whereString = '';
  2053.         if ($priceMonitorData['domainId']) {
  2054.             $whereString .= 'IDENTITY(cn.domain) = ' . $priceMonitorData['domainId'];
  2055.         }
  2056.         if ($priceMonitorData['notified'] != 0) {
  2057.             if ($whereString != '') {
  2058.                 $whereString .= ' AND ';
  2059.             }
  2060.             switch ($priceMonitorData['notified']) {
  2061.                 case '1':
  2062.                     $whereString .= 'cn.notified IS NOT NULL';
  2063.                     break;
  2064.                 case '2':
  2065.                     $whereString .= 'cn.notified IS NULL';
  2066.                     break;
  2067.             }
  2068.         }
  2069.         if ($whereString != '') {
  2070.             $whereString = "WHERE $whereString";
  2071.         }
  2072.         $orderByColumn = $priceMonitorData['order'][0] ?? '';
  2073.         $orderByType = strlen($priceMonitorData['order']) == 2 ? ($priceMonitorData['order'][1] === 'A' ? 'A' : 'D') : 'D';
  2074.         /**
  2075.          * Array for order by fields
  2076.          */
  2077.         $availableOrderBy = ['C', 'N', 'D', 'S', 'P', 'F', 'I'];
  2078.         // Build order by sql statement
  2079.         if ($orderByColumn != '' && in_array($orderByColumn, $availableOrderBy)) {
  2080.             if ($orderByColumn == 'C') {
  2081.                 $orderBySql = ' ORDER BY cnp.code';
  2082.             } elseif ($orderByColumn == 'N') {
  2083.                 $orderBySql = " ORDER BY CONCAT(cnpt.title, ' ', cnpv.name, ' ', cnp.name)";
  2084.             } elseif ($orderByColumn == 'S') {
  2085.                 $orderBySql = ' ORDER BY cn.sourcePrice';
  2086.             } elseif ($orderByColumn == 'P') {
  2087.                 $orderBySql = ' ORDER BY cn.price';
  2088.             } elseif ($orderByColumn == 'F') {
  2089.                 $orderBySql = ' ORDER BY pm.currentVatPrice';
  2090.             } elseif ($orderByColumn == 'I') {
  2091.                 $orderBySql = ' ORDER BY cnp.inStock';
  2092.             } else {
  2093.                 $orderBySql = ' ORDER BY cn.created';
  2094.             }
  2095.             if ($orderByType == 'D') {
  2096.                 $orderBySql .= ' DESC';
  2097.             }
  2098.         } else {
  2099.             $orderBySql = ' ORDER BY cn.created DESC';
  2100.         }
  2101.         return $entityManager->createQuery('SELECT cn
  2102.                 FROM AtechNetCoreBundle:CustomerNotification cn
  2103.                 INNER JOIN AtechNetCoreBundle:Product p WITH cn.product = p AND IDENTITY(cn.product) = ' . $priceMonitorData['productId'] . "
  2104.                 $whereString
  2105.                 $orderBySql
  2106.             ")->getResult();
  2107.     }
  2108.     /**
  2109.      * Get parts sections with categories for the parts page
  2110.      * @param Domain $domain
  2111.      * @param SeoUrlHelper $seoUrlHelper
  2112.      * @param ProductHelper $productHelper
  2113.      * @param MultiStore $multiStore
  2114.      * @return array
  2115.      */
  2116.     public function getPartsSections(Domain $domain, SeoUrlHelper $seoUrlHelper, ProductHelper $productHelper, MultiStore $multiStore): array
  2117.     {
  2118.         $sections = [];
  2119.         $translator = $this->translator;
  2120.         switch ($domain->getId()) {
  2121.             case Domain::DOMAIN_ID_RT_SK:
  2122.             case Domain::DOMAIN_ID_WS_SK:
  2123.                 $sections = [
  2124.                     [
  2125.                         // Parts
  2126.                         'title' => $translator->trans('Parts'), // Alkatrészek
  2127.                         'description' => $translator->trans('Parts description'),
  2128.                         'categoryId' => 447,
  2129.                         'categories' => [
  2130.                             [
  2131.                                 // Motherboard
  2132.                                 'subCategoryId' => 104,
  2133.                                 'image' => 'furbify-komp-motherboard.jpg',
  2134.                             ],
  2135.                             [
  2136.                                 // Video card
  2137.                                 'subCategoryId' => 36,
  2138.                                 'image' => 'furbify-komp-videokartya-graficka-karta.jpg',
  2139.                             ],
  2140.                             [
  2141.                                 // Cable and reduction
  2142.                                 'subCategoryId' => 227,
  2143.                                 'image' => 'furbify-komp-kabel-adapter.jpg',
  2144.                             ],
  2145.                             [
  2146.                                 // Laptop memory (RAM)
  2147.                                 'subCategoryId' => 25,
  2148.                                 'image' => 'furbify-komp-laptop-notebook-memory.jpg',
  2149.                             ],
  2150.                             [
  2151.                                 // PC memory (RAM)
  2152.                                 'subCategoryId' => 26,
  2153.                                 'image' => 'furbify-komp-pc-memory.jpg',
  2154.                             ],
  2155.                             [
  2156.                                 // Optical disk drive
  2157.                                 'subCategoryId' => 32,
  2158.                                 'image' => 'furbify-komp-optical-drive.jpg',
  2159.                             ],
  2160.                             [
  2161.                                 // HDD / SSD
  2162.                                 'subCategoryId' => 27,
  2163.                                 'image' => 'furbify-komp-ssd.jpg',
  2164.                             ],
  2165.                             [
  2166.                                 // Processors
  2167.                                 'subCategoryId' => 29,
  2168.                                 'image' => 'furbify-komp-processor.jpg',
  2169.                             ],
  2170.                             [
  2171.                                 // Laptop display
  2172.                                 'subCategoryId' => 34,
  2173.                                 'image' => 'furbify-komp-laptop-display.jpg',
  2174.                             ],
  2175.                             [
  2176.                                 // Laptop keyboard
  2177.                                 'subCategoryId' => 35,
  2178.                                 'image' => 'furbify-komp-laptop-billentyuzet-notebook-klavesnica.jpg',
  2179.                             ],
  2180.                             [
  2181.                                 // Power supply
  2182.                                 'subCategoryId' => 100,
  2183.                                 'image' => 'furbify-komp-zdroj-tapegyseg.jpg',
  2184.                             ],
  2185.                             [
  2186.                                 // Notebook battery
  2187.                                 'subCategoryId' => 234,
  2188.                                 'image' => 'furbify-komp-notebook-akku.jpg',
  2189.                             ],
  2190.                             [
  2191.                                 // Monitor stand
  2192.                                 'subCategoryId' => 366,
  2193.                                 'image' => 'furbify-monitor-stand.jpg',
  2194.                             ],
  2195.                             [
  2196.                                 // Laptop adapter
  2197.                                 'subCategoryId' => 369,
  2198.                                 'image' => 'furbify-laptop-adapter.jpg',
  2199.                             ], [
  2200.                                 // Laptop touchpad
  2201.                                 'subCategoryId' => 402,
  2202.                                 'image' => 'furbify-laptop-touchpad.jpg',
  2203.                             ], [
  2204.                                 // Laptop top cover
  2205.                                 'subCategoryId' => 403,
  2206.                                 'image' => 'furbify-laptop-top-cover.jpg',
  2207.                             ], [
  2208.                                 // Laptop bottom cover
  2209.                                 'subCategoryId' => 404,
  2210.                                 'image' => 'furbify-laptop-bottom-cover.jpg',
  2211.                             ], [
  2212.                                 // Laptop bottom base cover
  2213.                                 'subCategoryId' => 405,
  2214.                                 'image' => 'furbify-laptop-bottom-base-cover.jpg',
  2215.                             ], [
  2216.                                 // Laptop internal base plate
  2217.                                 'subCategoryId' => 406,
  2218.                                 'image' => 'furbify-laptop-internal-base-plate.jpg',
  2219.                             ], [
  2220.                                 // Laptop palmrest
  2221.                                 'subCategoryId' => 407,
  2222.                                 'image' => 'furbify-laptop-palmrest.jpg',
  2223.                             ], [
  2224.                                 // Laptop display hinge
  2225.                                 'subCategoryId' => 408,
  2226.                                 'image' => 'furbify-laptop-display-hinge.jpg',
  2227.                             ], [
  2228.                                 // Laptop display hinge cover
  2229.                                 'subCategoryId' => 409,
  2230.                                 'image' => 'furbify-laptop-display-hinge-cover.jpg',
  2231.                             ], [
  2232.                                 // Laptop bezel
  2233.                                 'subCategoryId' => 410,
  2234.                                 'image' => 'furbify-laptop-bezel.jpg',
  2235.                             ], [
  2236.                                 // Laptop speaker
  2237.                                 'subCategoryId' => 411,
  2238.                                 'image' => 'furbify-laptop-speaker.jpg',
  2239.                             ], [
  2240.                                 // Laptop usb module
  2241.                                 'subCategoryId' => 412,
  2242.                                 'image' => 'furbify-laptop-usb-module.jpg',
  2243.                             ], [
  2244.                                 // Laptop cooling fan
  2245.                                 'subCategoryId' => 413,
  2246.                                 'image' => 'furbify-laptop-cooling-fan.jpg',
  2247.                             ], [
  2248.                                 // Laptop heatsing
  2249.                                 'subCategoryId' => 414,
  2250.                                 'image' => 'furbify-laptop-heatsing.jpg',
  2251.                             ], [
  2252.                                 // Laptop LVDS cable
  2253.                                 'subCategoryId' => 415,
  2254.                                 'image' => 'furbify-laptop-lvds-cable.jpg',
  2255.                             ], [
  2256.                                 // Laptop sata cable
  2257.                                 'subCategoryId' => 416,
  2258.                                 'image' => 'furbify-laptop-sata-cable.jpg',
  2259.                             ], [
  2260.                                 // Laptop flex cable
  2261.                                 'subCategoryId' => 417,
  2262.                                 'image' => 'furbify-laptop-flex-cable.jpg',
  2263.                             ], [
  2264.                                 // Laptop internal cable
  2265.                                 'subCategoryId' => 418,
  2266.                                 'image' => 'furbify-laptop-internal-cable.jpg',
  2267.                             ], [
  2268.                                 // PC internal cable
  2269.                                 'subCategoryId' => 419,
  2270.                                 'image' => 'furbify-pc-internal-cable.jpg',
  2271.                             ], [
  2272.                                 // PC heatsin
  2273.                                 'subCategoryId' => 420,
  2274.                                 'image' => 'furbify-pc-heatsin.jpg',
  2275.                             ], [
  2276.                                 // PC cooling fan
  2277.                                 'subCategoryId' => 421,
  2278.                                 'image' => 'furbify-pc-cooling-fan.jpg',
  2279.                             ], [
  2280.                                 // Sound card
  2281.                                 'subCategoryId' => 541,
  2282.                                 'image' => 'furbify-sound-card.png',
  2283.                             ], [
  2284.                                 // PC speaker
  2285.                                 'subCategoryId' => 514,
  2286.                                 'image' => 'furbify-pc-speaker.png',
  2287.                             ], [
  2288.                                 // PC-internal-part
  2289.                                 'subCategoryId' => 560,
  2290.                                 'image' => 'furbify-pc-internal-part.png',
  2291.                             ], [
  2292.                                 // PC-internal-module
  2293.                                 'subCategoryId' => 553,
  2294.                                 'image' => 'furbify-pc-internal-module.png',
  2295.                             ], [
  2296.                                 // PCI-express-card
  2297.                                 'subCategoryId' => 544,
  2298.                                 'image' => 'furbify-pci-express-card.png',
  2299.                             ], [
  2300.                                 // PC case
  2301.                                 'subCategoryId' => 517,
  2302.                                 'image' => 'furbify-pc-case.png',
  2303.                             ], [
  2304.                                 // Notebook-webcam
  2305.                                 'subCategoryId' => 511,
  2306.                                 'image' => 'furbify-notebook-webcam.png',
  2307.                             ], [
  2308.                                 // Notebook-other-cover
  2309.                                 'subCategoryId' => 547,
  2310.                                 'image' => 'furbify-notebook-other-cover.png',
  2311.                             ], [
  2312.                                 // Notebook_microphone
  2313.                                 'subCategoryId' => 523,
  2314.                                 'image' => 'furbify-notebook-microphone.png',
  2315.                             ], [
  2316.                                 // Notebook_internal_part
  2317.                                 'subCategoryId' => 562,
  2318.                                 'image' => 'furbify-notebook-internal-part.png',
  2319.                             ], [
  2320.                                 // Notebook_internal_module
  2321.                                 'subCategoryId' => 551,
  2322.                                 'image' => 'furbify-notebook-internal-module.png',
  2323.                             ], [
  2324.                                 // HDD_SSD_Caddy
  2325.                                 'subCategoryId' => 505,
  2326.                                 'image' => 'furbify-hdd-ssd-caddy.png',
  2327.                             ], [
  2328.                                 // Other product
  2329.                                 'subCategoryId' => 362,
  2330.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  2331.                             ],
  2332.                         ],
  2333.                     ],
  2334.                 ];
  2335.                 break;
  2336.             case Domain::DOMAIN_ID_RT_HU:
  2337.             case Domain::DOMAIN_ID_WS_HU:
  2338.                 $sections = [
  2339.                     [
  2340.                         // Parts
  2341.                         'title' => $translator->trans('Parts'),
  2342.                         'description' => $translator->trans('Parts description'),
  2343.                         'categoryId' => 44,
  2344.                         'categories' => [
  2345.                             [
  2346.                                 // Motherboard
  2347.                                 'subCategoryId' => 105,
  2348.                                 'image' => 'furbify-komp-motherboard.jpg',
  2349.                             ],
  2350.                             [
  2351.                                 // Video card
  2352.                                 'subCategoryId' => 73,
  2353.                                 'image' => 'furbify-komp-videokartya-graficka-karta.jpg',
  2354.                             ],
  2355.                             [
  2356.                                 // Cable and reduction
  2357.                                 'subCategoryId' => 223,
  2358.                                 'image' => 'furbify-komp-kabel-adapter.jpg',
  2359.                             ],
  2360.                             [
  2361.                                 // Laptop memory (RAM)
  2362.                                 'subCategoryId' => 63,
  2363.                                 'image' => 'furbify-komp-laptop-notebook-memory.jpg',
  2364.                             ],
  2365.                             [
  2366.                                 // PC memory (RAM)
  2367.                                 'subCategoryId' => 64,
  2368.                                 'image' => 'furbify-komp-pc-memory.jpg',
  2369.                             ],
  2370.                             [
  2371.                                 // Optical disk drive
  2372.                                 'subCategoryId' => 70,
  2373.                                 'image' => 'furbify-komp-optical-drive.jpg',
  2374.                             ],
  2375.                             [
  2376.                                 // HDD / SSD
  2377.                                 'subCategoryId' => 65,
  2378.                                 'image' => 'furbify-komp-ssd.jpg',
  2379.                             ],
  2380.                             [
  2381.                                 // Processors
  2382.                                 'subCategoryId' => 67,
  2383.                                 'image' => 'furbify-komp-processor.jpg',
  2384.                             ],
  2385.                             [
  2386.                                 // Laptop display
  2387.                                 'subCategoryId' => 71,
  2388.                                 'image' => 'furbify-komp-laptop-display.jpg',
  2389.                             ],
  2390.                             [
  2391.                                 // Laptop keyboard
  2392.                                 'subCategoryId' => 72,
  2393.                                 'image' => 'furbify-komp-laptop-billentyuzet-notebook-klavesnica.jpg',
  2394.                             ],
  2395.                             [
  2396.                                 // Power supply
  2397.                                 'subCategoryId' => 101,
  2398.                                 'image' => 'furbify-komp-zdroj-tapegyseg.jpg',
  2399.                             ],
  2400.                             [
  2401.                                 // Notebook battery
  2402.                                 'subCategoryId' => 45,
  2403.                                 'image' => 'furbify-komp-notebook-akku.jpg',
  2404.                             ],
  2405.                             [
  2406.                                 // Monitor stand
  2407.                                 'subCategoryId' => 365,
  2408.                                 'image' => 'furbify-monitor-stand.jpg',
  2409.                             ],
  2410.                             [
  2411.                                 // Laptop adapter
  2412.                                 'subCategoryId' => 368,
  2413.                                 'image' => 'furbify-laptop-adapter.jpg',
  2414.                             ], [
  2415.                                 // Laptop touchpad
  2416.                                 'subCategoryId' => 382,
  2417.                                 'image' => 'furbify-laptop-touchpad.jpg',
  2418.                             ], [
  2419.                                 // Laptop top cover
  2420.                                 'subCategoryId' => 383,
  2421.                                 'image' => 'furbify-laptop-top-cover.jpg',
  2422.                             ], [
  2423.                                 // Laptop bottom cover
  2424.                                 'subCategoryId' => 384,
  2425.                                 'image' => 'furbify-laptop-bottom-cover.jpg',
  2426.                             ], [
  2427.                                 // Laptop bottom base cover
  2428.                                 'subCategoryId' => 385,
  2429.                                 'image' => 'furbify-laptop-bottom-base-cover.jpg',
  2430.                             ], [
  2431.                                 // Laptop internal base plate
  2432.                                 'subCategoryId' => 386,
  2433.                                 'image' => 'furbify-laptop-internal-base-plate.jpg',
  2434.                             ], [
  2435.                                 // Laptop palmrest
  2436.                                 'subCategoryId' => 387,
  2437.                                 'image' => 'furbify-laptop-palmrest.jpg',
  2438.                             ], [
  2439.                                 // Laptop display hinge
  2440.                                 'subCategoryId' => 388,
  2441.                                 'image' => 'furbify-laptop-display-hinge.jpg',
  2442.                             ], [
  2443.                                 // Laptop display hinge cover
  2444.                                 'subCategoryId' => 389,
  2445.                                 'image' => 'furbify-laptop-display-hinge-cover.jpg',
  2446.                             ], [
  2447.                                 // Laptop bezel
  2448.                                 'subCategoryId' => 390,
  2449.                                 'image' => 'furbify-laptop-bezel.jpg',
  2450.                             ], [
  2451.                                 // Laptop speaker
  2452.                                 'subCategoryId' => 391,
  2453.                                 'image' => 'furbify-laptop-speaker.jpg',
  2454.                             ], [
  2455.                                 // Laptop usb module
  2456.                                 'subCategoryId' => 392,
  2457.                                 'image' => 'furbify-laptop-usb-module.jpg',
  2458.                             ], [
  2459.                                 // Laptop cooling fan
  2460.                                 'subCategoryId' => 393,
  2461.                                 'image' => 'furbify-laptop-cooling-fan.jpg',
  2462.                             ], [
  2463.                                 // Laptop heatsing
  2464.                                 'subCategoryId' => 394,
  2465.                                 'image' => 'furbify-laptop-heatsing.jpg',
  2466.                             ], [
  2467.                                 // Laptop LVDS cable
  2468.                                 'subCategoryId' => 395,
  2469.                                 'image' => 'furbify-laptop-lvds-cable.jpg',
  2470.                             ], [
  2471.                                 // Laptop sata cable
  2472.                                 'subCategoryId' => 396,
  2473.                                 'image' => 'furbify-laptop-sata-cable.jpg',
  2474.                             ], [
  2475.                                 // Laptop flex cable
  2476.                                 'subCategoryId' => 397,
  2477.                                 'image' => 'furbify-laptop-flex-cable.jpg',
  2478.                             ], [
  2479.                                 // Laptop internal cable
  2480.                                 'subCategoryId' => 398,
  2481.                                 'image' => 'furbify-laptop-internal-cable.jpg',
  2482.                             ], [
  2483.                                 // PC internal cable
  2484.                                 'subCategoryId' => 399,
  2485.                                 'image' => 'furbify-pc-internal-cable.jpg',
  2486.                             ], [
  2487.                                 // PC heatsin
  2488.                                 'subCategoryId' => 400,
  2489.                                 'image' => 'furbify-pc-heatsin.jpg',
  2490.                             ], [
  2491.                                 // PC cooling fan
  2492.                                 'subCategoryId' => 401,
  2493.                                 'image' => 'furbify-pc-cooling-fan.jpg',
  2494.                             ], [
  2495.                                 // Sound card
  2496.                                 'subCategoryId' => 540,
  2497.                                 'image' => 'furbify-sound-card.png',
  2498.                             ], [
  2499.                                 // PC speaker
  2500.                                 'subCategoryId' => 513,
  2501.                                 'image' => 'furbify-pc-speaker.png',
  2502.                             ], [
  2503.                                 // PC-internal-part
  2504.                                 'subCategoryId' => 557,
  2505.                                 'image' => 'furbify-pc-internal-part.png',
  2506.                             ], [
  2507.                                 // PC-internal-module
  2508.                                 'subCategoryId' => 550,
  2509.                                 'image' => 'furbify-pc-internal-module.png',
  2510.                             ], [
  2511.                                 // PCI-express-card
  2512.                                 'subCategoryId' => 543,
  2513.                                 'image' => 'furbify-pci-express-card.png',
  2514.                             ], [
  2515.                                 // PC case
  2516.                                 'subCategoryId' => 516,
  2517.                                 'image' => 'furbify-pc-case.png',
  2518.                             ], [
  2519.                                 // Notebook-webcam
  2520.                                 'subCategoryId' => 510,
  2521.                                 'image' => 'furbify-notebook-webcam.png',
  2522.                             ], [
  2523.                                 // Notebook-other-cover
  2524.                                 'subCategoryId' => 546,
  2525.                                 'image' => 'furbify-notebook-other-cover.png',
  2526.                             ], [
  2527.                                 // Notebook_microphone
  2528.                                 'subCategoryId' => 522,
  2529.                                 'image' => 'furbify-notebook-microphone.png',
  2530.                             ], [
  2531.                                 // Notebook_internal_part
  2532.                                 'subCategoryId' => 555,
  2533.                                 'image' => 'furbify-notebook-internal-part.png',
  2534.                             ], [
  2535.                                 // Notebook_internal_module
  2536.                                 'subCategoryId' => 549,
  2537.                                 'image' => 'furbify-notebook-internal-module.png',
  2538.                             ], [
  2539.                                 // HDD_SSD_Caddy
  2540.                                 'subCategoryId' => 504,
  2541.                                 'image' => 'furbify-hdd-ssd-caddy.png',
  2542.                             ], [
  2543.                                 // Other product
  2544.                                 'subCategoryId' => 363,
  2545.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  2546.                             ],
  2547.                         ],
  2548.                     ],
  2549.                 ];
  2550.                 break;
  2551.             case Domain::DOMAIN_ID_RT_CZ:
  2552.             case Domain::DOMAIN_ID_WS_CZ:
  2553.                 $sections = [
  2554.                     [
  2555.                         // Parts
  2556.                         'title' => $translator->trans('Parts'),
  2557.                         'description' => $translator->trans('Parts description'),
  2558.                         'categoryId' => 446,
  2559.                         'categories' => [
  2560.                             [
  2561.                                 // Motherboard
  2562.                                 'subCategoryId' => 167,
  2563.                                 'image' => 'furbify-komp-motherboard.jpg',
  2564.                             ],
  2565.                             [
  2566.                                 // Video card
  2567.                                 'subCategoryId' => 147,
  2568.                                 'image' => 'furbify-komp-videokartya-graficka-karta.jpg',
  2569.                             ],
  2570.                             [
  2571.                                 // Cable and reduction
  2572.                                 'subCategoryId' => 232,
  2573.                                 'image' => 'furbify-komp-kabel-adapter.jpg',
  2574.                             ],
  2575.                             [
  2576.                                 // Laptop memory (RAM)
  2577.                                 'subCategoryId' => 137,
  2578.                                 'image' => 'furbify-komp-laptop-notebook-memory.jpg',
  2579.                             ],
  2580.                             [
  2581.                                 // PC memory (RAM)
  2582.                                 'subCategoryId' => 138,
  2583.                                 'image' => 'furbify-komp-pc-memory.jpg',
  2584.                             ],
  2585.                             [
  2586.                                 // Optical disk drive
  2587.                                 'subCategoryId' => 144,
  2588.                                 'image' => 'furbify-komp-optical-drive.jpg',
  2589.                             ],
  2590.                             [
  2591.                                 // HDD / SSD
  2592.                                 'subCategoryId' => 139,
  2593.                                 'image' => 'furbify-komp-ssd.jpg',
  2594.                             ],
  2595.                             [
  2596.                                 // Processors
  2597.                                 'subCategoryId' => 141,
  2598.                                 'image' => 'furbify-komp-processor.jpg',
  2599.                             ],
  2600.                             [
  2601.                                 // Laptop display
  2602.                                 'subCategoryId' => 145,
  2603.                                 'image' => 'furbify-komp-laptop-display.jpg',
  2604.                             ],
  2605.                             [
  2606.                                 // Laptop keyboard
  2607.                                 'subCategoryId' => 146,
  2608.                                 'image' => 'furbify-komp-laptop-billentyuzet-notebook-klavesnica.jpg',
  2609.                             ],
  2610.                             [
  2611.                                 // Power supply
  2612.                                 'subCategoryId' => 166,
  2613.                                 'image' => 'furbify-komp-zdroj-tapegyseg.jpg',
  2614.                             ],
  2615.                             [
  2616.                                 // Notebook battery
  2617.                                 'subCategoryId' => 119,
  2618.                                 'image' => 'furbify-komp-notebook-akku.jpg',
  2619.                             ],
  2620.                             [
  2621.                                 // Monitor stand
  2622.                                 'subCategoryId' => 367,
  2623.                                 'image' => 'furbify-monitor-stand.jpg',
  2624.                             ], [
  2625.                                 // Laptop adapter
  2626.                                 'subCategoryId' => 370,
  2627.                                 'image' => 'furbify-laptop-adapter.jpg',
  2628.                             ], [
  2629.                                 // Laptop touchpad
  2630.                                 'subCategoryId' => 422,
  2631.                                 'image' => 'furbify-laptop-touchpad.jpg',
  2632.                             ], [
  2633.                                 // Laptop top cover
  2634.                                 'subCategoryId' => 423,
  2635.                                 'image' => 'furbify-laptop-top-cover.jpg',
  2636.                             ], [
  2637.                                 // Laptop bottom cover
  2638.                                 'subCategoryId' => 424,
  2639.                                 'image' => 'furbify-laptop-bottom-cover.jpg',
  2640.                             ], [
  2641.                                 // Laptop bottom base cover
  2642.                                 'subCategoryId' => 425,
  2643.                                 'image' => 'furbify-laptop-bottom-base-cover.jpg',
  2644.                             ], [
  2645.                                 // Laptop internal base plate
  2646.                                 'subCategoryId' => 426,
  2647.                                 'image' => 'furbify-laptop-internal-base-plate.jpg',
  2648.                             ], [
  2649.                                 // Laptop palmrest
  2650.                                 'subCategoryId' => 427,
  2651.                                 'image' => 'furbify-laptop-palmrest.jpg',
  2652.                             ], [
  2653.                                 // Laptop display hinge
  2654.                                 'subCategoryId' => 428,
  2655.                                 'image' => 'furbify-laptop-display-hinge.jpg',
  2656.                             ], [
  2657.                                 // Laptop display hinge cover
  2658.                                 'subCategoryId' => 429,
  2659.                                 'image' => 'furbify-laptop-display-hinge-cover.jpg',
  2660.                             ], [
  2661.                                 // Laptop bezel
  2662.                                 'subCategoryId' => 430,
  2663.                                 'image' => 'furbify-laptop-bezel.jpg',
  2664.                             ], [
  2665.                                 // Laptop speaker
  2666.                                 'subCategoryId' => 431,
  2667.                                 'image' => 'furbify-laptop-speaker.jpg',
  2668.                             ], [
  2669.                                 // Laptop usb module
  2670.                                 'subCategoryId' => 432,
  2671.                                 'image' => 'furbify-laptop-usb-module.jpg',
  2672.                             ], [
  2673.                                 // Laptop cooling fan
  2674.                                 'subCategoryId' => 433,
  2675.                                 'image' => 'furbify-laptop-cooling-fan.jpg',
  2676.                             ], [
  2677.                                 // Laptop heatsing
  2678.                                 'subCategoryId' => 434,
  2679.                                 'image' => 'furbify-laptop-heatsing.jpg',
  2680.                             ], [
  2681.                                 // Laptop LVDS cable
  2682.                                 'subCategoryId' => 435,
  2683.                                 'image' => 'furbify-laptop-lvds-cable.jpg',
  2684.                             ], [
  2685.                                 // Laptop sata cable
  2686.                                 'subCategoryId' => 436,
  2687.                                 'image' => 'furbify-laptop-sata-cable.jpg',
  2688.                             ], [
  2689.                                 // Laptop flex cable
  2690.                                 'subCategoryId' => 437,
  2691.                                 'image' => 'furbify-laptop-flex-cable.jpg',
  2692.                             ], [
  2693.                                 // Laptop internal cable
  2694.                                 'subCategoryId' => 438,
  2695.                                 'image' => 'furbify-laptop-internal-cable.jpg',
  2696.                             ], [
  2697.                                 // PC internal cable
  2698.                                 'subCategoryId' => 439,
  2699.                                 'image' => 'furbify-pc-internal-cable.jpg',
  2700.                             ], [
  2701.                                 // PC heatsin
  2702.                                 'subCategoryId' => 440,
  2703.                                 'image' => 'furbify-pc-heatsin.jpg',
  2704.                             ], [
  2705.                                 // PC cooling fan
  2706.                                 'subCategoryId' => 441,
  2707.                                 'image' => 'furbify-pc-cooling-fan.jpg',
  2708.                             ], [
  2709.                                 // Sound card
  2710.                                 'subCategoryId' => 542,
  2711.                                 'image' => 'furbify-sound-card.png',
  2712.                             ], [
  2713.                                 // PC speaker
  2714.                                 'subCategoryId' => 515,
  2715.                                 'image' => 'furbify-pc-speaker.png',
  2716.                             ], [
  2717.                                 // PC-internal-part
  2718.                                 'subCategoryId' => 559,
  2719.                                 'image' => 'furbify-pc-internal-part.png',
  2720.                             ], [
  2721.                                 // PC-internal-module
  2722.                                 'subCategoryId' => 554,
  2723.                                 'image' => 'furbify-pc-internal-module.png',
  2724.                             ], [
  2725.                                 // PCI-express-card
  2726.                                 'subCategoryId' => 545,
  2727.                                 'image' => 'furbify-pci-express-card.png',
  2728.                             ], [
  2729.                                 // PC case
  2730.                                 'subCategoryId' => 518,
  2731.                                 'image' => 'furbify-pc-case.png',
  2732.                             ], [
  2733.                                 // Notebook-webcam
  2734.                                 'subCategoryId' => 512,
  2735.                                 'image' => 'furbify-notebook-webcam.png',
  2736.                             ], [
  2737.                                 // Notebook-other-cover
  2738.                                 'subCategoryId' => 548,
  2739.                                 'image' => 'furbify-notebook-other-cover.png',
  2740.                             ], [
  2741.                                 // Notebook_microphone
  2742.                                 'subCategoryId' => 524,
  2743.                                 'image' => 'furbify-notebook-microphone.png',
  2744.                             ], [
  2745.                                 // Notebook_internal_part
  2746.                                 'subCategoryId' => 561,
  2747.                                 'image' => 'furbify-notebook-internal-part.png',
  2748.                             ], [
  2749.                                 // Notebook_internal_module
  2750.                                 'subCategoryId' => 552,
  2751.                                 'image' => 'furbify-notebook-internal-module.png',
  2752.                             ], [
  2753.                                 // HDD_SSD_Caddy
  2754.                                 'subCategoryId' => 506,
  2755.                                 'image' => 'furbify-hdd-ssd-caddy.png',
  2756.                             ], [
  2757.                                 // Other product
  2758.                                 'subCategoryId' => 380,
  2759.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  2760.                             ],
  2761.                         ],
  2762.                     ],
  2763.                 ];
  2764.                 break;
  2765.             case Domain::DOMAIN_ID_RT_EU:
  2766.             case Domain::DOMAIN_ID_WS_EU:
  2767.                 $sections = [
  2768.                     [
  2769.                         // Parts
  2770.                         'title' => $translator->trans('Parts'), // Parts
  2771.                         'description' => $translator->trans('Parts description'),
  2772.                         'categoryId' => 458,
  2773.                         'categories' => [
  2774.                             [
  2775.                                 // Motherboard
  2776.                                 'subCategoryId' => 321,
  2777.                                 'image' => 'furbify-komp-motherboard.jpg',
  2778.                             ],
  2779.                             [
  2780.                                 // Video card
  2781.                                 'subCategoryId' => 302,
  2782.                                 'image' => 'furbify-komp-videokartya-graficka-karta.jpg',
  2783.                             ],
  2784.                             [
  2785.                                 // Cable and reduction
  2786.                                 'subCategoryId' => 336,
  2787.                                 'image' => 'furbify-komp-kabel-adapter.jpg',
  2788.                             ],
  2789.                             [
  2790.                                 // Laptop memory (RAM)
  2791.                                 'subCategoryId' => 292,
  2792.                                 'image' => 'furbify-komp-laptop-notebook-memory.jpg',
  2793.                             ],
  2794.                             [
  2795.                                 // PC memory (RAM)
  2796.                                 'subCategoryId' => 293,
  2797.                                 'image' => 'furbify-komp-pc-memory.jpg',
  2798.                             ],
  2799.                             [
  2800.                                 // Optical disk drive
  2801.                                 'subCategoryId' => 299,
  2802.                                 'image' => 'furbify-komp-optical-drive.jpg',
  2803.                             ],
  2804.                             [
  2805.                                 // HDD / SSD
  2806.                                 'subCategoryId' => 294,
  2807.                                 'image' => 'furbify-komp-ssd.jpg',
  2808.                             ],
  2809.                             [
  2810.                                 // Processors
  2811.                                 'subCategoryId' => 296,
  2812.                                 'image' => 'furbify-komp-processor.jpg',
  2813.                             ],
  2814.                             [
  2815.                                 // Laptop display
  2816.                                 'subCategoryId' => 300,
  2817.                                 'image' => 'furbify-komp-laptop-display.jpg',
  2818.                             ],
  2819.                             [
  2820.                                 // Laptop keyboard
  2821.                                 'subCategoryId' => 301,
  2822.                                 'image' => 'furbify-komp-laptop-billentyuzet-notebook-klavesnica.jpg',
  2823.                             ],
  2824.                             [
  2825.                                 // Power supply
  2826.                                 'subCategoryId' => 320,
  2827.                                 'image' => 'furbify-komp-zdroj-tapegyseg.jpg',
  2828.                             ],
  2829.                             [
  2830.                                 // Notebook battery
  2831.                                 'subCategoryId' => 274,
  2832.                                 'image' => 'furbify-komp-notebook-akku.jpg',
  2833.                             ],
  2834.                             [
  2835.                                 // Monitor stand
  2836.                                 'subCategoryId' => 465,
  2837.                                 'image' => 'furbify-monitor-stand.jpg',
  2838.                             ],
  2839.                             [
  2840.                                 // Laptop adapter
  2841.                                 'subCategoryId' => 466,
  2842.                                 'image' => 'furbify-laptop-adapter.jpg',
  2843.                             ], [
  2844.                                 // Laptop touchpad
  2845.                                 'subCategoryId' => 467,
  2846.                                 'image' => 'furbify-laptop-touchpad.jpg',
  2847.                             ], [
  2848.                                 // Laptop top cover
  2849.                                 'subCategoryId' => 468,
  2850.                                 'image' => 'furbify-laptop-top-cover.jpg',
  2851.                             ], [
  2852.                                 // Laptop bottom cover
  2853.                                 'subCategoryId' => 469,
  2854.                                 'image' => 'furbify-laptop-bottom-cover.jpg',
  2855.                             ], [
  2856.                                 // Laptop bottom base cover
  2857.                                 'subCategoryId' => 470,
  2858.                                 'image' => 'furbify-laptop-bottom-base-cover.jpg',
  2859.                             ], [
  2860.                                 // Laptop internal base plate
  2861.                                 'subCategoryId' => 471,
  2862.                                 'image' => 'furbify-laptop-internal-base-plate.jpg',
  2863.                             ], [
  2864.                                 // Laptop palmrest
  2865.                                 'subCategoryId' => 472,
  2866.                                 'image' => 'furbify-laptop-palmrest.jpg',
  2867.                             ], [
  2868.                                 // Laptop display hinge
  2869.                                 'subCategoryId' => 473,
  2870.                                 'image' => 'furbify-laptop-display-hinge.jpg',
  2871.                             ], [
  2872.                                 // Laptop display hinge cover
  2873.                                 'subCategoryId' => 474,
  2874.                                 'image' => 'furbify-laptop-display-hinge-cover.jpg',
  2875.                             ], [
  2876.                                 // Laptop bezel
  2877.                                 'subCategoryId' => 476,
  2878.                                 'image' => 'furbify-laptop-bezel.jpg',
  2879.                             ], [
  2880.                                 // Laptop speaker
  2881.                                 'subCategoryId' => 475,
  2882.                                 'image' => 'furbify-laptop-speaker.jpg',
  2883.                             ], [
  2884.                                 // Laptop usb module
  2885.                                 'subCategoryId' => 477,
  2886.                                 'image' => 'furbify-laptop-usb-module.jpg',
  2887.                             ], [
  2888.                                 // Laptop cooling fan
  2889.                                 'subCategoryId' => 478,
  2890.                                 'image' => 'furbify-laptop-cooling-fan.jpg',
  2891.                             ], [
  2892.                                 // Laptop heatsing
  2893.                                 'subCategoryId' => 479,
  2894.                                 'image' => 'furbify-laptop-heatsing.jpg',
  2895.                             ], [
  2896.                                 // Laptop LVDS cable
  2897.                                 'subCategoryId' => 480,
  2898.                                 'image' => 'furbify-laptop-lvds-cable.jpg',
  2899.                             ], [
  2900.                                 // Laptop sata cable
  2901.                                 'subCategoryId' => 481,
  2902.                                 'image' => 'furbify-laptop-sata-cable.jpg',
  2903.                             ], [
  2904.                                 // Laptop flex cable
  2905.                                 'subCategoryId' => 482,
  2906.                                 'image' => 'furbify-laptop-flex-cable.jpg',
  2907.                             ], [
  2908.                                 // Laptop internal cable
  2909.                                 'subCategoryId' => 483,
  2910.                                 'image' => 'furbify-laptop-internal-cable.jpg',
  2911.                             ], [
  2912.                                 // PC internal cable
  2913.                                 'subCategoryId' => 484,
  2914.                                 'image' => 'furbify-pc-internal-cable.jpg',
  2915.                             ], [
  2916.                                 // PC heatsin
  2917.                                 'subCategoryId' => 485,
  2918.                                 'image' => 'furbify-pc-heatsin.jpg',
  2919.                             ], [
  2920.                                 // PC cooling fan
  2921.                                 'subCategoryId' => 486,
  2922.                                 'image' => 'furbify-pc-cooling-fan.jpg',
  2923.                             ], [
  2924.                                 // Other product
  2925.                                 'subCategoryId' => 464,
  2926.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  2927.                             ],
  2928.                         ],
  2929.                     ],
  2930.                 ];
  2931.                 break;
  2932.         }
  2933.         return $this->createCategorySectionsArray($sections, $seoUrlHelper, $productHelper, $multiStore);
  2934.     }
  2935.     /**
  2936.      * Get accessories sections with categories for the parts page
  2937.      * @param Domain $domain
  2938.      * @param SeoUrlHelper $seoUrlHelper
  2939.      * @param ProductHelper $productHelper
  2940.      * @param MultiStore $multiStore
  2941.      * @return array
  2942.      */
  2943.     public function getAccessoriesSections(Domain $domain, SeoUrlHelper $seoUrlHelper, ProductHelper $productHelper, MultiStore $multiStore): array
  2944.     {
  2945.         $sections = [];
  2946.         $translator = $this->translator;
  2947.         switch ($domain->getId()) {
  2948.             case Domain::DOMAIN_ID_RT_SK:
  2949.             case Domain::DOMAIN_ID_WS_SK:
  2950.                 $sections = [
  2951.                     [
  2952.                         // Accessories
  2953.                         'title' => $translator->trans('Accessories'),
  2954.                         'description' => $translator->trans('Accessories description'),
  2955.                         'categoryId' => 7,
  2956.                         'categories' => [
  2957.                             [
  2958.                                 // USB wifi
  2959.                                 'subCategoryId' => 10,
  2960.                                 'image' => 'furbify-komp-usb-wifi.jpg',
  2961.                             ],
  2962.                             [
  2963.                                 // Notebook bag
  2964.                                 'subCategoryId' => 98,
  2965.                                 'image' => 'furbify-komp-notebook-taska.jpg',
  2966.                             ],
  2967.                             [
  2968.                                 // Docking stations
  2969.                                 'subCategoryId' => 28,
  2970.                                 'image' => 'furbify-komp-dokovacia-stanica-laptop-dokkolo.jpg',
  2971.                             ],
  2972.                             [
  2973.                                 // Webcamera
  2974.                                 'subCategoryId' => 194,
  2975.                                 'image' => 'furbify-komp-webkamera.jpg',
  2976.                             ],
  2977.                             [
  2978.                                 // Keyboard
  2979.                                 'subCategoryId' => 31,
  2980.                                 'image' => 'furbify-komp-klavesnica-billentyuzet.jpg',
  2981.                             ],
  2982.                             [
  2983.                                 // Mouse
  2984.                                 'subCategoryId' => 30,
  2985.                                 'image' => 'furbify-komp-mouse.jpg',
  2986.                             ],
  2987.                             [
  2988.                                 // Headphones
  2989.                                 'subCategoryId' => 226,
  2990.                                 'image' => 'furbify-komp-headphone-headset.jpg',
  2991.                             ],
  2992.                             [
  2993.                                 // Speaker
  2994.                                 'subCategoryId' => 201,
  2995.                                 'image' => 'furbify-komp-reproduktor.jpg',
  2996.                             ],
  2997.                             [
  2998.                                 // Notebook cooler
  2999.                                 'subCategoryId' => 200,
  3000.                                 'image' => 'furbify-komp-chladic-notebook-huto.jpg',
  3001.                             ],
  3002.                             [
  3003.                                 // USB flash
  3004.                                 'subCategoryId' => 228,
  3005.                                 'image' => 'furbify-komp-usb-pendrive.jpg',
  3006.                             ],
  3007.                             [
  3008.                                 // Mobile case
  3009.                                 'subCategoryId' => 238,
  3010.                                 'image' => 'furbify-mobile-case.jpg',
  3011.                                 // 'noPath' => true
  3012.                             ],
  3013.                             [
  3014.                                 // Memory card
  3015.                                 'subCategoryId' => 247,
  3016.                                 'image' => 'furbify-sd-card.jpg',
  3017.                                 // 'noPath' => true
  3018.                             ],
  3019.                             [
  3020.                                 // Toner
  3021.                                 'subCategoryId' => 180,
  3022.                                 'image' => 'furbify-toner.jpg',
  3023.                                 // 'noPath' => true
  3024.                             ], [
  3025.                                 // Printing drums
  3026.                                 'subCategoryId' => 183,
  3027.                                 'image' => 'furbify-drum.jpg',
  3028.                                 // 'noPath' => true
  3029.                             ], [
  3030.                                 // Printer cartridge
  3031.                                 'subCategoryId' => 188,
  3032.                                 'image' => 'furbify-printer-cartridge.jpg',
  3033.                                 // 'noPath' => true
  3034.                             ], [
  3035.                                 // Surge arrest
  3036.                                 'subCategoryId' => 375,
  3037.                                 'image' => 'furbify-surge-arrest.png',
  3038.                             ], [
  3039.                                 // UPS
  3040.                                 'subCategoryId' => 499,
  3041.                                 'image' => 'furbify-ups.png',
  3042.                             ], [
  3043.                                 // USB-other
  3044.                                 'subCategoryId' => 532,
  3045.                                 'image' => 'furbify-usb-other.png',
  3046.                             ], [
  3047.                                 // USB-Bluetooth
  3048.                                 'subCategoryId' => 526,
  3049.                                 'image' => 'furbify-usb-bluetooth.png',
  3050.                             ], [
  3051.                                 // Power-distribution-unit
  3052.                                 'subCategoryId' => 502,
  3053.                                 'image' => 'furbify-power-distribution-unit.png',
  3054.                             ], [
  3055.                                 // Network-accessory
  3056.                                 'subCategoryId' => 535,
  3057.                                 'image' => 'furbify-network-accessory.png',
  3058.                             ], [
  3059.                                 // microphone
  3060.                                 'subCategoryId' => 520,
  3061.                                 'image' => 'furbify-microphone.png',
  3062.                             ], /*[ // Keyboard
  3063.                                 'subCategoryId' => 508,
  3064.                                 'image' => 'furbify-keyboard.png'
  3065.                             ],*/
  3066.                             [
  3067.                                 // Other product
  3068.                                 'subCategoryId' => 563,
  3069.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  3070.                             ],
  3071.                         ],
  3072.                     ], [
  3073.                         // Software & Services
  3074.                         'title' => $translator->trans('Software & Services'),
  3075.                         'description' => $translator->trans('Software & Services description'),
  3076.                         'categoryId' => 7,
  3077.                         'categories' => [
  3078.                             [
  3079.                                 // Office
  3080.                                 'subCategoryId' => 91,
  3081.                                 'image' => 'furbify-komp-office.jpg',
  3082.                             ],
  3083.                             [
  3084.                                 // Antivirus
  3085.                                 'subCategoryId' => 264,
  3086.                                 'image' => 'furbify-komp-eset.jpg',
  3087.                             ],
  3088.                         ],
  3089.                     ],
  3090.                 ];
  3091.                 break;
  3092.             case Domain::DOMAIN_ID_RT_HU:
  3093.             case Domain::DOMAIN_ID_WS_HU:
  3094.                 $sections = [
  3095.                     [
  3096.                         // Accessories
  3097.                         'title' => $translator->trans('Accessories'),
  3098.                         'description' => $translator->trans('Accessories description'),
  3099.                         'categoryId' => 567,
  3100.                         'categories' => [
  3101.                             [
  3102.                                 // USB wifi
  3103.                                 'subCategoryId' => 47,
  3104.                                 'image' => 'furbify-komp-usb-wifi.jpg',
  3105.                             ],
  3106.                             [
  3107.                                 // Notebook bag
  3108.                                 'subCategoryId' => 99,
  3109.                                 'image' => 'furbify-komp-notebook-taska.jpg',
  3110.                             ],
  3111.                             [
  3112.                                 // Docking stations
  3113.                                 'subCategoryId' => 66,
  3114.                                 'image' => 'furbify-komp-dokovacia-stanica-laptop-dokkolo.jpg',
  3115.                             ],
  3116.                             [
  3117.                                 // Webcamera
  3118.                                 'subCategoryId' => 195,
  3119.                                 'image' => 'furbify-komp-webkamera.jpg',
  3120.                             ],
  3121.                             [
  3122.                                 // Keyboard
  3123.                                 'subCategoryId' => 69,
  3124.                                 'image' => 'furbify-komp-klavesnica-billentyuzet.jpg',
  3125.                             ],
  3126.                             [
  3127.                                 // Mouse
  3128.                                 'subCategoryId' => 68,
  3129.                                 'image' => 'furbify-komp-mouse.jpg',
  3130.                             ],
  3131.                             [
  3132.                                 // Headphones
  3133.                                 'subCategoryId' => 204,
  3134.                                 'image' => 'furbify-komp-headphone-headset.jpg',
  3135.                             ],
  3136.                             [
  3137.                                 // Speaker
  3138.                                 'subCategoryId' => 205,
  3139.                                 'image' => 'furbify-komp-reproduktor.jpg',
  3140.                             ],
  3141.                             [
  3142.                                 // Notebook cooler
  3143.                                 'subCategoryId' => 209,
  3144.                                 'image' => 'furbify-komp-chladic-notebook-huto.jpg',
  3145.                             ],
  3146.                             [
  3147.                                 // Mobile case
  3148.                                 'subCategoryId' => 237,
  3149.                                 'image' => 'furbify-mobile-case.jpg',
  3150.                                 // 'noPath' => true
  3151.                             ],
  3152.                             [
  3153.                                 // Toner
  3154.                                 'subCategoryId' => 181,
  3155.                                 'image' => 'furbify-toner.jpg',
  3156.                                 // 'noPath' => true
  3157.                             ], [
  3158.                                 // Printing drums
  3159.                                 'subCategoryId' => 184,
  3160.                                 'image' => 'furbify-drum.jpg',
  3161.                                 // 'noPath' => true
  3162.                             ], [
  3163.                                 // Printer cartridge
  3164.                                 'subCategoryId' => 189,
  3165.                                 'image' => 'furbify-printer-cartridge.jpg',
  3166.                                 // 'noPath' => true
  3167.                             ], [
  3168.                                 // Surge arrest
  3169.                                 'subCategoryId' => 374,
  3170.                                 'image' => 'furbify-surge-arrest.png',
  3171.                             ], [
  3172.                                 // UPS
  3173.                                 'subCategoryId' => 498,
  3174.                                 'image' => 'furbify-ups.png',
  3175.                             ], [
  3176.                                 // USB-other
  3177.                                 'subCategoryId' => 528,
  3178.                                 'image' => 'furbify-usb-other.png',
  3179.                             ], [
  3180.                                 // USB-Bluetooth
  3181.                                 'subCategoryId' => 525,
  3182.                                 'image' => 'furbify-usb-bluetooth.png',
  3183.                             ], [
  3184.                                 // Power-distribution-unit
  3185.                                 'subCategoryId' => 501,
  3186.                                 'image' => 'furbify-power-distribution-unit.png',
  3187.                             ], [
  3188.                                 // Network-accessory
  3189.                                 'subCategoryId' => 534,
  3190.                                 'image' => 'furbify-network-accessory.png',
  3191.                             ], [
  3192.                                 // microphone
  3193.                                 'subCategoryId' => 519,
  3194.                                 'image' => 'furbify-microphone.png',
  3195.                             ], /*[ // Keyboard
  3196.                                 'subCategoryId' => 507,
  3197.                                 'image' => 'furbify-keyboard.png'
  3198.                             ],*/
  3199.                             [
  3200.                                 // Other product
  3201.                                 'subCategoryId' => 568,
  3202.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  3203.                             ],
  3204.                         ],
  3205.                     ], [
  3206.                         // Software & Services
  3207.                         'title' => $translator->trans('Software & Services'),
  3208.                         'description' => $translator->trans('Software & Services description'),
  3209.                         'categoryId' => 44,
  3210.                         'categories' => [
  3211.                             [
  3212.                                 // Antivirus
  3213.                                 'subCategoryId' => 263,
  3214.                                 'image' => 'furbify-komp-eset.jpg',
  3215.                             ],
  3216.                         ],
  3217.                     ],
  3218.                 ];
  3219.                 break;
  3220.             case Domain::DOMAIN_ID_RT_CZ:
  3221.             case Domain::DOMAIN_ID_WS_CZ:
  3222.                 $sections = [
  3223.                     [
  3224.                         // Accessories
  3225.                         'title' => $translator->trans('Accessories'),
  3226.                         'description' => $translator->trans('Accessories description'),
  3227.                         'categoryId' => 118,
  3228.                         'categories' => [
  3229.                             [
  3230.                                 // USB wifi
  3231.                                 'subCategoryId' => 121,
  3232.                                 'image' => 'furbify-komp-usb-wifi.jpg',
  3233.                             ],
  3234.                             [
  3235.                                 // Notebook bag
  3236.                                 'subCategoryId' => 165,
  3237.                                 'image' => 'furbify-komp-notebook-taska.jpg',
  3238.                             ],
  3239.                             [
  3240.                                 // Docking stations
  3241.                                 'subCategoryId' => 140,
  3242.                                 'image' => 'furbify-komp-dokovacia-stanica-laptop-dokkolo.jpg',
  3243.                             ],
  3244.                             [
  3245.                                 // Webcamera
  3246.                                 'subCategoryId' => 196,
  3247.                                 'image' => 'furbify-komp-webkamera.jpg',
  3248.                             ],
  3249.                             [
  3250.                                 // Keyboard
  3251.                                 'subCategoryId' => 143,
  3252.                                 'image' => 'furbify-komp-klavesnica-billentyuzet.jpg',
  3253.                             ],
  3254.                             [
  3255.                                 // Mouse
  3256.                                 'subCategoryId' => 142,
  3257.                                 'image' => 'furbify-komp-mouse.jpg',
  3258.                             ],
  3259.                             [
  3260.                                 // Headphones
  3261.                                 'subCategoryId' => 230,
  3262.                                 'image' => 'furbify-komp-headphone-headset.jpg',
  3263.                             ],
  3264.                             [
  3265.                                 // Speaker
  3266.                                 'subCategoryId' => 211,
  3267.                                 'image' => 'furbify-komp-reproduktor.jpg',
  3268.                             ],
  3269.                             [
  3270.                                 // Notebook cooler
  3271.                                 'subCategoryId' => 215,
  3272.                                 'image' => 'furbify-komp-chladic-notebook-huto.jpg',
  3273.                             ],
  3274.                             [
  3275.                                 // USB flash
  3276.                                 'subCategoryId' => 233,
  3277.                                 'image' => 'furbify-komp-usb-pendrive.jpg',
  3278.                             ],
  3279.                             [
  3280.                                 // Memory card
  3281.                                 'subCategoryId' => 248,
  3282.                                 'image' => 'furbify-sd-card.jpg',
  3283.                                 // 'noPath' => true
  3284.                             ],
  3285.                             [
  3286.                                 // Printers
  3287.                                 'subCategoryId' => 176,
  3288.                                 'image' => 'furbify-printer.jpg',
  3289.                                 // 'noPath' => true
  3290.                             ],
  3291.                             [
  3292.                                 // Toner
  3293.                                 'subCategoryId' => 182,
  3294.                                 'image' => 'furbify-toner.jpg',
  3295.                                 // 'noPath' => true
  3296.                             ], [
  3297.                                 // Printing drums
  3298.                                 'subCategoryId' => 185,
  3299.                                 'image' => 'furbify-drum.jpg',
  3300.                                 // 'noPath' => true
  3301.                             ], [
  3302.                                 // Printer cartridge
  3303.                                 'subCategoryId' => 190,
  3304.                                 'image' => 'furbify-printer-cartridge.jpg',
  3305.                                 // 'noPath' => true
  3306.                             ], [
  3307.                                 // Surge arrest
  3308.                                 'subCategoryId' => 376,
  3309.                                 'image' => 'furbify-surge-arrest.png',
  3310.                             ], [
  3311.                                 // Mobile Case
  3312.                                 'subCategoryId' => 450,
  3313.                                 'image' => 'furbify-mobile-case.jpg',
  3314.                             ], [
  3315.                                 // UPS
  3316.                                 'subCategoryId' => 500,
  3317.                                 'image' => 'furbify-ups.png',
  3318.                             ], [
  3319.                                 // USB-other
  3320.                                 'subCategoryId' => 533,
  3321.                                 'image' => 'furbify-usb-other.png',
  3322.                             ], [
  3323.                                 // USB-Bluetooth
  3324.                                 'subCategoryId' => 527,
  3325.                                 'image' => 'furbify-usb-bluetooth.png',
  3326.                             ], [
  3327.                                 // Power-distribution-unit
  3328.                                 'subCategoryId' => 503,
  3329.                                 'image' => 'furbify-power-distribution-unit.png',
  3330.                             ], [
  3331.                                 // Network-accessory
  3332.                                 'subCategoryId' => 536,
  3333.                                 'image' => 'furbify-network-accessory.png',
  3334.                             ], [
  3335.                                 // microphone
  3336.                                 'subCategoryId' => 521,
  3337.                                 'image' => 'furbify-microphone.png',
  3338.                             ], /*[ // Keyboard
  3339.                                 'subCategoryId' => 509,
  3340.                                 'image' => 'furbify-keyboard.png'
  3341.                             ],*/
  3342.                             [
  3343.                                 // Other product
  3344.                                 'subCategoryId' => 564,
  3345.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  3346.                             ],
  3347.                         ],
  3348.                     ], [
  3349.                         // Software & Services
  3350.                         'title' => $translator->trans('Software & Services'),
  3351.                         'description' => $translator->trans('Software & Services description'),
  3352.                         'categoryId' => 118,
  3353.                         'categories' => [
  3354.                             [
  3355.                                 // Office
  3356.                                 'subCategoryId' => 159,
  3357.                                 'image' => 'furbify-komp-office.jpg',
  3358.                             ],
  3359.                             [
  3360.                                 // Antivirus
  3361.                                 'subCategoryId' => 266,
  3362.                                 'image' => 'furbify-komp-eset.jpg',
  3363.                             ],
  3364.                         ],
  3365.                     ],
  3366.                 ];
  3367.                 break;
  3368.             case Domain::DOMAIN_ID_RT_EU:
  3369.             case Domain::DOMAIN_ID_WS_EU:
  3370.                 $sections = [
  3371.                     [
  3372.                         // Accessories
  3373.                         'title' => $translator->trans('Accessories'),
  3374.                         'description' => $translator->trans('Accessories description'),
  3375.                         'categoryId' => 273,
  3376.                         'categories' => [
  3377.                             [
  3378.                                 // USB wifi
  3379.                                 'subCategoryId' => 276,
  3380.                                 'image' => 'furbify-komp-usb-wifi.jpg',
  3381.                             ],
  3382.                             [
  3383.                                 // Notebook bag
  3384.                                 'subCategoryId' => 319,
  3385.                                 'image' => 'furbify-komp-notebook-taska.jpg',
  3386.                             ],
  3387.                             [
  3388.                                 // Docking stations
  3389.                                 'subCategoryId' => 275,
  3390.                                 'image' => 'furbify-komp-dokovacia-stanica-laptop-dokkolo.jpg',
  3391.                             ],
  3392.                             [
  3393.                                 // Webcamera
  3394.                                 'subCategoryId' => 328,
  3395.                                 'image' => 'furbify-komp-webkamera.jpg',
  3396.                             ],
  3397.                             [
  3398.                                 // Keyboard
  3399.                                 'subCategoryId' => 298,
  3400.                                 'image' => 'furbify-komp-klavesnica-billentyuzet.jpg',
  3401.                             ],
  3402.                             [
  3403.                                 // Mouse
  3404.                                 'subCategoryId' => 297,
  3405.                                 'image' => 'furbify-komp-mouse.jpg',
  3406.                             ],
  3407.                             [
  3408.                                 // Headphones
  3409.                                 'subCategoryId' => 335,
  3410.                                 'image' => 'furbify-komp-headphone-headset.jpg',
  3411.                             ],
  3412.                             [
  3413.                                 // Speaker
  3414.                                 'subCategoryId' => 332,
  3415.                                 'image' => 'furbify-komp-reproduktor.jpg',
  3416.                             ],
  3417.                             [
  3418.                                 // Notebook cooler
  3419.                                 'subCategoryId' => 331,
  3420.                                 'image' => 'furbify-komp-chladic-notebook-huto.jpg',
  3421.                             ],
  3422.                             [
  3423.                                 // USB flash
  3424.                                 'subCategoryId' => 337,
  3425.                                 'image' => 'furbify-komp-usb-pendrive.jpg',
  3426.                             ],
  3427.                             [
  3428.                                 // Mobile case
  3429.                                 'subCategoryId' => 351,
  3430.                                 'image' => 'furbify-mobile-case.jpg',
  3431.                                 // 'noPath' => true
  3432.                             ],
  3433.                             [
  3434.                                 // Memory card
  3435.                                 'subCategoryId' => 354,
  3436.                                 'image' => 'furbify-sd-card.jpg',
  3437.                                 // 'noPath' => true
  3438.                             ],
  3439.                             [
  3440.                                 // Toner
  3441.                                 'subCategoryId' => 325,
  3442.                                 'image' => 'furbify-toner.jpg',
  3443.                                 // 'noPath' => true
  3444.                             ], [
  3445.                                 // Printing drums
  3446.                                 'subCategoryId' => 326,
  3447.                                 'image' => 'furbify-drum.jpg',
  3448.                                 // 'noPath' => true
  3449.                             ], [
  3450.                                 // Printer cartridge
  3451.                                 'subCategoryId' => 327,
  3452.                                 'image' => 'furbify-printer-cartridge.jpg',
  3453.                                 // 'noPath' => true
  3454.                             ], [
  3455.                                 // Surge arrest
  3456.                                 'subCategoryId' => 463,
  3457.                                 'image' => 'furbify-surge-arrest.png',
  3458.                             ], [
  3459.                                 // Other product
  3460.                                 'subCategoryId' => 464,
  3461.                                 'image' => 'furbify-other-product.png', // (kwap-kwap)
  3462.                             ],
  3463.                         ],
  3464.                     ], [
  3465.                         // Software & Services
  3466.                         'title' => $translator->trans('Software & Services'),
  3467.                         'description' => $translator->trans('Software & Services description'),
  3468.                         'categoryId' => 313,
  3469.                         'categories' => [
  3470.                             [
  3471.                                 // Office
  3472.                                 'subCategoryId' => 314,
  3473.                                 'image' => 'furbify-komp-office.jpg',
  3474.                             ],
  3475.                             [
  3476.                                 // Antivirus
  3477.                                 'subCategoryId' => 339,
  3478.                                 'image' => 'furbify-komp-eset.jpg',
  3479.                             ],
  3480.                         ],
  3481.                     ],
  3482.                 ];
  3483.                 break;
  3484.         }
  3485.         return $this->createCategorySectionsArray($sections, $seoUrlHelper, $productHelper, $multiStore);
  3486.     }
  3487.     /**
  3488.      * Get other products section with categories
  3489.      * @param Domain $domain
  3490.      * @param SeoUrlHelper $seoUrlHelper
  3491.      * @param ProductHelper $productHelper
  3492.      * @param MultiStore $multiStore
  3493.      * @return array
  3494.      */
  3495.     public function getOtherProductsSection(Domain $domain, SeoUrlHelper $seoUrlHelper, ProductHelper $productHelper, MultiStore $multiStore): array
  3496.     {
  3497.         $sections = [];
  3498.         $translator = $this->translator;
  3499.         switch ($domain->getId()) {
  3500.             case Domain::DOMAIN_ID_RT_SK:
  3501.             case Domain::DOMAIN_ID_WS_SK:
  3502.                 $sections = [
  3503.                     [
  3504.                         // Other categories
  3505.                         'title' => $translator->trans('Other categories title'),
  3506.                         'description' => '',
  3507.                         'categoryId' => 362,
  3508.                         'categories' => [
  3509.                             [
  3510.                                 // Tablet
  3511.                                 'subCategoryId' => 269,
  3512.                                 'image' => 'furbify-tablet.jpg',
  3513.                                 'noPath' => true,
  3514.                             ],
  3515.                             [
  3516.                                 // SmartWatch
  3517.                                 'subCategoryId' => 272,
  3518.                                 'image' => 'furbify-smartwatch.jpg',
  3519.                                 'noPath' => true,
  3520.                             ],
  3521.                             [
  3522.                                 // Printers
  3523.                                 'subCategoryId' => 114,
  3524.                                 'image' => 'furbify-printer.jpg',
  3525.                                 'noPath' => true,
  3526.                             ],
  3527.                             [
  3528.                                 // Projektors
  3529.                                 'subCategoryId' => 191,
  3530.                                 'image' => 'furbify-projektor.jpg',
  3531.                                 'noPath' => true,
  3532.                             ],
  3533.                             [
  3534.                                 // Visualiser
  3535.                                 'subCategoryId' => 529,
  3536.                                 'image' => 'furbify-visualiser.png',
  3537.                                 'noPath' => true,
  3538.                             ],
  3539.                         ],
  3540.                     ],
  3541.                 ];
  3542.                 break;
  3543.             case Domain::DOMAIN_ID_RT_HU:
  3544.             case Domain::DOMAIN_ID_WS_HU:
  3545.                 $sections = [
  3546.                     [
  3547.                         // Other categories
  3548.                         'title' => $translator->trans('Other categories title'),
  3549.                         'description' => '',
  3550.                         'categoryId' => 44,
  3551.                         'categories' => [
  3552.                             [
  3553.                                 // Tablet
  3554.                                 'subCategoryId' => 268,
  3555.                                 'image' => 'furbify-tablet.jpg',
  3556.                                 'noPath' => true,
  3557.                             ],
  3558.                             [
  3559.                                 // SmartWatch
  3560.                                 'subCategoryId' => 271,
  3561.                                 'image' => 'furbify-smartwatch.jpg',
  3562.                                 'noPath' => true,
  3563.                             ],
  3564.                             [
  3565.                                 // Printers
  3566.                                 'subCategoryId' => 115,
  3567.                                 'image' => 'furbify-printer.jpg',
  3568.                                 'noPath' => true,
  3569.                             ],
  3570.                             [
  3571.                                 // Projektors
  3572.                                 'subCategoryId' => 192,
  3573.                                 'image' => 'furbify-projektor.jpg',
  3574.                                 'noPath' => true,
  3575.                             ],
  3576.                             [
  3577.                                 // Visualiser
  3578.                                 'subCategoryId' => 530,
  3579.                                 'image' => 'furbify-visualiser.png',
  3580.                                 'noPath' => true,
  3581.                             ],
  3582.                         ],
  3583.                     ],
  3584.                 ];
  3585.                 break;
  3586.             case Domain::DOMAIN_ID_RT_CZ:
  3587.             case Domain::DOMAIN_ID_WS_CZ:
  3588.                 $sections = [
  3589.                     [
  3590.                         // Other categories
  3591.                         'title' => $translator->trans('Other categories title'),
  3592.                         'description' => '',
  3593.                         'categoryId' => 118,
  3594.                         'categories' => [
  3595.                             [
  3596.                                 // Tablet
  3597.                                 'subCategoryId' => 270,
  3598.                                 'image' => 'furbify-tablet.jpg',
  3599.                                 'noPath' => true,
  3600.                             ],
  3601.                             [
  3602.                                 // SmartWatch
  3603.                                 'subCategoryId' => 364,
  3604.                                 'image' => 'furbify-smartwatch.jpg',
  3605.                                 'noPath' => true,
  3606.                             ],
  3607.                             [
  3608.                                 // Printers
  3609.                                 'subCategoryId' => 176,
  3610.                                 'image' => 'furbify-printer.jpg',
  3611.                                 'noPath' => true,
  3612.                             ],
  3613.                             [
  3614.                                 // Projektors
  3615.                                 'subCategoryId' => 193,
  3616.                                 'image' => 'furbify-projektor.jpg',
  3617.                                 'noPath' => true,
  3618.                             ],
  3619.                             [
  3620.                                 // Visualiser
  3621.                                 'subCategoryId' => 531,
  3622.                                 'image' => 'furbify-visualiser.png',
  3623.                                 'noPath' => true,
  3624.                             ],
  3625.                         ],
  3626.                     ],
  3627.                 ];
  3628.                 break;
  3629.             case Domain::DOMAIN_ID_RT_EU:
  3630.             case Domain::DOMAIN_ID_WS_EU:
  3631.                 $sections = [
  3632.                     [
  3633.                         // Other categories
  3634.                         'title' => $translator->trans('Other categories title'),
  3635.                         'description' => '',
  3636.                         'categoryId' => 273,
  3637.                         'categories' => [
  3638.                             [
  3639.                                 // Tablet
  3640.                                 'subCategoryId' => 360,
  3641.                                 'image' => 'furbify-tablet.jpg',
  3642.                                 'noPath' => true,
  3643.                             ],
  3644.                             [
  3645.                                 // SmartWatch
  3646.                                 'subCategoryId' => 361,
  3647.                                 'image' => 'furbify-smartwatch.jpg',
  3648.                                 'noPath' => true,
  3649.                             ],
  3650.                             [
  3651.                                 // Printers
  3652.                                 'subCategoryId' => 324,
  3653.                                 'image' => 'furbify-printer.jpg',
  3654.                                 'noPath' => true,
  3655.                             ],
  3656.                             [
  3657.                                 // Projektors
  3658.                                 'subCategoryId' => 348,
  3659.                                 'image' => 'furbify-projektor.jpg',
  3660.                                 'noPath' => true,
  3661.                             ],
  3662.                         ],
  3663.                     ],
  3664.                 ];
  3665.                 break;
  3666.         }
  3667.         return $this->createCategorySectionsArray($sections, $seoUrlHelper, $productHelper, $multiStore);
  3668.     }
  3669.     /**
  3670.      * @param array $sectionsArray
  3671.      * @param SeoUrlHelper $seoUrlHelper
  3672.      * @param ProductHelper $productHelper
  3673.      * @param MultiStore $multiStore
  3674.      * @return array
  3675.      */
  3676.     private function createCategorySectionsArray($sectionsArray, SeoUrlHelper $seoUrlHelper, ProductHelper $productHelper, MultiStore $multiStore): array
  3677.     {
  3678.         // Request
  3679.         $request = $seoUrlHelper->getRequest();
  3680.         // Entity Manager
  3681.         $entityManager = $this->getEntityManager();
  3682.         // Category Page Repository
  3683.         $categoryPageRepository = $entityManager->getRepository(CategoryPage::class);
  3684.         $categoryPageRepository->setSession($request->getSession());
  3685.         // Find alternative categories if language is switched
  3686.         if ($multiStore->isLanguageSwitched()) {
  3687.             foreach ($sectionsArray as $sectionKey => $section) {
  3688.                 // Category ID
  3689.                 $categoryId = $section['categoryId'];
  3690.                 // Category Page
  3691.                 $categoryPage = $categoryPageRepository->find($categoryId);
  3692.                 // Alternative Category Page
  3693.                 $alternativeCategoryPage = $categoryPageRepository->getAlternativeCategoryByLanguage($categoryPage);
  3694.                 // Skip if alternative category page was not found
  3695.                 if (is_null($alternativeCategoryPage)) {
  3696.                     unset($sectionsArray[$sectionKey]);
  3697.                     continue;
  3698.                 }
  3699.                 // Sub-Categories
  3700.                 foreach ($section['categories'] as $categoryKey => $category) {
  3701.                     // Sub-Category ID
  3702.                     $subCategoryId = $category['subCategoryId'];
  3703.                     // Sub-Category Page
  3704.                     $subCategoryPage = $categoryPageRepository->find($subCategoryId);
  3705.                     // Skip if sub-category page was not found
  3706.                     if (empty($subCategoryPage)) {
  3707.                         unset($sectionsArray[$sectionKey]['categories'][$categoryKey]);
  3708.                         continue;
  3709.                     }
  3710.                     // Alternative Sub-Category Page
  3711.                     $alternativeSubCategoryPage = $categoryPageRepository->getAlternativeCategoryByLanguage($subCategoryPage);
  3712.                     // Sub-Category Page Meta
  3713.                     if (!is_null($alternativeSubCategoryPage)) {
  3714.                         $subCategoryMeta = $alternativeSubCategoryPage->getMetaByLanguage($multiStore->getLanguage());
  3715.                     } else {
  3716.                         $subCategoryMeta = $subCategoryPage->getMetaByLanguage($multiStore->getDefaultLanguage());
  3717.                     }
  3718.                     // Skip if sub-category page meta was not found
  3719.                     if (empty($subCategoryMeta)) {
  3720.                         unset($sectionsArray[$sectionKey]['categories'][$categoryKey]);
  3721.                         continue;
  3722.                     }
  3723.                     // Sub-Category Title
  3724.                     $sectionsArray[$sectionKey]['categories'][$categoryKey]['name'] = $subCategoryMeta->getTitle();
  3725.                     // Sub-Category Path
  3726.                     $path = $categoryId . '_' . $subCategoryId;
  3727.                     if (isset($sectionsArray[$sectionKey]['categories'][$categoryKey]['noPath'])
  3728.                         && $sectionsArray[$sectionKey]['categories'][$categoryKey]['noPath'] == true) {
  3729.                         $path = $subCategoryId;
  3730.                     }
  3731.                     // Sun-Category Url
  3732.                     $sectionsArray[$sectionKey]['categories'][$categoryKey]['href'] = str_replace('http:', 'https:', $seoUrlHelper->link('category', [
  3733.                         'path' => $path, 
  3734.                         'isBazaar'  => $subCategoryPage->isShowOnlyBazaar()
  3735.                     ]));
  3736.                     // Sub-Category amount of products
  3737.                     $sectionsArray[$sectionKey]['categories'][$categoryKey]['productCount'] = $productHelper->countInCategory([
  3738.                         'filterCategoryId' => $subCategoryId,
  3739.                     ]);
  3740.                 }
  3741.             }
  3742.         } // Otherwise
  3743.         else {
  3744.             foreach ($sectionsArray as $sectionKey => $section) {
  3745.                 // Category ID
  3746.                 $categoryId = $section['categoryId'];
  3747.                 // Sub-Categories
  3748.                 foreach ($section['categories'] as $categoryKey => $category) {
  3749.                     // Sub-Category ID
  3750.                     $subCategoryId = $category['subCategoryId'];
  3751.                     // Sub-Category Page
  3752.                     $subCategoryPage = $categoryPageRepository->find($subCategoryId);
  3753.                     // Skip if sub-category page was not found
  3754.                     if (empty($subCategoryPage)) {
  3755.                         unset($sectionsArray[$sectionKey]['categories'][$categoryKey]);
  3756.                         continue;
  3757.                     }
  3758.                     // Sub-Category Page Meta
  3759.                     $subCategoryMeta = $subCategoryPage->getMetaByLanguage($multiStore->getLanguage());
  3760.                     // Skip if sub-category page meta was not found
  3761.                     if (empty($subCategoryMeta)) {
  3762.                         unset($sectionsArray[$sectionKey]['categories'][$categoryKey]);
  3763.                         continue;
  3764.                     }
  3765.                     // Sub-Category Title
  3766.                     $sectionsArray[$sectionKey]['categories'][$categoryKey]['name'] = $subCategoryMeta->getTitle();
  3767.                     // Sub-Category Path
  3768.                     $path = $categoryId . '_' . $subCategoryId;
  3769.                     if (isset($sectionsArray[$sectionKey]['categories'][$categoryKey]['noPath'])
  3770.                         && $sectionsArray[$sectionKey]['categories'][$categoryKey]['noPath'] == true) {
  3771.                         $path = $subCategoryId;
  3772.                     }
  3773.                     // Sun-Category Url
  3774.                     $sectionsArray[$sectionKey]['categories'][$categoryKey]['href'] = str_replace('http:', 'https:', $seoUrlHelper->link('category', [
  3775.                         'path' => $path,
  3776.                         'isBazaar'  => $subCategoryPage->isShowOnlyBazaar()
  3777.                     ]));
  3778.                     // Sub-Category amount of products
  3779.                     $sectionsArray[$sectionKey]['categories'][$categoryKey]['productCount'] = $productHelper->countInCategory([
  3780.                         'filterCategoryId' => $subCategoryId,
  3781.                     ]);
  3782.                 }
  3783.             }
  3784.         }
  3785.         return $sectionsArray;
  3786.     }
  3787.     /**
  3788.      * @param string $slug
  3789.      * @param int $categoryId
  3790.      * @return int
  3791.      */
  3792.     public function checkRouteSlugForOtherCategoriesPages(string $slug, int $categoryId): int
  3793.     {
  3794.         switch ($slug) {
  3795.             case 'prislusenstvo/dalsie-kategorie':
  3796.                 $categoryId = 563;
  3797.                 break;
  3798.             case 'prislusenstvi/ostatni-kategorie':
  3799.                 $categoryId = 564;
  3800.                 break;
  3801.         }
  3802.         return $categoryId;
  3803.     }
  3804.     /**
  3805.      * @param Request $request
  3806.      * @param array $visibleDomains
  3807.      * @param array $listOrderBy
  3808.      * @return array
  3809.      * @throws Exception
  3810.      * @throws \Doctrine\DBAL\Driver\Exception
  3811.      */
  3812.     public function listProductByFilter(Request $request, array $visibleDomains, array $listOrderBy): array
  3813.     {
  3814.         $nsQuery = $request->query;
  3815.         $searchPage = $nsQuery->has('search_page') && $nsQuery->has('search_page') == 1;
  3816.         $search_string = $nsQuery->has('search_string') ? trim($nsQuery->get('search_string')) : '';
  3817.         $filterDomainListed = false;
  3818.         foreach ($visibleDomains as $domainId) {
  3819.             if ($nsQuery->has('domain_' . $domainId . '_listed')) {
  3820.                 $filterDomainListed = true;
  3821.                 break;
  3822.             }
  3823.         }
  3824.         if (
  3825.             !(
  3826.                 !$searchPage ||
  3827.                 $search_string != '' ||
  3828.                 $nsQuery->has('available') ||
  3829.                 $nsQuery->has('in_stock') ||
  3830.                 $nsQuery->has('shadow') ||
  3831.                 $nsQuery->has('negatives') ||
  3832.                 $nsQuery->has('product_type') ||
  3833.                 $nsQuery->has('vendor') ||
  3834.                 $nsQuery->get('on-heureka-feed') ||
  3835.                 $nsQuery->get('product_filter') ||
  3836.                 $nsQuery->get('product_model_meta') ||
  3837.                 $filterDomainListed)
  3838.         ) {
  3839.             return [
  3840.                 'products' => [],
  3841.                 'count' => 0,
  3842.             ];
  3843.         }
  3844.         $orderParam = $request->query->get('order');
  3845.         $order_by = 'C';
  3846.         $order_type = 'ASC';
  3847.         $onHeurekaFilter = false;
  3848.         $filterHasBadgeParam = false;
  3849.         if (strlen($orderParam) == 2 && array_key_exists($orderParam[0], $listOrderBy)) {
  3850.             $order_by = strtoupper($orderParam[0]);
  3851.             $order_type = ($orderParam[1] === 'A') ? 'ASC' : 'DESC';
  3852.         }
  3853.         $where = '1=1';
  3854.         $whereParams = [];
  3855.         if (!is_null($nsQuery)) {
  3856.             if ($nsQuery->has('available') && (int)$nsQuery->get('available') === 1) {
  3857.                 $where .= ' AND p.available > 0';
  3858.             }
  3859.             if ($nsQuery->has('in_stock') && (int)$nsQuery->get('in_stock') === 1) {
  3860.                 $where .= ' AND p.in_stock > 0';
  3861.             }
  3862.             if ($nsQuery->has('product_filter') && (int)$nsQuery->get('product_filter') !== 0) {
  3863.                 $where .= ' AND p.product_model_id = ' . (int)$nsQuery->get('product_filter');
  3864.             }
  3865.             if ($nsQuery->get('shadow') == 1) {
  3866.                 $where .= ' AND p.out_of_stock_status = 4';
  3867.             }
  3868.             if ($nsQuery->get('negatives') == 1) {
  3869.                 $where .= ' AND (p.in_stock < 0 OR p.available < 0)';
  3870.             }
  3871.             if (!$nsQuery->has('archived') || ($nsQuery->has('archived') && (int)$nsQuery->get('archived') === 0)) {
  3872.                 $where .= ' AND p.archived = 0';
  3873.             } else {
  3874.                 $where .= ' AND p.archived = 1';
  3875.             }
  3876.             $discountedValue = null;
  3877.             $implodeSeparator = 'OR';
  3878.             if ($nsQuery->get('discounted') == 1) {
  3879.                 $discountedValue = 1;
  3880.             } elseif ($nsQuery->get('discounted') == 2) {
  3881.                 $discountedValue = 0;
  3882.                 $implodeSeparator = 'AND';
  3883.             }
  3884.             if ($discountedValue !== null) {
  3885.                 $whereDomains = [];
  3886.                 foreach ($visibleDomains as $domainId) {
  3887.                     $whereDomains[] = 'pm' . (int)$domainId . ".discounted = $discountedValue";
  3888.                 }
  3889.                 $where .= ' AND ( ' . implode(" $implodeSeparator ", $whereDomains) . ' )';
  3890.             }
  3891.             if ($nsQuery->get('bestseller') == 1) {
  3892.                 $whereDomains = [];
  3893.                 foreach ($visibleDomains as $domainId) {
  3894.                     $whereDomains[] = 'pm' . (int)$domainId . '.best_seller = 1';
  3895.                 }
  3896.                 $where .= ' AND ( ' . implode(' OR ', $whereDomains) . ' )';
  3897.             }
  3898.             if ($nsQuery->get('on-heureka-feed') == 1) {
  3899.                 $where .= ' AND (p.available > 0 AND p.archived = 0 AND p.quality_status IN (' . Product::QualityStatus_Platinum . ' ,' . Product::QualityStatus_New . ') AND pt.id != ' . ProductType::PRODUCT_TYPE_ID_SOFTWARE . " AND pt.title != 'do not include')";
  3900.                 $onHeurekaFilter = true;
  3901.             }
  3902.             if ($nsQuery->get('flash-sale') == 1) {
  3903.                 $whereDomains = [];
  3904.                 foreach ($visibleDomains as $domainId) {
  3905.                     $whereDomains[] = '( pm' . (int)$domainId . '.sale_start IS NOT NULL AND pm' . (int)$domainId . '.sale_end IS NOT NULL AND pm' . (int)$domainId . '.sale_start < NOW() AND pm' . (int)$domainId . '.sale_end > NOW() ) OR ( pm' . (int)$domainId . '.sale_start IS NOT NULL AND pm' . (int)$domainId . '.sale_end IS NULL AND pm' . (int)$domainId . '.sale_start < NOW() ) OR ( pm' . (int)$domainId . '.sale_start IS NULL AND pm' . (int)$domainId . '.sale_end IS NOT NULL AND pm' . (int)$domainId . '.sale_end > NOW())';
  3906.                 }
  3907.                 $where .= ' AND ( ' . implode(' OR ', $whereDomains) . ' )';
  3908.             }
  3909.             if ($nsQuery->get('product_or_container') == 1) {
  3910.                 $where .= ' AND p.container = 0';
  3911.             } elseif ($nsQuery->get('product_or_container') == 2) {
  3912.                 $where .= ' AND p.container = 1';
  3913.             }
  3914.             if ($nsQuery->get('scannable') == 1) {
  3915.                 $where .= ' AND p.scannable = 1';
  3916.             } elseif ($nsQuery->get('scannable') == 2) {
  3917.                 $where .= ' AND p.scannable = 0';
  3918.             }
  3919.             if ($nsQuery->get('photo_status') == 1) {
  3920.                 $where .= ' AND (SELECT COUNT(ph.id) FROM photos ph WHERE album_id = a.id) > 0';
  3921.             } elseif ($nsQuery->get('photo_status') == 2) {
  3922.                 $where .= ' AND (SELECT COUNT(ph.id) FROM photos ph WHERE album_id = a.id) = 0';
  3923.             }
  3924.             if ($nsQuery->get('product_tag_status') == 1) {
  3925.                 $where .= ' AND (SELECT COUNT(ptp.producttag_id) FROM product_tags_products ptp WHERE ptp.product_id = p.id) > 0';
  3926.             } elseif ($nsQuery->get('product_tag_status') == 2) {
  3927.                 $where .= ' AND (SELECT COUNT(ptp.producttag_id) FROM product_tags_products ptp WHERE ptp.product_id = p.id) = 0';
  3928.             }
  3929.             if (in_array($nsQuery->get('position'), [1, 2])) {
  3930.                 if ($nsQuery->get('position') == 1) {
  3931.                     $where .= ' AND (p.stock_position IS NULL OR p.stock_position = "")';
  3932.                 } else {
  3933.                     $where .= ' AND p.stock_position IS NOT NULL AND p.stock_position != ""';
  3934.                 }
  3935.             }
  3936.             if ($nsQuery->has('product_model_meta')) {
  3937.                 $productIds = $this->getEntityManager()->getRepository(Product::class)
  3938.                     ->createQueryBuilder('p')
  3939.                     ->select('DISTINCT p.id')
  3940.                     ->innerJoin('p.productModel', 'pm')
  3941.                     ->innerJoin('pm.productModelMetas', 'm')
  3942.                     ->where('m.text IS NOT NULL')
  3943.                     ->getQuery()->getScalarResult();
  3944.                 $ids = array_column($productIds, 'id');
  3945.                 if ($nsQuery->get('product_model_meta') == 1) {
  3946.                     $where .= ' AND p.id IN (' . implode(',', $ids) . ')';
  3947.                 } else {
  3948.                     $where .= ' AND p.id NOT IN (' . implode(',', $ids) . ')';
  3949.                 }
  3950.             }
  3951.             if ($nsQuery->has('product_type')) {
  3952.                 $productTypes = $nsQuery->get('product_type');
  3953.                 $parts = [17, 43, 50, 110, 114, 113, 55, 145, 146, 147, 149, 150, 151, 153, 154, 155, 156, 159, 160, 164, 167, 170, 180, 181, 184, 185, 186, 187, 191, 192, 193, 199, 202, 203, 204, 205, 212, 218, 220];
  3954.                 if (is_array($productTypes)) {
  3955.                     $values = [];
  3956.                     foreach ($productTypes as $productTypeId) {
  3957.                         if ((int)$productTypeId == -2) {
  3958.                             $values = array_merge($values, $parts);
  3959.                         } else {
  3960.                             $values[] = (int)$productTypeId;
  3961.                         }
  3962.                     }
  3963.                 } else {
  3964.                     $values = [];
  3965.                     foreach (explode(',', $productTypes) as $productTypeId) {
  3966.                         if ((int)$productTypeId == -2) {
  3967.                             $values = array_merge($values, $parts);
  3968.                         } else {
  3969.                             $values[] = (int)$productTypeId;
  3970.                         }
  3971.                     }
  3972.                 }
  3973.                 if ($values) {
  3974.                     $where .= ' AND (p.type_id IN (' . implode(',', $values) . ') OR pmpt.id IN (' . implode(',', $values) . ')) ';
  3975.                 }
  3976.             }
  3977.             if ($nsQuery->has('badge')) {
  3978.                 $badges = $nsQuery->get('badge');
  3979.                 if ($badges) {
  3980.                     $selectedBadges = [];
  3981.                     foreach (explode(',', $badges) as $badge) {
  3982.                         $selectedBadges[] = $badge;
  3983.                     }
  3984.                 }
  3985.                 if ($selectedBadges) {
  3986.                     $where .= ' AND (';
  3987.                     foreach ($selectedBadges as $badge) {
  3988.                         $where .= "p.badges LIKE '%$badge\"%' OR ";
  3989.                     }
  3990.                     $where = substr($where, 0, -4) . ')';
  3991.                 }
  3992.                 $filterHasBadgeParam = true;
  3993.             }
  3994.             if ($nsQuery->has('vendor')) {
  3995.                 $vendor = $nsQuery->get('vendor');
  3996.                 if (is_array($vendor)) {
  3997.                     $values = [];
  3998.                     foreach ($vendor as $vendorId) {
  3999.                         $values[] = (int)$vendorId;
  4000.                     }
  4001.                 } else {
  4002.                     $values = [];
  4003.                     foreach (explode(',', $vendor) as $vendorId) {
  4004.                         $values[] = (int)$vendorId;
  4005.                     }
  4006.                 }
  4007.                 if ($values) {
  4008.                     $where .= ' AND (p.vendor_id IN (' . implode(',', $values) . ') OR pmv.id IN (' . implode(',', $values) . ')) ';
  4009.                 }
  4010.             }
  4011.             foreach ($visibleDomains as $domainId) {
  4012.                 if ($nsQuery->has('domain_' . $domainId . '_listed')) {
  4013.                     $where .= $this->getWhereFilter($nsQuery->get('domain_' . $domainId . '_listed'), $domainId);
  4014.                 }
  4015.             }
  4016.             if ($nsQuery->has('quality')) {
  4017.                 $qualities = $nsQuery->get('quality');
  4018.                 $where .= ' AND p.quality_status IN (' . implode(',', explode(',', $qualities)) . ')';
  4019.             }
  4020.             if ($nsQuery->has('tag')) {
  4021.                 $where .= ' AND (
  4022.                         SELECT COUNT(ptp.producttag_id) 
  4023.                         FROM product_tags_products ptp 
  4024.                         WHERE ptp.product_id = p.id
  4025.                         AND ptp.producttag_id IN (' . implode(',', explode(',', $nsQuery->get('tag'))) . ')
  4026.                         ) > 0';
  4027.             }
  4028.             if ($nsQuery->has('model_type') && $nsQuery->get('model_type') != 0) {
  4029.                 $where .= ' AND pm.is_product_v2 = ' . ($nsQuery->get('model_type') == 1 ? '0' : '1');
  4030.             }
  4031.         }
  4032.         // Connection
  4033.         $connection = $this->getEntityManager()->getConnection();
  4034.         // Hits
  4035.         $hits = [];
  4036.         if ($search_string != '' && $search_string != 'error') {
  4037.             $expressions = $orWhere = [];
  4038.             // Only Product Name - Exact Match
  4039.             if ($nsQuery->has('onlyProductNameExact') && (int)$nsQuery->get('onlyProductNameExact') == 1) {
  4040.                 $expression = trim($search_string);
  4041.                 $where .= " AND p.name LIKE '%$expression%'";
  4042.             } // Only Product Name
  4043.             elseif ($nsQuery->has('onlyProductName') && (int)$nsQuery->get('onlyProductName') == 1) {
  4044.                 $expression = strtok($search_string, ' ');
  4045.                 while ($expression !== false) {
  4046.                     $expressions[] = trim($expression);
  4047.                     $expression = strtok(' ');
  4048.                 }
  4049.                 if (count($expressions) > 0) {
  4050.                     foreach ($expressions as $expression) {
  4051.                         $orWhere[] = "p.name LIKE '%$expression%'";
  4052.                     }
  4053.                 }
  4054.                 if (!empty($orWhere)) {
  4055.                     $where .= ' AND ( ' . implode(' OR ', $orWhere) . ' )';
  4056.                 }
  4057.             } else {
  4058.                 // Stock Position
  4059.                 $stockPosition = $this->getEntityManager()->getRepository(StockPosition::class)->findOneBy([
  4060.                     'name' => $search_string,
  4061.                     'archived' => 0,
  4062.                 ]);
  4063.                 if ($stockPosition) {
  4064.                     $spId = (int)$stockPosition->getId();
  4065.                     $where .= " AND ( p.stock_position LIKE '$spId,%' OR p.stock_position LIKE '%,$spId' OR p.stock_position LIKE '%,$spId,%' OR p.stock_position LIKE '$spId' )";
  4066.                 } else {
  4067.                     $stockPositions = $connection->fetchAllAssociative(
  4068.                         'SELECT sp.id, sp.name FROM stock_positions AS sp WHERE sp.name LIKE ' . $connection->quote($search_string . '%') . ''
  4069.                     );
  4070.                     if (strpos($search_string, '|') === false) {
  4071.                         $expression = strtok($search_string, ' ');
  4072.                         while ($expression !== false) {
  4073.                             $expressions[] = trim($expression);
  4074.                             $expression = strtok(' ');
  4075.                         }
  4076.                         if (count($expressions) > 0) {
  4077.                             foreach ($expressions as $expression) {
  4078.                                 $orWhere[] = "p.name LIKE '%$expression%'";
  4079.                                 $orWhere[] = "p.detail LIKE '%$expression%'";
  4080.                                 $orWhere[] = "p.cache_description LIKE '%$expression%'";
  4081.                                 $orWhere[] = "v.name LIKE '%$expression%'";
  4082.                                 $orWhere[] = "pt.title LIKE '%$expression%'";
  4083.                                 $hits[] = "IF (( p.cache_description LIKE '%$expression%' OR p.name LIKE '%$expression%' OR v.name LIKE '%$expression%' OR pt.title LIKE '%$expression%' ), 1, 0 )";
  4084.                             }
  4085.                         }
  4086.                     } else {
  4087.                         $orWhere[] = "p.cache_description LIKE '%$search_string%'";
  4088.                     }
  4089.                     if (!empty($stockPositions)) {
  4090.                         $findByStockPosition = [];
  4091.                         foreach ($stockPositions as $stockPosition) {
  4092.                             $spId = (int)$stockPosition['id'];
  4093.                             $findByStockPosition[] = "( p.stock_position LIKE '$spId,%' OR p.stock_position LIKE '%,$spId' OR p.stock_position LIKE '%,$spId,%' OR p.stock_position LIKE '$spId' )";
  4094.                         }
  4095.                     } else {
  4096.                         $findByStockPosition = null;
  4097.                     }
  4098.                     $where .= " AND
  4099.                         (
  4100.                             p.code LIKE '%$search_string%' OR
  4101.                             p.detail LIKE '%$search_string%' OR
  4102.                             pt.title LIKE '%$search_string%' OR
  4103.                             v.name LIKE '%$search_string%' OR
  4104.                             CONCAT(v.name, ' ', pt.title, ' ', p.name) LIKE '%$search_string%' OR
  4105.                             CONCAT(v.name, ' ', p.name, ' ', pt.title) LIKE '%$search_string%' OR
  4106.                             CONCAT(pt.title, ' ', v.name, ' ', pt.title) LIKE '%$search_string%' OR 
  4107.                             ( 
  4108.                                 SELECT COUNT(_pc.id) FROM product_tags_products _pcp, product_tag _pc WHERE _pcp.product_id = p.id AND _pcp.producttag_id = _pc.id AND _pc.enabled = TRUE AND _pc.type = 2 AND (
  4109.                                     ( _pc.startDate IS NULL AND _pc.endDate IS NULL ) OR 
  4110.                                     ( _pc.startDate IS NULL AND _pc.endDate >= NOW() ) OR 
  4111.                                     ( _pc.endDate IS NULL AND _pc.startDate <= NOW() ) OR 
  4112.                                     ( _pc.endDate >= NOW() AND _pc.startDate <= NOW() )
  4113.                                 ) AND _pc.title LIKE '%$search_string%' 
  4114.                             ) > 0 OR 
  4115.                             p.part_number LIKE '%$search_string%' OR
  4116.                             p.ean LIKE '%$search_string%' OR
  4117.                             p.gtin LIKE '%$search_string%' " . (!empty($orWhere) ? ' OR (( ' . implode(' OR ', $orWhere) . ' ) ' . (!empty($hits) ? ' AND (' . implode(' + ', $hits) . ' ) >= ' . count($expressions) : '') . ')' : '') . '
  4118.                             ' . (!is_null($findByStockPosition) ? ' OR ( ' . implode(' OR ', $findByStockPosition) . ' )' : '') . '
  4119.                         )';
  4120.                 }
  4121.             }
  4122.         }
  4123.         $productMetaSelects = '';
  4124.         $productMetaLeftJoins = '';
  4125.         $productTagSelects = '';
  4126.         $i = 0;
  4127.         foreach ($visibleDomains as $domainId) {
  4128.             $i++;
  4129.             $domainId = (int)$domainId;
  4130.             $productMetaSelects .= 'pm' . $domainId . '.price price' . $domainId . ', pm' . $domainId . '.discount discount' . $domainId . ', pm' . $domainId . '.discounted discounted' . $domainId . ', pm' . $domainId . '.active active' . $domainId . ', pm' . $domainId . '.listed listed' . $domainId . ', pm' . $domainId . '.vat vat' . $domainId . ', pm' . $domainId . '.bazaar bz' . $domainId . ', pm' . $domainId . '.re_lenovo rl' . $domainId . ', pm' . $domainId . '.hp_renew hr' . $domainId . ', pm' . $domainId . '.best_seller bs' . $domainId . ', IF(((pm' . $domainId . '.sale_start IS NOT NULL AND pm' . $domainId . '.sale_end IS NOT NULL AND pm' . $domainId . '.sale_start < NOW() AND pm' . $domainId . '.sale_end > NOW())OR(pm' . $domainId . '.sale_start IS NOT NULL AND pm' . $domainId . '.sale_end IS NULL AND pm' . $domainId . '.sale_start < NOW())OR(pm' . $domainId . '.sale_start IS NULL AND pm' . $domainId . '.sale_end IS NOT NULL AND pm' . $domainId . '.sale_end > NOW())),1,0) fs' . $domainId;
  4131.             $productMetaLeftJoins .= 'LEFT JOIN products__metas pm' . $domainId . ' ON pm' . $domainId . '.domain_id = ' . $domainId . ' AND pm' . $domainId . '.product_id = p.id ';
  4132.             $productTagSelects .= '(SELECT pc.id
  4133.                 FROM product_tags_products pcp, product_tag pc
  4134.                 WHERE pcp.product_id = p.id
  4135.                     AND pcp.producttag_id = pc.id
  4136.                     AND pc.domain_id = ' . $domainId . '
  4137.                     AND pc.enabled = TRUE
  4138.                     AND pc.type = 2
  4139.                     AND (
  4140.                         (pc.startDate IS NULL AND pc.endDate IS NULL)
  4141.                         OR (pc.startDate IS NULL AND pc.endDate >= NOW())
  4142.                         OR (pc.endDate IS NULL AND pc.startDate <= NOW())
  4143.                         OR (pc.endDate >= NOW() AND pc.startDate <= NOW())
  4144.                     )
  4145.                 LIMIT 1
  4146.             ) AS product_tag' . $domainId . ', ';
  4147.             if (count($visibleDomains) > $i) {
  4148.                 $productMetaSelects .= ',';
  4149.             }
  4150.         }
  4151.         //-> Pagination
  4152.         $page = $nsQuery->get('page') == 'all' ? $nsQuery->get('page') : (int)$nsQuery->get('page');
  4153.         if ($search_string == 'error') {
  4154.             $page = 'all';
  4155.         }
  4156.         if ($onHeurekaFilter) {
  4157.             $productMetaLeftJoins .= 'INNER JOIN products__metas pmh ON pmh.product_id = p.id AND pmh.domain_id IN (' . Domain::DOMAIN_ID_RT_SK . ',' . Domain::DOMAIN_ID_WS_SK . ') AND pmh.active = 1 ';
  4158.         }
  4159.         $statement = $connection->prepare('SELECT COUNT(DISTINCT p.id) as total FROM products p LEFT JOIN product_types pt ON p.type_id = pt.id LEFT JOIN product_model pm ON p.product_model_id = pm.id LEFT JOIN product_types pmpt ON pmpt.id = pm.type_id LEFT JOIN vendors pmv ON pmv.id = pm.vendor_id LEFT JOIN vendors v ON v.id = p.vendor_id ' . $productMetaLeftJoins . ' LEFT JOIN albums a ON a.id = p.photo_album_id WHERE ' . $where);
  4160.         foreach ($whereParams as $key => $value) {
  4161.             $statement->bindValue($key, $value);
  4162.         }
  4163.         $total = $statement->executeQuery()->fetchAllAssociative()[0]['total'];
  4164.         $limit = $page == 'all' ? $total : 300;
  4165.         $page = $page == 'all' ? 1 : $page;
  4166.         $offset = 0;
  4167.         if ($total > $limit) {
  4168.             if ($page > ceil($total / $limit)) {
  4169.                 $page = ceil($total / $limit);
  4170.             }
  4171.             if ($page < 1) {
  4172.                 $page = 1;
  4173.             }
  4174.             $offset = ($page - 1) * $limit;
  4175.         }
  4176.         //<- Pagination
  4177.         $stmt = $connection->prepare(
  4178.             'SELECT p.id as product_id, p.id, p.code, p.name, p.archived, p.cache_description, p.available, p.out_of_stock_status, p.in_stock, p.updated_by_time, p.stock_position, p.container, p.scannable, p.ean, p.part_number, p.gtin, 
  4179.             pt.id type_id, pt.title type, pmpt.id model_product_type_id, pmpt.title model_product_type,
  4180.             v.id vendor_id, v.name vendor, pmv.id model_product_vendor_id, pmv.name model_product_vendor, 
  4181.             pm.is_product_v2, 
  4182.             p.last_price, p.avg_price, p.spare_part_card, p.spare_part_price,' . ($filterHasBadgeParam ? ' p.badges,' : '') . ' CONCAT(updatedBy.first_name, " ", updatedBy.last_name) AS updated_by,
  4183.             (SELECT COUNT(smi.id) FROM stock_movement_item smi WHERE smi.product_id = p.id) AS sm_count,
  4184.             COALESCE(
  4185.                 ( SELECT _ph.cloudflare_id FROM photos AS _ph WHERE _ph.album_id = p.photo_album_id ORDER BY _ph.sort_index ASC LIMIT 1 ),
  4186.                 ( SELECT _phm.cloudflare_id FROM photos AS _phm WHERE _phm.album_id = pm.photo_album_id ORDER BY _phm.sort_index ASC LIMIT 1 ),
  4187.                 \'\'
  4188.             ) AS photo' . ($productTagSelects == '' && $productMetaSelects == '' ? '' : ',') . '
  4189.             ' . $productTagSelects . '
  4190.             ' . $productMetaSelects . '
  4191.             FROM products p
  4192.             LEFT JOIN product_types pt ON p.type_id = pt.id
  4193.             LEFT JOIN product_model pm ON p.product_model_id = pm.id
  4194.             LEFT JOIN product_types pmpt ON pmpt.id = pm.type_id
  4195.             LEFT JOIN vendors pmv ON pmv.id = pm.vendor_id
  4196.             LEFT JOIN vendors v ON v.id = p.vendor_id
  4197.             ' . $productMetaLeftJoins . '
  4198.             LEFT JOIN albums a ON a.id = p.photo_album_id
  4199.             LEFT JOIN users updatedBy ON updatedBy.id = p.updated_by
  4200.             WHERE ' . $where . '
  4201.             ORDER BY ' . $listOrderBy[$order_by] . ' ' . ($order_type) . (
  4202.                 ' LIMIT ' . $limit . ' OFFSET ' . $offset
  4203.             )
  4204.         );
  4205.         foreach ($whereParams as $key => $value) {
  4206.             $stmt->bindValue($key, $value);
  4207.         }
  4208.         $products = $stmt->executeQuery()->fetchAllAssociative();
  4209.         $productIds = [];
  4210.         foreach ($products as $product) {
  4211.             $productIds[] = $product['product_id'];
  4212.         }
  4213.         if ($search_string == 'error' && count($products) > 0) {
  4214.             $availableAndInStockCounts = $this->getAvailableAndInStockCounts($productIds);
  4215.             $productToShow = [];
  4216.             /**
  4217.              * @var Product $product
  4218.              */
  4219.             foreach ($products as $product) {
  4220.                 if (
  4221.                     array_key_exists($product['product_id'], $availableAndInStockCounts) &&
  4222.                     ($availableAndInStockCounts[$product['product_id']]['available'] != $product['available'] ||
  4223.                         $availableAndInStockCounts[$product['product_id']]['in_stock'] != $product['in_stock'])
  4224.                 ) {
  4225.                     $productToShow[] = $product;
  4226.                 }
  4227.             }
  4228.             $products = $productToShow;
  4229.         }
  4230.         $counts = count($products) > 0 ? $this->getAvailableAndInStockCounts($productIds) : [];
  4231.         // Repair products with wrong in_stock or available field values
  4232.         if (!empty($counts)) {
  4233.             foreach ($products as $key => $product) {
  4234.                 if (isset($counts[$product['product_id']])) {
  4235.                     if ($counts[$product['product_id']]['in_stock'] != $product['in_stock'] || $counts[$product['product_id']]['available'] != $product['available']) {
  4236.                         $connection->executeQuery('UPDATE products SET in_stock = ' . $counts[$product['product_id']]['in_stock'] . ', available = ' . $counts[$product['product_id']]['available'] . ' WHERE id = ' . $product['product_id']);
  4237.                         $products[$key]['in_stock'] = $counts[$product['product_id']]['in_stock'];
  4238.                         $products[$key]['available'] = $counts[$product['product_id']]['available'];
  4239.                     }
  4240.                 }
  4241.             }
  4242.         }
  4243.         // Product Repository
  4244.         $productRepository = $this->getEntityManager()->getRepository(Product::class);
  4245.         // Mask Stock Positions
  4246.         foreach ($products as $key => $product) {
  4247.             $products[$key]['stock_position'] = $productRepository->getNamedStockPositions($product['stock_position']);
  4248.         }
  4249.         if ($listOrderBy[$order_by] == 'stock_position') {
  4250.             if ($order_type == 'DESC') {
  4251.                 usort($products, function ($a, $b) {
  4252.                     return strcmp($b['stock_position'], $a['stock_position']);
  4253.                 });
  4254.             } else {
  4255.                 usort($products, function ($a, $b) {
  4256.                     return strcmp($a['stock_position'], $b['stock_position']);
  4257.                 });
  4258.             }
  4259.         }
  4260.         // Product Tags
  4261.         $productTags = [];
  4262.         $productTagsResults = $this->getEntityManager()->getRepository(ProductTag::class)->findAll();
  4263.         foreach ($productTagsResults as $productTag) {
  4264.             $productTags[$productTag->getId()] = [
  4265.                 'title' => $productTag->getTitle(),
  4266.                 'color' => $productTag->getColor(),
  4267.             ];
  4268.         }
  4269.         return [
  4270.             'products' => $products,
  4271.             'count' => $counts,
  4272.             'pagination' => [
  4273.                 'page' => $page,
  4274.                 'limit' => $limit,
  4275.                 'total' => $total,
  4276.             ],
  4277.             'productTags' => $productTags,
  4278.         ];
  4279.     }
  4280.     /**
  4281.      * @param int $listParam
  4282.      * @param int $domainId
  4283.      * @return string
  4284.      */
  4285.     private function getWhereFilter(int $listParam, int $domainId): string
  4286.     {
  4287.         // out_of_stock_status ?
  4288.         $pre = 'pm' . $domainId;
  4289.         if ($listParam == 1) { // On web
  4290.             return " AND p.available > 0  AND {$pre}.active = 1 AND {$pre}.listed = 1 AND {$pre}.orderable = 1 AND {$pre}.price != 0";
  4291.         }
  4292.         if ($listParam == 2) { // From link
  4293.             return " AND p.available > 0 AND {$pre}.active = 0 AND {$pre}.listed = 1 AND {$pre}.orderable = 1 AND {$pre}.price != 0";
  4294.         }
  4295.         if ($listParam == 3) { // None
  4296.             return " AND (p.available <= 0 OR {$pre}.active = 0 OR {$pre}.listed = 0 OR {$pre}.orderable = 0 OR {$pre}.price = 0)";
  4297.         }
  4298.         return '';
  4299.     }
  4300.     /**
  4301.      * @param $products
  4302.      * @param $fistRow
  4303.      * @return string
  4304.      */
  4305.     public function getCSVListOfProducts($products, $fistRow = true): string
  4306.     {
  4307.         $entityManager = $this->getEntityManager();
  4308.         $domains = $entityManager->createQuery('SELECT d
  4309.                 FROM AtechNetCoreBundle:Domain d
  4310.                 WHERE d.id IN (' . implode(',', Domain::DOMAINS_WITH_WEBSITE) . ')')
  4311.             ->getResult();
  4312.         $firsRow = [
  4313.             'Code',
  4314.             'Container?',
  4315.             'Type',
  4316.             'Brand',
  4317.             'Name',
  4318.             'Processor',
  4319.             'Display size',
  4320.             'Display resolution',
  4321.             'RAM',
  4322.             'HDD',
  4323.             'SSD',
  4324.             'Video card',
  4325.             'Chassis',
  4326.             'OS',
  4327.             'Keyboard language',
  4328.             'Touchscreen',
  4329.             'Webcam',
  4330.             'Quality',
  4331.             'Available',
  4332.             'Stock position',
  4333.             'Last buying price (EUR) no VAT',
  4334.             'Average buying price (EUR) no VAT',
  4335.             'WS SK (EUR) with VAT',
  4336.             'WS SK DISC (EUR) with VAT',
  4337.             'WS SK fLash sale START',
  4338.             'WS SK fLash sale END',
  4339.             'WS HU (HUF) with VAT',
  4340.             'WS HU DISC (HUF) with VAT',
  4341.             'WS HU fLash sale START',
  4342.             'WS HU fLash sale END',
  4343.             'WS CZ (CZK) with VAT',
  4344.             'WS CZ DISC (CZK) with VAT',
  4345.             'WS CZ fLash sale START',
  4346.             'WS CZ fLash sale END',
  4347.             'WS EU (EUR) without VAT',
  4348.             'WS EU DISC (EUR) without VAT',
  4349.             'WS EU fLash sale START',
  4350.             'WS EU fLash sale END',
  4351.             'RT SK (EUR) with VAT',
  4352.             'RT SK DISC (EUR) with VAT',
  4353.             'RT SK fLash sale START',
  4354.             'RT SK fLash sale END',
  4355.             'RT HU (HUF) with VAT',
  4356.             'RT HU DISC (HUF) with VAT',
  4357.             'RT HU fLash sale START',
  4358.             'RT HU fLash sale END',
  4359.             'RT CZ (CZK) with VAT',
  4360.             'RT CZ DISC (CZK) with VAT',
  4361.             'RT CZ fLash sale START',
  4362.             'RT CZ fLash sale END',
  4363.             'RT EU (EUR) with VAT',
  4364.             'RT EU DISC (EUR) with VAT',
  4365.             'RT EU fLash sale START',
  4366.             'RT EU fLash sale END',
  4367.             'Keyboard language',
  4368.             'WS SK DISCOUNTED',
  4369.             'WS HU DISCOUNTED',
  4370.             'WS CZ DISCOUNTED',
  4371.             'WS EU DISCOUNTED',
  4372.             'RT SK DISCOUNTED',
  4373.             'RT HU DISCOUNTED',
  4374.             'RT CZ DISCOUNTED',
  4375.             'RT EU DISCOUNTED',
  4376.             'WS SK ONLINE',
  4377.             'WS HU ONLINE',
  4378.             'WS CZ ONLINE',
  4379.             'WS EU ONLINE',
  4380.             'RT SK ONLINE',
  4381.             'RT HU ONLINE',
  4382.             'RT CZ ONLINE',
  4383.             'RT EU ONLINE',
  4384.             'LAST SALE',
  4385.             'BADGES',
  4386.             'PRODUCT TAGS',
  4387.             'Product in stock',
  4388.             'Container-child? & NOT-ONLINE',
  4389.         ];
  4390.         $lines = [];
  4391.         if ($fistRow) {
  4392.             $lines[] = implode("\t", $firsRow);
  4393.         }
  4394.         /**
  4395.          * @var Product $product
  4396.          * @var ProductAttribute $prodAttr
  4397.          */
  4398.         if (count($products) > 0) {
  4399.             $productRepo = $entityManager->getRepository(Product::class);
  4400.             $productList = is_array($products) ? array_values($products) : $products->toArray();
  4401.             // Pre-warm lazy collections — eliminates N+1 per product
  4402.             $entityManager->createQuery(
  4403.                 'SELECT p, pa, o, a FROM AtechNetCoreBundle:Product p
  4404.                  LEFT JOIN p.productAttributes pa LEFT JOIN pa.option o LEFT JOIN o.attribute a
  4405.                  WHERE p IN (:products)'
  4406.             )->setParameter('products', $productList)->getResult();
  4407.             $entityManager->createQuery(
  4408.                 'SELECT p, pm, d FROM AtechNetCoreBundle:Product p
  4409.                  LEFT JOIN p.metas pm LEFT JOIN pm.domain d
  4410.                  WHERE p IN (:products)'
  4411.             )->setParameter('products', $productList)->getResult();
  4412.             $entityManager->createQuery(
  4413.                 'SELECT p, pt FROM AtechNetCoreBundle:Product p
  4414.                  LEFT JOIN p.tags pt WHERE p IN (:products)'
  4415.             )->setParameter('products', $productList)->getResult();
  4416.             // Batch container-child check — replaces findBy() per product
  4417.             $productIds = array_map(fn ($p) => $p->getId(), $productList);
  4418.             $containerChildSet = array_flip(array_column(
  4419.                 $entityManager->createQuery(
  4420.                     'SELECT IDENTITY(pc.child) AS childId FROM AtechNetCoreBundle:ProductContainer pc
  4421.                      WHERE pc.child IN (:ids)'
  4422.                 )->setParameter('ids', $productIds)->getScalarResult(),
  4423.                 'childId'
  4424.             ));
  4425.             // Batch keyboard language for type-52 products — replaces per-product DQL query
  4426.             $keyboardLangMap = [];
  4427.             $type52Ids = array_values(array_map(
  4428.                 fn ($p) => $p->getId(),
  4429.                 array_filter($productList, fn ($p) => $p->getType()->getId() === 52)
  4430.             ));
  4431.             if ($type52Ids) {
  4432.                 foreach ($entityManager->createQuery(
  4433.                     'SELECT IDENTITY(pa.product) AS pid, o.title AS title
  4434.                      FROM AtechNetCoreBundle:ProductAttribute pa
  4435.                      LEFT JOIN pa.option o LEFT JOIN o.attribute a
  4436.                      WHERE a.id = :attr AND pa.product IN (:ids)'
  4437.                 )->setParameters(['attr' => 94, 'ids' => $type52Ids])->getScalarResult() as $row) {
  4438.                     $keyboardLangMap[(int) $row['pid']] = $row['title'];
  4439.                 }
  4440.             }
  4441.             foreach ($products as $product) {
  4442.                 $specAttributes = $product->getSpecificationProductAttributeOptions();
  4443.                 $processor = $ram = $hdd = $ssd = $videoCard = $chassis = $os = $keyboardLang = $displaySize = $touchscreen = $webcam = $displayResolution = '';
  4444.                 foreach ($specAttributes as $prodAttr) {
  4445.                     switch ($prodAttr->getOption()->getAttribute()->getId()) {
  4446.                         case 1:
  4447.                             $chassis = $prodAttr->getOption()->getTitle();
  4448.                             break;
  4449.                         case 3:
  4450.                         case 4:
  4451.                             $ram = $prodAttr->getOption()->getTitle();
  4452.                             break;
  4453.                         case 6:
  4454.                         case 7:
  4455.                         case 8:
  4456.                         case 9:
  4457.                             $hdd = $prodAttr->getOption()->getFullTitle();
  4458.                             break;
  4459.                         case 5:
  4460.                             $ssd = $prodAttr->getOption()->getFullTitle();
  4461.                             break;
  4462.                         case 12:
  4463.                             $displaySize = $prodAttr->getOption()->getTitle();
  4464.                             break;
  4465.                         case 139:
  4466.                             $touchscreen = $prodAttr->getOption()->getTitle();
  4467.                             break;
  4468.                         case 19:
  4469.                             $webcam = $prodAttr->getOption()->getTitle();
  4470.                             break;
  4471.                         case 25:
  4472.                             $videoCard .= ($videoCard != '' ? ', ' : '') . $prodAttr->getOption()->getTitle();
  4473.                             break;
  4474.                         case 32:
  4475.                         case 33:
  4476.                             $processor = $prodAttr->getOption()->getTitle();
  4477.                             break;
  4478.                         case 41:
  4479.                             $os = $prodAttr->getOption()->getTitle();
  4480.                             break;
  4481.                         case 63:
  4482.                             $displayResolution = $prodAttr->getOption()->getTitle();
  4483.                             break;
  4484.                         case 94:
  4485.                             $keyboardLang = $prodAttr->getoption()->getTitle();
  4486.                             break;
  4487.                     }
  4488.                 }
  4489.                 $WSSKMeta = $product->getDomainMeta($domains[1]);
  4490.                 $WSHUMeta = $product->getDomainMeta($domains[3]);
  4491.                 $WSCZMeta = $product->getDomainMeta($domains[4]);
  4492.                 $WSEUMeta = $product->getDomainMeta($domains[5]);
  4493.                 $RTSKMeta = $product->getDomainMeta($domains[0]);
  4494.                 $RTHUMeta = $product->getDomainMeta($domains[2]);
  4495.                 $RTCZMeta = $product->getDomainMeta($domains[6]);
  4496.                 $RTEUMeta = $product->getDomainMeta($domains[7]);
  4497.                 $productTags = '';
  4498.                 foreach ($product->getTags() as $productTag) {
  4499.                     if ($productTags != '') {
  4500.                         $productTags .= ',';
  4501.                     }
  4502.                     $productTags .= $productTag->getTitle();
  4503.                 }
  4504.                 $productInStock = $product->getInStock();
  4505.                 $isContainerChild = isset($containerChildSet[$product->getId()]);
  4506.                 $isContainerChildAndNotAvailableOnWeb = $isContainerChild && (
  4507.                     ($WSSKMeta->isActive() && $WSSKMeta->isListed() && $WSSKMeta->isOrderable()) ||
  4508.                     ($WSHUMeta->isActive() && $WSHUMeta->isListed() && $WSHUMeta->isOrderable()) ||
  4509.                     ($WSCZMeta->isActive() && $WSCZMeta->isListed() && $WSCZMeta->isOrderable()) ||
  4510.                     ($WSEUMeta->isActive() && $WSEUMeta->isListed() && $WSEUMeta->isOrderable()) ||
  4511.                     ($RTSKMeta->isActive() && $RTSKMeta->isListed() && $RTSKMeta->isOrderable()) ||
  4512.                     ($RTHUMeta->isActive() && $RTHUMeta->isListed() && $RTHUMeta->isOrderable()) ||
  4513.                     ($RTCZMeta->isActive() && $RTCZMeta->isListed() && $RTCZMeta->isOrderable()) ||
  4514.                     ($RTEUMeta->isActive() && $RTEUMeta->isListed() && $RTEUMeta->isOrderable())
  4515.                 ) ? '1' : '0';
  4516.                 $row = [
  4517.                     $product->getCode(),
  4518.                     $product->isContainer() ? '1' : '0',
  4519.                     $product->getType()->getTitle(),
  4520.                     $product->getVendor()->getName(),
  4521.                     $product->getName(),
  4522.                     $processor,
  4523.                     $displaySize,
  4524.                     $displayResolution,
  4525.                     $ram,
  4526.                     $hdd,
  4527.                     $ssd,
  4528.                     $videoCard,
  4529.                     $chassis,
  4530.                     $os,
  4531.                     $keyboardLang,
  4532.                     $touchscreen,
  4533.                     $webcam,
  4534.                     $product->getQualityStatusTitle($product->getQualityStatus()),
  4535.                     $product->getAvailable(),
  4536.                     $productRepo->getNamedStockPositions($product->getStockPosition()),
  4537.                     str_replace('.', ',', $product->getLastPrice()),
  4538.                     str_replace('.', ',', $product->getAvgPrice()),
  4539.                     str_replace('.', ',', $WSSKMeta->getDomainVatPrice()),
  4540.                     str_replace('.', ',', $WSSKMeta->getDomainVatDiscount()),
  4541.                     $WSSKMeta->getSaleStart() ? $WSSKMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4542.                     $WSSKMeta->getSaleEnd() ? $WSSKMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4543.                     str_replace('.', ',', $WSHUMeta->getDomainVatPrice()),
  4544.                     str_replace('.', ',', $WSHUMeta->getDomainVatDiscount()),
  4545.                     $WSHUMeta->getSaleStart() ? $WSHUMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4546.                     $WSHUMeta->getSaleEnd() ? $WSHUMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4547.                     str_replace('.', ',', $WSCZMeta->getDomainVatPrice()),
  4548.                     str_replace('.', ',', $WSCZMeta->getDomainVatDiscount()),
  4549.                     $WSCZMeta->getSaleStart() ? $WSCZMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4550.                     $WSCZMeta->getSaleEnd() ? $WSCZMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4551.                     str_replace('.', ',', $WSEUMeta->getDomainPrice()),
  4552.                     str_replace('.', ',', $WSEUMeta->getDomainDiscount()),
  4553.                     $WSEUMeta->getSaleStart() ? $WSEUMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4554.                     $WSEUMeta->getSaleEnd() ? $WSEUMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4555.                     str_replace('.', ',', $RTSKMeta->getDomainVatPrice()),
  4556.                     str_replace('.', ',', $RTSKMeta->getDomainVatDiscount()),
  4557.                     $RTSKMeta->getSaleStart() ? $RTSKMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4558.                     $RTSKMeta->getSaleEnd() ? $RTSKMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4559.                     str_replace('.', ',', $RTHUMeta->getDomainVatPrice()),
  4560.                     str_replace('.', ',', $RTHUMeta->getDomainVatDiscount()),
  4561.                     $RTHUMeta->getSaleStart() ? $RTHUMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4562.                     $RTHUMeta->getSaleEnd() ? $RTHUMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4563.                     str_replace('.', ',', $RTCZMeta->getDomainVatPrice()),
  4564.                     str_replace('.', ',', $RTCZMeta->getDomainVatDiscount()),
  4565.                     $RTCZMeta->getSaleStart() ? $RTCZMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4566.                     $RTCZMeta->getSaleEnd() ? $RTCZMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4567.                     str_replace('.', ',', $RTEUMeta->getDomainVatPrice()),
  4568.                     str_replace('.', ',', $RTEUMeta->getDomainVatDiscount()),
  4569.                     $RTEUMeta->getSaleStart() ? $RTEUMeta->getSaleStart()->format('Y.m.d, H:i') : '',
  4570.                     $RTEUMeta->getSaleEnd() ? $RTEUMeta->getSaleEnd()->format('Y.m.d, H:i') : '',
  4571.                     $product->getType()->getId() !== 52 ? '-' : ($keyboardLangMap[$product->getId()] ?? $productRepo->getProductKeyboardLanguageSetting($product)),
  4572.                     $WSSKMeta->isDiscounted() ? '1' : '0',
  4573.                     $WSHUMeta->isDiscounted() ? '1' : '0',
  4574.                     $WSCZMeta->isDiscounted() ? '1' : '0',
  4575.                     $WSEUMeta->isDiscounted() ? '1' : '0',
  4576.                     $RTSKMeta->isDiscounted() ? '1' : '0',
  4577.                     $RTHUMeta->isDiscounted() ? '1' : '0',
  4578.                     $RTCZMeta->isDiscounted() ? '1' : '0',
  4579.                     $RTEUMeta->isDiscounted() ? '1' : '0',
  4580.                     $product->getAvailable() > 0 && $WSSKMeta->isActive() && $WSSKMeta->isListed() && $WSSKMeta->isOrderable() ? '1' : '0',
  4581.                     $product->getAvailable() > 0 && $WSHUMeta->isActive() && $WSHUMeta->isListed() && $WSHUMeta->isOrderable() ? '1' : '0',
  4582.                     $product->getAvailable() > 0 && $WSCZMeta->isActive() && $WSCZMeta->isListed() && $WSCZMeta->isOrderable() ? '1' : '0',
  4583.                     $product->getAvailable() > 0 && $WSEUMeta->isActive() && $WSEUMeta->isListed() && $WSEUMeta->isOrderable() ? '1' : '0',
  4584.                     $product->getAvailable() > 0 && $RTSKMeta->isActive() && $RTSKMeta->isListed() && $RTSKMeta->isOrderable() ? '1' : '0',
  4585.                     $product->getAvailable() > 0 && $RTHUMeta->isActive() && $RTHUMeta->isListed() && $RTHUMeta->isOrderable() ? '1' : '0',
  4586.                     $product->getAvailable() > 0 && $RTCZMeta->isActive() && $RTCZMeta->isListed() && $RTCZMeta->isOrderable() ? '1' : '0',
  4587.                     $product->getAvailable() > 0 && $RTEUMeta->isActive() && $RTEUMeta->isListed() && $RTEUMeta->isOrderable() ? '1' : '0',
  4588.                     !is_null($product->getLastSale()) ? $product->getLastSale()->format('Y.m.d, H:i') : '-',
  4589.                     implode(',', $product->getBadges()),
  4590.                     $productTags,
  4591.                     $productInStock,
  4592.                     $isContainerChildAndNotAvailableOnWeb,
  4593.                 ];
  4594.                 $lines[] = implode("\t", $row);
  4595.             }
  4596.         }
  4597.         return implode("\n", $lines);
  4598.     }
  4599.     /**
  4600.      * @param array $items
  4601.      * @param bool $printFirstRow
  4602.      * @return string
  4603.      */
  4604.     public function getCSVListOfStockItems(array $items, bool $printFirstRow): string
  4605.     {
  4606.         $firsRow = [
  4607.             'First in',
  4608.             'Shipment',
  4609.             'Code',
  4610.             'Serial number',
  4611.             'Product type',
  4612.             'Brand',
  4613.             'Name',
  4614.             'Available quantity',
  4615.             'In stock quantity',
  4616.             'Stock position',
  4617.             'Last buy no VAT (EUR)',
  4618.             'Average buy no VAT (EUR)',
  4619.         ];
  4620.         $csv = [];
  4621.         if ($printFirstRow) {
  4622.             $csv[] = $firsRow;
  4623.         }
  4624.         /**
  4625.          * @var Product $product
  4626.          * @var ProductAttribute $prodAttr
  4627.          */
  4628.         if (count($items) > 0) {
  4629.             /** @var StockItem $item */
  4630.             foreach ($items as $item) {
  4631.                 $product = $item->getProduct();
  4632.                 $row = [
  4633.                     $item->getFirstMovement()->getCreated()->format('Y-m-d'),
  4634.                     $item->getFirstMovement()?->getShipment()?->getShipmentId() ?? '-',
  4635.                     $product->getCode(),
  4636.                     $item->getSerialNumber(),
  4637.                     $product->getType()->getTitle(),
  4638.                     $product->getVendor()->getName(),
  4639.                     $product->getName(),
  4640.                     $product->getAvailable(),
  4641.                     $product->getInStock(),
  4642.                     $item->getLastKnownStockPosition()?->getName() ?? 'unknown',
  4643.                     $item->getPurchasePriceCalculated(),
  4644.                     $product->getAvgPrice(),
  4645.                 ];
  4646.                 $csv[] = $row;
  4647.             }
  4648.         }
  4649.         $output = '';
  4650.         $fp = fopen('php://temp', 'r+');
  4651.         foreach ($csv as $row) {
  4652.             fputcsv($fp, $row, "\t", '"', "\\");
  4653.         }
  4654.         rewind($fp);
  4655.         $output = stream_get_contents($fp);
  4656.         fclose($fp);
  4657.         return $output;
  4658.     }
  4659.     /**
  4660.      * @param string $productCodeList
  4661.      * @return string
  4662.      * @throws Exception
  4663.      */
  4664.     public function getCSVListOfAvailableStockItems(string $productCodeList): string
  4665.     {
  4666.         $entityManager = $this->getEntityManager();
  4667.         $connection = $entityManager->getConnection();
  4668.         $query = "SELECT
  4669.                 si.aiken_code,
  4670.                 p.code AS product_code,
  4671.                 p.cache_description AS config,
  4672.                 si.purchase_price,
  4673.                 sp.name AS stock_position,
  4674.                 si.note,
  4675.                 si.error_codes,
  4676.                 si.available AS is_available
  4677.             FROM stock_items si
  4678.             LEFT JOIN stock_movement_item smiOldest ON smiOldest.id = (
  4679.                 SELECT smi2.stockmovementitem_id
  4680.                 FROM stock_movement_items_stock_items smi2
  4681.                 LEFT JOIN stock_movement_item smiC ON smi2.stockmovementitem_id = smiC.id
  4682.                 WHERE smi2.stockitem_id = si.id
  4683.                 ORDER BY smiC.created DESC
  4684.                 LIMIT 1
  4685.             )
  4686.             LEFT JOIN stock_movement_item smiIn ON smiIn.id = (
  4687.                 SELECT smi3.stockmovementitem_id
  4688.                 FROM stock_movement_items_stock_items smi3
  4689.                 LEFT JOIN stock_movement_item smiB ON smi3.stockmovementitem_id = smiB.id
  4690.                 WHERE smi3.stockitem_id = si.id
  4691.                 ORDER BY smiB.created ASC
  4692.                 LIMIT 1
  4693.             )
  4694.             LEFT JOIN stock_movements smOldest ON smOldest.id = smiOldest.stock_movement_id
  4695.             LEFT JOIN stock_movements smIn ON smIn.id = smiIn.stock_movement_id
  4696.             INNER JOIN products p ON p.id = smiOldest.product_id AND p.id IN($productCodeList)
  4697.             LEFT JOIN stock_positions AS sp ON ( sp.id = si.last_stock_position_id )
  4698.             WHERE si.in_stock = '1'
  4699.                 AND smOldest.type != 2
  4700.         ";
  4701.         $stockItemList = $connection->executeQuery($query);
  4702.         $stockItemList = $stockItemList->fetchAllAssociative();
  4703.         $firsRow = [
  4704.             'aiken_code',
  4705.             'product_code',
  4706.             'config',
  4707.             'purchase_price',
  4708.             'stock_position',
  4709.             'is_available',
  4710.             'note',
  4711.             'errors',
  4712.         ];
  4713.         $csv[] = $firsRow;
  4714.         $allErrorCode = $entityManager->getRepository(ErrorCode::class)->findAll();
  4715.         $errorList = [];
  4716.         /**
  4717.          * @var ErrorCode $errorCode
  4718.          */
  4719.         foreach ($allErrorCode as $errorCode) {
  4720.             $errorList[$errorCode->getCode()] = $errorCode->getHuText();
  4721.         }
  4722.         foreach ($stockItemList as $stockItemData) {
  4723.             $stockItemErrorCodeList = explode(' ', $stockItemData['error_codes']);
  4724.             $stockItemErrorListHu = [];
  4725.             foreach ($stockItemErrorCodeList as $errorCode) {
  4726.                 if ($errorCode != '' && !empty($errorList[$errorCode])) {
  4727.                     $stockItemErrorListHu[] = $errorList[$errorCode];
  4728.                 }
  4729.             }
  4730.             $csv[] = [
  4731.                 $stockItemData['aiken_code'],
  4732.                 $stockItemData['product_code'],
  4733.                 $stockItemData['config'],
  4734.                 $stockItemData['purchase_price'],
  4735.                 $stockItemData['stock_position'],
  4736.                 $stockItemData['is_available'] == 1 ? 'true' : 'false',
  4737.                 preg_replace('/\s+/', ' ', $stockItemData['note']),
  4738.                 implode(' | ', $stockItemErrorListHu),
  4739.             ];
  4740.         }
  4741.         $output = '';
  4742.         foreach ($csv as $row) {
  4743.             $output .= implode(';', $row) . "\n";
  4744.         }
  4745.         return $output;
  4746.     }
  4747.     /**
  4748.      * @param ProductMeta $productMeta
  4749.      * @return void
  4750.      * @throws NotSupported
  4751.      * @throws OptimisticLockException
  4752.      */
  4753.     public function updateAllContainerChildPrice(ProductMeta $productMeta): void
  4754.     {
  4755.         $entityManager = $this->getEntityManager();
  4756.         $productContainers = $entityManager->getRepository(ProductContainer::class)->findBy([
  4757.             'child' => $productMeta->getProduct(),
  4758.             'allowPriceSyncFromChildProduct' => true,
  4759.         ]);
  4760.         $domain = $productMeta->getDomain();
  4761.         foreach ($productContainers as $container) {
  4762.             $containerItemMetaToUpdate = $entityManager->getRepository(ProductContainerMeta::class)->findOneBy([
  4763.                 'productContainer' => $container,
  4764.                 'domain' => $domain,
  4765.             ]);
  4766.             $containerItemMetaToUpdate->setPrice($productMeta->getPrice());
  4767.             $containerItemMetaToUpdate->setDiscount($productMeta->getDiscount());
  4768.             $entityManager->persist($containerItemMetaToUpdate);
  4769.             $entityManager->flush($containerItemMetaToUpdate);
  4770.             $this->syncContainerPricesByDomain($container, $domain);
  4771.         }
  4772.     }
  4773.     /**
  4774.      * @param ProductContainer $productContainer
  4775.      * @param Domain $domain
  4776.      * @param bool $skipFlush
  4777.      * @return void
  4778.      * @throws Exception
  4779.      */
  4780.     public function syncContainerPricesByDomain(ProductContainer $productContainer, Domain $domain, bool $skipFlush = false): void
  4781.     {
  4782.         $conn      = $this->getEntityManager()->getConnection();
  4783.         $productId = $productContainer->getProduct()->getId();
  4784.         $domainId  = $domain->getId();
  4785.         $sums = $conn->executeQuery(
  4786.             'SELECT COALESCE(SUM(pcm.price), 0) AS total_price, COALESCE(SUM(pcm.discount), 0) AS total_discount
  4787.              FROM product_container pc
  4788.              INNER JOIN product_container_metas pcm ON pcm.product_container_id = pc.id
  4789.              WHERE pc.product_id = :productId AND pcm.domain_id = :domainId',
  4790.             ['productId' => $productId, 'domainId' => $domainId]
  4791.         )->fetchAssociative();
  4792.         $conn->executeStatement(
  4793.             'UPDATE products__metas SET price = :price, discount = :discount
  4794.              WHERE product_id = :productId AND domain_id = :domainId',
  4795.             [
  4796.                 'price'     => $sums['total_price'],
  4797.                 'discount'  => $sums['total_discount'],
  4798.                 'productId' => $productId,
  4799.                 'domainId'  => $domainId,
  4800.             ]
  4801.         );
  4802.         if (!$skipFlush) {
  4803.             $this->getEntityManager()->flush();
  4804.         }
  4805.     }
  4806.     /**
  4807.      * @param Basket $basket
  4808.      * @return bool
  4809.      */
  4810.     public function isBasketIncludesGamerProduct(Basket $basket): bool
  4811.     {
  4812.         /**
  4813.          * @var BasketItem $bi
  4814.          */
  4815.         foreach ($basket->getBasketItems() as $bi) {
  4816.             if ($bi->getProduct()->hasBadgeItem('gamer')) {
  4817.                 return true;
  4818.             }
  4819.         }
  4820.         return false;
  4821.     }
  4822.     /**
  4823.      * @param int $osAttributeOptionId
  4824.      * @param int $attributeId
  4825.      * @return void
  4826.      * @var Product $product
  4827.      */
  4828.     public function checkAndUpdateOSAttribute(Product $product, int $osAttributeOptionId, int $attributeId): void
  4829.     {
  4830.         if ($osAttributeOptionId == AttributeOption::ATTRIBUTE_OPTION_ID_WINDOWS_11) {
  4831.             if (!$product->isWindows11Compatible()) {
  4832.                 return;
  4833.             }
  4834.         }
  4835.         if (str_contains(strtolower($product->getFullTitle()), 'chromebook')) {
  4836.             return;
  4837.         }
  4838.         if ($osAttributeOptionId == AttributeOption::ATTRIBUTE_OPTION_ID_MAC_OS) {
  4839.             if ($product->getVendor()->getId() != Vendor::VENDOR_ID_APPLE) {
  4840.                 return;
  4841.             }
  4842.             if (!in_array($product->getType()->getId(), [ProductType::PRODUCT_TYPE_ID_PC, ProductType::PRODUCT_TYPE_ID_NOTEBOOK])) {
  4843.                 return;
  4844.             }
  4845.         }
  4846.         $productAttributeOperatingSystem = $this->getEntityManager()
  4847.             ->createQuery(
  4848.                 'SELECT pa
  4849.                     FROM AtechNetCoreBundle:ProductAttribute pa
  4850.                     INNER JOIN pa.option o WITH IDENTITY(o.attribute) = ' . $attributeId . '
  4851.                     WHERE IDENTITY(pa.product) = ' . $product->getId()
  4852.             )
  4853.             ->getResult();
  4854.         $selectedOperationSystem = count($productAttributeOperatingSystem) > 0 ? $productAttributeOperatingSystem[0]->getOption() : false;
  4855.         if ($selectedOperationSystem) {
  4856.             if ($selectedOperationSystem->getId() != $osAttributeOptionId) {
  4857.                 $em = $this->getEntityManager();
  4858.                 $em->remove($productAttributeOperatingSystem[0]);
  4859.                 $em->flush();
  4860.                 $this->addProductAttribute($product, $osAttributeOptionId);
  4861.             }
  4862.             return;
  4863.         }
  4864.         $this->addProductAttribute($product, $osAttributeOptionId);
  4865.     }
  4866.     /**
  4867.      * @param Product $product
  4868.      * @param int $attributeOptionId
  4869.      * @return void
  4870.      */
  4871.     private function addProductAttribute(Product $product, int $attributeOptionId): void
  4872.     {
  4873.         $entityManager = $this->getEntityManager();
  4874.         $newProductAttribute = new ProductAttribute();
  4875.         $attributeOption = $entityManager->getRepository(AttributeOption::class)->find($attributeOptionId);
  4876.         $newProductAttribute->setProduct($product);
  4877.         $newProductAttribute->setOption($attributeOption);
  4878.         $entityManager->persist($newProductAttribute);
  4879.         $entityManager->flush();
  4880.     }
  4881.     /**
  4882.      * @param int $type
  4883.      * @return null|array
  4884.      */
  4885.     public function getAllProductTagsByType(int $type): ?array
  4886.     {
  4887.         $productTags = $this->getEntityManager()->createQuery('SELECT pt
  4888.                 FROM AtechNetCoreBundle:ProductTag pt
  4889.                 WHERE pt.type = :type')
  4890.             ->setParameters([
  4891.                 'type' => $type,
  4892.             ])
  4893.             ->getResult();
  4894.         $formattedProductTagList = [];
  4895.         /**
  4896.          * @var ProductTag $productTags
  4897.          */
  4898.         foreach ($productTags as $productTag) {
  4899.             $formattedProductTagList[$productTag->getId()] = $productTag->getTitle();
  4900.         }
  4901.         return $formattedProductTagList;
  4902.     }
  4903.     /**
  4904.      * @param array $attributes
  4905.      * @return void
  4906.      */
  4907.     public function getCpuGrouppedProductUAttributeArray(array &$attributes): void
  4908.     {
  4909.         $oldAttributes = $attributes;
  4910.         if (!(array_key_exists(Attribute::ATTRIBUTE_ID_CPU_GENERATION, $attributes) || array_key_exists(Attribute::ATTRIBUTE_ID_CPU_PC_FAMILY, $attributes) || array_key_exists(Attribute::ATTRIBUTE_ID_CPU_NB_FAMILY, $attributes))) {
  4911.             return;
  4912.         }
  4913.         if (!(array_key_exists(Attribute::ATTRIBUTE_ID_CPU_PC, $attributes) || array_key_exists(Attribute::ATTRIBUTE_ID_CPU_NB, $attributes))) {
  4914.             return;
  4915.         }
  4916.         $cpuAttribute = array_key_exists(Attribute::ATTRIBUTE_ID_CPU_PC, $attributes) ? $attributes[Attribute::ATTRIBUTE_ID_CPU_PC] : $attributes[Attribute::ATTRIBUTE_ID_CPU_NB];
  4917.         $cpuGenerationAttribute = array_key_exists(Attribute::ATTRIBUTE_ID_CPU_GENERATION, $attributes) ? $attributes[Attribute::ATTRIBUTE_ID_CPU_GENERATION] : null;
  4918.         $cpuFamilyAttribute = array_key_exists(Attribute::ATTRIBUTE_ID_CPU_PC_FAMILY, $attributes) ? $attributes[Attribute::ATTRIBUTE_ID_CPU_PC_FAMILY] : (array_key_exists(Attribute::ATTRIBUTE_ID_CPU_NB_FAMILY, $attributes) ? $attributes[Attribute::ATTRIBUTE_ID_CPU_NB_FAMILY] : null);
  4919.         unset($oldAttributes[Attribute::ATTRIBUTE_ID_CPU_PC]);
  4920.         unset($oldAttributes[Attribute::ATTRIBUTE_ID_CPU_NB]);
  4921.         unset($oldAttributes[Attribute::ATTRIBUTE_ID_CPU_PC_FAMILY]);
  4922.         unset($oldAttributes[Attribute::ATTRIBUTE_ID_CPU_NB_FAMILY]);
  4923.         unset($oldAttributes[Attribute::ATTRIBUTE_ID_CPU_GENERATION]);
  4924.         $attributes = array_merge([$cpuAttribute, $cpuFamilyAttribute, $cpuGenerationAttribute], $oldAttributes);
  4925.     }
  4926.     /**
  4927.      * @param Product $product
  4928.      * @return bool
  4929.      */
  4930.     public function isFreeEsetCompatible(Product $product): bool
  4931.     {
  4932.         if (!in_array($product->getType()->getId(), ProductType::getFreeEsetCompatibleList())) {
  4933.             return false;
  4934.         }
  4935.         if (in_array('ChromeBook', $product->getBadges()) || str_contains(strtolower($product->getFullTitle()), 'chromebook')) {
  4936.             return false;
  4937.         }
  4938.         if ($product->getVendor()->getName() == 'Apple') {
  4939.             return false;
  4940.         }
  4941.         /**
  4942.          * @var ProductAttribute $productAttribute
  4943.          */
  4944.         foreach ($product->getConfigurationAttributes() as $productAttribute) {
  4945.             if ($productAttribute->getOption()->getAttribute()->getId() == Attribute::ATTRIBUTE_ID_CPU_GENERATION) {
  4946.                 $selectedOption = $productAttribute->getOption();
  4947.                 $cpuGenerationNumber = (int)str_replace('. Generation', '', $selectedOption->getTitle());
  4948.                 return $cpuGenerationNumber >= 6;
  4949.             }
  4950.         }
  4951.         return false;
  4952.     }
  4953.     public function getAllStockPositionList()
  4954.     {
  4955.         $query = $this->getEntityManager()->createQuery('SELECT DISTINCT sp.name FROM AtechNetCoreBundle:StockItemPosition sip JOIN AtechNetCoreBundle:StockPosition sp WITH sp.id = sip.stockPosition');
  4956.         $positionListArrays = $query->getResult();
  4957.         $positionList = [];
  4958.         foreach ($positionListArrays as $arrayItem) {
  4959.             $positionList[] = $arrayItem['name'];
  4960.         }
  4961.         return $positionList;
  4962.     }
  4963.     /**
  4964.      * @param ProductMeta $productMeta
  4965.      * @param bool $isDiscounted
  4966.      * @return void
  4967.      */
  4968.     public function syncContainerChildMetaPrice(&$entityManager, ProductMeta $productMeta, float $price, bool $isDiscounted = false, bool $skipFlush = false): void
  4969.     {
  4970.         $query = $entityManager->createQuery(
  4971.             'SELECT pcMeta
  4972.                 FROM AtechNetCoreBundle:ProductContainerMeta pcMeta
  4973.                 INNER JOIN pcMeta.productContainer pc WITH pc.child = :metaProduct AND pc.allowPriceSyncFromChildProduct = 1
  4974.                 WHERE pcMeta.domain = :domain'
  4975.         )
  4976.             ->setParameters([
  4977.                 'metaProduct' => $productMeta->getProduct(),
  4978.                 'domain' => $productMeta->getDomain(),
  4979.             ]);
  4980.         $conn   = $entityManager->getConnection();
  4981.         $column = $isDiscounted ? 'discount' : 'price';
  4982.         /**
  4983.          * @var ProductContainerMeta $containerMeta
  4984.          */
  4985.         foreach ($query->getResult() as $containerMeta) {
  4986.             $conn->executeStatement(
  4987.                 "UPDATE product_container_metas SET {$column} = :price WHERE id = :id",
  4988.                 ['price' => $price, 'id' => $containerMeta->getId()]
  4989.             );
  4990.             $this->syncContainerPricesByDomain($containerMeta->getProductContainer(), $containerMeta->getDomain(), $skipFlush);
  4991.         }
  4992.     }
  4993.     /**
  4994.      * @param Product $product
  4995.      * @return void
  4996.      */
  4997.     public function checkAndUpdateRAMAttributes(Product $product): void
  4998.     {
  4999.         $ramProductAttributeIds = [
  5000.             Attribute::ATTRIBUTE_ID_RAM_PC,
  5001.             Attribute::ATTRIBUTE_ID_RAM_NB,
  5002.         ];
  5003.         /**
  5004.          * @var ProductAttribute $ramProductAttribute
  5005.          */
  5006.         $ramProductAttribute = $product->getProductAttributes()->filter(
  5007.             function (ProductAttribute $pa) use ($ramProductAttributeIds) {
  5008.                 return in_array($pa->getOption()->getAttribute()->getId(), $ramProductAttributeIds);
  5009.             }
  5010.         )->first();
  5011.         if (!$ramProductAttribute) {
  5012.             return;
  5013.         }
  5014.         $sizeMatches = $ddrMatches = [];
  5015.         preg_match('/([0-9]+?GB)/', $ramProductAttribute->getOption()->getTitle(), $sizeMatches);
  5016.         preg_match('/(DDR[0-9])/', $ramProductAttribute->getOption()->getTitle(), $ddrMatches);
  5017.         if (count($ddrMatches) < 1 && count($sizeMatches) < 1) {
  5018.             return;
  5019.         }
  5020.         $ramSize = (int)str_replace('GB', '', $sizeMatches[0]);
  5021.         $ddrInfo = $ddrMatches[0] ?? '';
  5022.         if ($ramSize < 2 || $ramProductAttribute->getMultiplier() > 0) {
  5023.             return;
  5024.         }
  5025.         switch ($ramProductAttribute->getOption()->getAttribute()->getId()) {
  5026.             case Attribute::ATTRIBUTE_ID_RAM_PC:
  5027.                 $ramTypeAttributeIdToAdd = Attribute::ATTRIBUTE_ID_RAM_PC_TYPE;
  5028.                 break;
  5029.             case Attribute::ATTRIBUTE_ID_RAM_NB:
  5030.                 $ramTypeAttributeIdToAdd = Attribute::ATTRIBUTE_ID_RAM_NB_TYPE;
  5031.                 break;
  5032.         }
  5033.         $ramData = [
  5034.             'size' => $ramSize,
  5035.             'type' => $ddrInfo,
  5036.             'ramTypeAttributeIdToAdd' => $ramTypeAttributeIdToAdd,
  5037.         ];
  5038.         $productAttributeOptionsToAdd = $this->prepareAttributeOptionsByRamData($ramData);
  5039.         $alreadyAddedSizeAndTypeRamAttribute = $this->getAddedSizeAndTypeRamAttribute($product);
  5040.         $entityManager = $this->getEntityManager();
  5041.         /**
  5042.          * @var AttributeOption $attributeOption
  5043.          */
  5044.         foreach ($productAttributeOptionsToAdd as $attributeId => $attributeOption) {
  5045.             if (count($alreadyAddedSizeAndTypeRamAttribute) < 1) {
  5046.                 $this->addProductAttribute($product, $attributeOption['option']->getId());
  5047.                 continue;
  5048.             }
  5049.             // if same attributes added
  5050.             if ($alreadyAddedSizeAndTypeRamAttribute[$attributeId] == $attributeOption['option']->getId()) {
  5051.                 continue;
  5052.             }
  5053.             // update ProductAttribute
  5054.             $productAttributeToUpdate = $entityManager->getRepository(ProductAttribute::class)->findOneBy([
  5055.                 'product' => $product,
  5056.                 'option' => $alreadyAddedSizeAndTypeRamAttribute[$attributeId],
  5057.             ]);
  5058.             if ($productAttributeToUpdate) {
  5059.                 $productAttributeToUpdate->setOption($attributeOption['option']);
  5060.                 $entityManager->persist($productAttributeToUpdate);
  5061.             }
  5062.         }
  5063.         $entityManager->flush();
  5064.     }
  5065.     /**
  5066.      * @param array $ramData
  5067.      * @return null|array
  5068.      */
  5069.     private function prepareAttributeOptionsByRamData(array $ramData): ?array
  5070.     {
  5071.         $entityManager = $this->getEntityManager();
  5072.         $attributeOptionsToReturn = [];
  5073.         $ramSizeAttributeOption = $entityManager->getRepository(AttributeOption::class)->findOneBy([
  5074.             'attribute' => Attribute::ATTRIBUTE_ID_RAM_SIZE,
  5075.             'title' => "{$ramData['size']}GB",
  5076.         ]);
  5077.         if ($ramSizeAttributeOption) {
  5078.             $attributeOptionsToReturn[Attribute::ATTRIBUTE_ID_RAM_SIZE] = [
  5079.                 'title' => $ramSizeAttributeOption->getTitle(),
  5080.                 'option' => $ramSizeAttributeOption,
  5081.             ];
  5082.         }
  5083.         $ramTypeAttributeOption = $entityManager->getRepository(AttributeOption::class)->findOneBy([
  5084.             'attribute' => $ramData['ramTypeAttributeIdToAdd'],
  5085.             'title' => $ramData['type'],
  5086.         ]);
  5087.         if ($ramTypeAttributeOption) {
  5088.             $attributeOptionsToReturn[$ramData['ramTypeAttributeIdToAdd']] = [
  5089.                 'title' => $ramTypeAttributeOption->getTitle(),
  5090.                 'option' => $ramTypeAttributeOption,
  5091.             ];
  5092.         }
  5093.         return $attributeOptionsToReturn;
  5094.     }
  5095.     /**
  5096.      * @param Product $product
  5097.      * @return array
  5098.      */
  5099.     private function getAddedSizeAndTypeRamAttribute(Product $product): array
  5100.     {
  5101.         $sizeAndTypeRamAttributeList = [
  5102.             Attribute::ATTRIBUTE_ID_RAM_SIZE,
  5103.             Attribute::ATTRIBUTE_ID_RAM_PC_TYPE,
  5104.             Attribute::ATTRIBUTE_ID_RAM_NB_TYPE,
  5105.         ];
  5106.         $addedRamAttributes = $product->getProductAttributes()->filter(
  5107.             function (ProductAttribute $pa) use ($sizeAndTypeRamAttributeList) {
  5108.                 return in_array($pa->getOption()->getAttribute()->getId(), $sizeAndTypeRamAttributeList);
  5109.             }
  5110.         );
  5111.         $ramAttributes = [];
  5112.         /**
  5113.          * @var ProductAttribute $productAttribute
  5114.          */
  5115.         foreach ($addedRamAttributes as $productAttribute) {
  5116.             $ramAttributes[$productAttribute->getOption()->getAttribute()->getId()] = $productAttribute->getOption()->getId();
  5117.         }
  5118.         return $ramAttributes;
  5119.     }
  5120.     /**
  5121.      * @return array
  5122.      */
  5123.     public function exportQuarantineSerialNumbers(): array
  5124.     {
  5125.         $entityManager = $this->getEntityManager();
  5126.         $output = [];
  5127.         $output[] = 'ID;Code;Type;Vendor;Product;Serial;Aiken;Stock Item Created;Movement created;MovementID;MovementComment';
  5128.         $sql = <<<SQL
  5129.             SELECT 
  5130.                 `p`.`id` AS `productId`, 
  5131.                 `p`.`code` AS `productCode`, 
  5132.                 `pt`.`title` AS `productType`, 
  5133.                 `v`.`name` AS `productVendor`, 
  5134.                 `p`.`name` AS `productName`, 
  5135.                 `sm`.`id` AS `movementId`, 
  5136.                 `sm`.`note` AS `movementNote`, 
  5137.                 `si`.`serial_number` AS `serialNumber`, 
  5138.                 `si`.`aiken_code` AS `aikenCode`,  
  5139.                 `si`.`created` AS `stockItemCreated`, 
  5140.                 `sm`.`created` AS `movementCreated`, 
  5141.                 `si`.`purchase_price` AS `purchasePrice` 
  5142.             FROM `stock_movements` AS `sm` 
  5143.             INNER JOIN `stock_movement_item` AS `smi` ON ( `smi`.`stock_movement_id` = `sm`.`id` ) 
  5144.             INNER JOIN `stock_movement_items_stock_items` AS `smisi` ON ( `smisi`.`stockmovementitem_id` = `smi`.`id` ) 
  5145.             INNER JOIN `stock_items` AS `si` ON ( `si`.`id` = `smisi`.`stockitem_id` )
  5146.             INNER JOIN `products` AS `p` ON ( `p`.`id` = `smi`.`product_id` ) 
  5147.             LEFT JOIN `product_types` AS `pt` ON ( `pt`.`id` = `p`.`type_id` ) 
  5148.             LEFT JOIN `vendors` AS `v` ON ( `v`.`id` = `p`.`vendor_id` ) 
  5149.             WHERE `sm`.`sub_type` = :subType 
  5150.             ORDER BY `sm`.`created` ASC 
  5151.         SQL;
  5152.         $quarantineMovementItems = $entityManager->getConnection()->executeQuery($sql, [
  5153.             'subType'   => StockMovement::SUB_TYPE_NEUTRAL_QUARANTINE 
  5154.         ]);
  5155.         while ( $item = $quarantineMovementItems->fetchAssociative() ) {
  5156.             $output[$item['serialNumber']] = implode(';', [
  5157.                 $item['productId'], 
  5158.                 $item['productCode'], 
  5159.                 $item['productType'], 
  5160.                 $item['productVendor'], 
  5161.                 $item['productName'], 
  5162.                 $item['serialNumber'], 
  5163.                 $item['aikenCode'], 
  5164.                 $item['stockItemCreated'], 
  5165.                 $item['movementCreated'], 
  5166.                 $item['movementId'], 
  5167.                 ( !is_null($item['movementNote']) ? trim(preg_replace('/\s+/', ' ', $item['movementNote'])) : '' ), 
  5168.                 $item['purchasePrice']
  5169.             ]);  
  5170.         }
  5171.         $params = [ 
  5172.             'name'          => '%QUARANTINE%', 
  5173.             'movementOut'   => StockMovement::TYPE_OUT 
  5174.         ];
  5175.         $types = [ 
  5176.             'name'          => ParameterType::STRING, 
  5177.             'movementOut'   => ParameterType::INTEGER
  5178.         ];
  5179.         $sql = <<<SQL
  5180.             SELECT 
  5181.                 `p`.`id` AS `productId`, 
  5182.                 `p`.`code` AS `productCode`, 
  5183.                 `pt`.`title` AS `productType`, 
  5184.                 `v`.`name` AS `productVendor`, 
  5185.                 `p`.`name` AS `productName`, 
  5186.                 `sm`.`created` AS `movementCreated`, 
  5187.                 `sm`.`id` AS `movementId`, 
  5188.                 `sm`.`note` AS `movementNote`, 
  5189.                 `sm`.`created` AS `movementCreated`, 
  5190.                 `si`.`serial_number` AS `serialNumber`, 
  5191.                 `si`.`aiken_code` AS `aikenCode`,  
  5192.                 `si`.`created` AS `stockItemCreated`, 
  5193.                 `si`.`purchase_price` AS `purchasePrice`  
  5194.             FROM `stock_items` AS `si` 
  5195.             INNER JOIN `stock_movement_items_stock_items` AS `smisi` ON ( `smisi`.`stockitem_id` = `si`.`id` ) 
  5196.             INNER JOIN `stock_movement_item` AS `smi` ON ( `smi`.`id` = `smisi`.`stockmovementitem_id` ) 
  5197.             INNER JOIN `products` AS `p` ON ( `p`.`id` = `smi`.`product_id` ) 
  5198.             LEFT JOIN `product_types` AS `pt` ON ( `pt`.`id` = `p`.`type_id` ) 
  5199.             LEFT JOIN `vendors` AS `v` ON ( `v`.`id` = `p`.`vendor_id` ) 
  5200.             LEFT JOIN `stock_movement_item` AS `smi2` ON ( `smi2`.`id` = ( 
  5201.                 SELECT `_smi`.`id` FROM `stock_movement_item` AS `_smi` WHERE `_smi`.`product_id` = `p`.`id` ORDER BY `_smi`.`created` DESC LIMIT 1 
  5202.             )) 
  5203.             LEFT JOIN `stock_movements` AS `sm` ON ( `sm`.`id` = `smi`.`stock_movement_id` )
  5204.             WHERE ( 
  5205.                 SELECT COUNT(`_sm`.`id`) 
  5206.                 FROM `stock_movements` AS `_sm` 
  5207.                 WHERE `_sm`.`id` = ( 
  5208.                     SELECT `__sm`.`id` 
  5209.                     FROM `stock_items` AS `_si` 
  5210.                     INNER JOIN `stock_movement_items_stock_items` AS `_smisi` ON ( `_smisi`.`stockitem_id` = `_si`.`id` ) 
  5211.                     INNER JOIN `stock_movement_item` AS `__smi` ON ( `__smi`.`id` = `_smisi`.`stockmovementitem_id` AND `__smi`.`product_id` = `p`.`id` ) 
  5212.                     INNER JOIN `stock_movements` AS `__sm` ON ( `__sm`.`id` = `__smi`.`stock_movement_id` ) 
  5213.                     WHERE `_si`.`id` = `si`.`id` 
  5214.                     ORDER BY `__sm`.`created` DESC 
  5215.                     LIMIT 1 
  5216.                 ) AND `_sm`.`type` = :movementOut 
  5217.             ) = 0 AND `p`.`name` LIKE :name 
  5218.         SQL;
  5219.         $quarantineProducts = $entityManager->getConnection()->executeQuery($sql, $params, $types);
  5220.         while ( $item = $quarantineProducts->fetchAssociative() ) {
  5221.             $output[$item['serialNumber']] = implode(';', [
  5222.                 $item['productId'], 
  5223.                 $item['productCode'], 
  5224.                 $item['productType'], 
  5225.                 $item['productVendor'], 
  5226.                 $item['productName'], 
  5227.                 $item['serialNumber'], 
  5228.                 $item['aikenCode'], 
  5229.                 $item['stockItemCreated'], 
  5230.                 $item['movementCreated'], 
  5231.                 $item['movementId'], 
  5232.                 ( !is_null($item['movementNote']) ? trim(preg_replace('/\s+/', ' ', $item['movementNote'])) : '' ), 
  5233.                 $item['purchasePrice']
  5234.             ]);  
  5235.         }
  5236.         return $output;
  5237.     }
  5238.     /**
  5239.      * @param string|null $productStockPosition
  5240.      * @return string
  5241.      * @throws \Psr\Cache\InvalidArgumentException
  5242.      */
  5243.     public function getNamedStockPositions(?string $productStockPosition): string
  5244.     {
  5245.         // Return on null
  5246.         if (is_null($productStockPosition)) {
  5247.             return '';
  5248.         }
  5249.         // Cache Key
  5250.         $cacheKey = 'productStockPositions.' . md5($productStockPosition);
  5251.         // Cache Item
  5252.         $cacheItem = $this->cache->getItem($cacheKey);
  5253.         // Return Data from Cache
  5254.         if ($cacheItem->isHit()) {
  5255.             // return $cacheItem->get(); // Disable for a while
  5256.         }
  5257.         // Entity Manager
  5258.         $entityManager = $this->getEntityManager();
  5259.         // Stock Positions
  5260.         $stockPositions = [];
  5261.         foreach (explode(',', $productStockPosition) as $stockPositionId) {
  5262.             $stockPosition = $entityManager->getRepository(StockPosition::class)->find($stockPositionId);
  5263.             if ($stockPosition) {
  5264.                 $stockPositions[] = $stockPosition->getName();
  5265.             }
  5266.         }
  5267.         // Return Data
  5268.         $stockPositions = implode(', ', $stockPositions);
  5269.         // Cache it
  5270.         $cacheItem->set($stockPositions);
  5271.         $cacheItem->expiresAfter(31536000); // 1 year
  5272.         $this->cache->save($cacheItem);
  5273.         return $stockPositions;
  5274.     }
  5275.     /**
  5276.      * @param ProductTag $productTag
  5277.      * @return Product|null
  5278.      */
  5279.     public function getAvailableProductByProductTag(ProductTag $productTag): ?Product
  5280.     {
  5281.         $entityManager = $this->getEntityManager();
  5282.         $products = $entityManager->createQuery('SELECT p
  5283.                 FROM AtechNetCoreBundle:Product p
  5284.                 LEFT JOIN p.tags productTag
  5285.                 WHERE productTag.id = :productTagId AND p.available > 0')
  5286.             ->setParameters([
  5287.                 'productTagId' => $productTag->getId(),
  5288.             ])
  5289.             ->getResult();
  5290.         if (count($products) > 0) {
  5291.             return $products[0];
  5292.         }
  5293.         return null;
  5294.     }
  5295.     /**
  5296.      * @param Product $product
  5297.      * @return void
  5298.      */
  5299.     public function checkAndUpdatePassmarkValue(Product $product): void
  5300.     {
  5301.         if ($product->getCpuMark() > 99) {
  5302.             return;
  5303.         }
  5304.         $cpuProductAttributeIds = [
  5305.             Attribute::ATTRIBUTE_ID_CPU_PC,
  5306.             Attribute::ATTRIBUTE_ID_CPU_NB,
  5307.             Attribute::ATTRIBUTE_ID_CPU_SERVER,
  5308.             Attribute::ATTRIBUTE_ID_CPU_TABLET,
  5309.             Attribute::ATTRIBUTE_ID_CPU_PHONE,
  5310.         ];
  5311.         /**
  5312.          * @var ProductAttribute $cpuProductAttribute
  5313.          */
  5314.         $cpuProductAttribute = $product->getProductAttributes()->filter(
  5315.             function (ProductAttribute $pa) use ($cpuProductAttributeIds) {
  5316.                 return in_array($pa->getOption()->getAttribute()->getId(), $cpuProductAttributeIds);
  5317.             }
  5318.         )->first();
  5319.         if (!$cpuProductAttribute) {
  5320.             return;
  5321.         }
  5322.         $cpuTitle = $cpuProductAttribute->getOption()->getTitle();
  5323.         $product->setCpuMark($this->getCpuMarkByTitle($cpuTitle));
  5324.         $this->getEntityManager()->flush();
  5325.     }
  5326.     /**
  5327.      * @param string $cpuTitle
  5328.      * @return int
  5329.      */
  5330.     private function getCpuMarkByTitle(string $cpuTitle): int
  5331.     {
  5332.         $csvPath = __DIR__ . '/../../../../web/uploads/cpu_mark_data.csv';
  5333.         $file = fopen($csvPath, 'r');
  5334.         $cpuMarkValue = 0;
  5335.         while (($line = fgetcsv($file, null, ',')) !== false) {
  5336.             if ($line[0] != 'cpu_title' && $line[0] == $cpuTitle) {
  5337.                 $cpuMarkValue = (int)str_replace([' ', ' '], '', $line[1]);
  5338.                 break;
  5339.             }
  5340.         }
  5341.         fclose($file);
  5342.         return $cpuMarkValue;
  5343.     }
  5344.     /**
  5345.      * Get Product Sales Chart Data
  5346.      *
  5347.      * @param Product $product
  5348.      * @param string $filterDateStart
  5349.      * @param string $filterDateEnd
  5350.      *
  5351.      * @return array
  5352.      */
  5353.     public function getChartProductSales(Product $product, string $filterDateStart = '', string $filterDateEnd = ''): array
  5354.     {
  5355.         // Entity Manager
  5356.         $entityManager = $this->getEntityManager();
  5357.         // Filter
  5358.         $filter = [
  5359.             'product' => $product->getId(),
  5360.             'stockMovementTypeOut' => StockMovement::TYPE_OUT,
  5361.             'subTypeConfigChange' => StockMovement::SUB_TYPE_OUT_CONFIG_CHANGE,
  5362.             'subTypeOrderConfigChange' => StockMovement::SUB_TYPE_OUT_ORDER_CONFIG_CHANGE,
  5363.             'subTypeToOtherStock' => StockMovement::SUB_TYPE_OUT_TO_OTHER_STOCK,
  5364.             'subTypeReturnedToSupplier' => StockMovement::SUB_TYPE_OUT_RETURNED_TO_SUPPLIER,
  5365.             'domainOther' => Domain::DOMAIN_ID_OTHER,
  5366.             'domainShop' => Domain::DOMAIN_ID_PC_SHOP,
  5367.             'dateStart' => $filterDateStart,
  5368.             'dateEnd' => $filterDateEnd,
  5369.         ];
  5370.         // Results
  5371.         $stockMovementItems = $entityManager->createQuery('SELECT smi
  5372.                 FROM AtechNetCoreBundle:StockMovementItem smi
  5373.                 JOIN smi.movement sm JOIN smi.product p
  5374.                 LEFT JOIN sm.merchant m
  5375.                 WHERE sm.type = :stockMovementTypeOut
  5376.                     AND sm.subType NOT IN ( :subTypeConfigChange, :subTypeOrderConfigChange, :subTypeToOtherStock, :subTypeReturnedToSupplier )
  5377.                     AND m.domain NOT IN ( :domainOther, :domainShop )
  5378.                     AND smi.product = :product
  5379.                     AND sm.created >= :dateStart
  5380.                     AND sm.created <= :dateEnd
  5381.                     AND smi.price > 0
  5382.                 ORDER BY p.code ASC')
  5383.             ->setParameters($filter)
  5384.             ->getResult();
  5385.         // Chart Data
  5386.         $data = [];
  5387.         /**
  5388.          * @var StockMovementItem $stockMovementItem
  5389.          */
  5390.         foreach ($stockMovementItems as $stockMovementItem) {
  5391.             // Stock Movement
  5392.             $stockMovement = $stockMovementItem->getMovement();
  5393.             // Customer
  5394.             $customer = $stockMovement->getMerchant();
  5395.             // Domain
  5396.             $domain = $customer->getDomain();
  5397.             // Date
  5398.             $date = $stockMovement->getCreated();
  5399.             // Date Key
  5400.             $dateKey = (clone $date)->format('Ymd');
  5401.             // Entry
  5402.             if (!isset($data[$dateKey])) {
  5403.                 $data[$dateKey] = [
  5404.                     'date' => $date->format('Y-m-d'),
  5405.                     'domains' => [],
  5406.                     'market' => [
  5407.                         'retail' => 0,
  5408.                         'wholesale' => 0,
  5409.                         'marketplace' => 0,
  5410.                     ],
  5411.                 ];
  5412.             }
  5413.             if (!isset($data[$dateKey]['domains'][$domain->getId()])) {
  5414.                 $data[$dateKey]['domains'][$domain->getId()] = 0;
  5415.             }
  5416.             $data[$dateKey]['domains'][$domain->getId()] += $stockMovementItem->getQuantity();
  5417.             if (in_array($domain->getId(), [Domain::DOMAIN_ID_RT_SK, Domain::DOMAIN_ID_RT_CZ, Domain::DOMAIN_ID_RT_HU, Domain::DOMAIN_ID_RT_EU])) {
  5418.                 $data[$dateKey]['market']['retail'] += $stockMovementItem->getQuantity();
  5419.             }
  5420.             if (in_array($domain->getId(), [Domain::DOMAIN_ID_WS_SK, Domain::DOMAIN_ID_WS_CZ, Domain::DOMAIN_ID_WS_HU, Domain::DOMAIN_ID_WS_EU])) {
  5421.                 $data[$dateKey]['market']['wholesale'] += $stockMovementItem->getQuantity();
  5422.             }
  5423.             if (in_array($domain->getId(), [Domain::DOMAIN_ID_B2C_EU])) {
  5424.                 $data[$dateKey]['market']['marketplace'] += $stockMovementItem->getQuantity();
  5425.             }
  5426.         }
  5427.         // Sort
  5428.         ksort($data);
  5429.         // Chart Data
  5430.         return $data;
  5431.     }
  5432.     /**
  5433.      * @param Product $product
  5434.      * @param string $filterDateStart
  5435.      * @param string $filterDateEnd
  5436.      * @param array $filterDomains
  5437.      *
  5438.      * @return array
  5439.      */
  5440.     public function getChartPriceChangelog(Product $product, string $filterDateStart = '', string $filterDateEnd = '', array $filterDomains = []): array
  5441.     {
  5442.         // Entity Manager
  5443.         $entityManager = $this->getEntityManager();
  5444.         // Filter
  5445.         $filter = [
  5446.             'product' => $product->getId(),
  5447.             'domains' => (
  5448.             !empty($filterDomains) ? $filterDomains : [
  5449.                 Domain::DOMAIN_ID_RT_SK, Domain::DOMAIN_ID_WS_SK,
  5450.                 Domain::DOMAIN_ID_RT_HU, Domain::DOMAIN_ID_WS_HU,
  5451.                 Domain::DOMAIN_ID_RT_CZ, Domain::DOMAIN_ID_WS_CZ,
  5452.                 Domain::DOMAIN_ID_RT_EU, Domain::DOMAIN_ID_WS_EU,
  5453.             ]
  5454.             ),
  5455.             'dateStart' => $filterDateStart,
  5456.             'dateEnd' => $filterDateEnd,
  5457.         ];
  5458.         // Results
  5459.         $results = $entityManager->createQuery("SELECT ppl.created AS date, ppl.newPrice AS price, COALESCE(u.firstName, '') AS firstName, COALESCE(u.lastName, '') AS lastName, d.id AS domainId FROM AtechNetCoreBundle:ProductPriceLog ppl JOIN ppl.domain d LEFT JOIN ppl.createdBy u WHERE ppl.product = :product AND ppl.domain IN (:domains) AND ppl.created >= :dateStart AND ppl.created <= :dateEnd ORDER BY ppl.created ASC")
  5460.             ->setParameters($filter)
  5461.             ->getResult();
  5462.         // Determine Date Interval
  5463.         $minDate = $maxDate = [];
  5464.         foreach ($results as $result) {
  5465.             $dateTime = (clone $result['date'])->setTime(0, 0)->getTimestamp();
  5466.             if (!isset($minDate[$result['domainId']])) {
  5467.                 $minDate[$result['domainId']] = $dateTime;
  5468.             }
  5469.             if ($minDate[$result['domainId']] > $dateTime) {
  5470.                 $minDate[$result['domainId']] = $dateTime;
  5471.             }
  5472.             if (!isset($maxDate[$result['domainId']])) {
  5473.                 $maxDate[$result['domainId']] = $dateTime;
  5474.             }
  5475.             if ($maxDate[$result['domainId']] < $dateTime) {
  5476.                 $maxDate[$result['domainId']] = $dateTime;
  5477.             }
  5478.         }
  5479.         // Chart Data
  5480.         $data = [];
  5481.         foreach ($minDate as $domainId => $dateTime) {
  5482.             // Domain Data
  5483.             if (!isset($data[$domainId])) {
  5484.                 $data[$domainId] = [];
  5485.             }
  5486.             // Domain Min Date
  5487.             $domainMinDate = $dateTime;
  5488.             // Domain Max Date
  5489.             $domainMaxDate = $maxDate[$domainId];
  5490.             // Previous Data
  5491.             $previousData = [];
  5492.             // Review Dates
  5493.             while ($domainMinDate <= $domainMaxDate) {
  5494.                 foreach ($results as $result) {
  5495.                     if ($result['domainId'] == $domainId) {
  5496.                         $originalValue = false;
  5497.                         $_dateTime = (clone $result['date'])->setTime(0, 0)->getTimestamp();
  5498.                         if ($_dateTime == $domainMinDate) {
  5499.                             $originalValue = true;
  5500.                             $_data = $previousData = [
  5501.                                 'tooltipDate' => date('H:i M d, Y', (clone $result['date'])->getTimestamp()),
  5502.                                 'createdBy' => trim($result['firstName'] . ' ' . $result['lastName']),
  5503.                                 'price' => $result['price'],
  5504.                             ];
  5505.                             $key = (clone $result['date'])->format('YmdHis');
  5506.                             $data[$domainId][$key] = array_merge($_data, [
  5507.                                 'date' => date('Y-m-d', $domainMinDate),
  5508.                                 'originalValue' => $originalValue,
  5509.                             ]);
  5510.                         } else {
  5511.                             $_data = $previousData;
  5512.                             $key = date('YmdHis', $domainMinDate);
  5513.                             $data[$domainId][$key] = array_merge($_data, [
  5514.                                 'date' => date('Y-m-d', $domainMinDate),
  5515.                                 'currentDate' => date('M d, Y', $domainMinDate),
  5516.                                 'originalValue' => $originalValue,
  5517.                             ]);
  5518.                         }
  5519.                     }
  5520.                 }
  5521.                 $domainMinDate = strtotime('+1 day', $domainMinDate);
  5522.             }
  5523.         }
  5524.         return $data;
  5525.     }
  5526.     /**
  5527.      * @param Product $product
  5528.      * @param string $filterDateStart
  5529.      * @param string $filterDateEnd
  5530.      * @param array $filterDomains
  5531.      *
  5532.      * @return array
  5533.      */
  5534.     public function getChartSellingPrices(Product $product, string $filterDateStart = '', string $filterDateEnd = '', array $filterDomains = []): array
  5535.     {
  5536.         // Entity Manager
  5537.         $entityManager = $this->getEntityManager();
  5538.         // Filter
  5539.         $filter = [
  5540.             'product' => $product->getId(),
  5541.             'domains' => (
  5542.             !empty($filterDomains) ? $filterDomains : [
  5543.                 Domain::DOMAIN_ID_RT_SK, Domain::DOMAIN_ID_WS_SK,
  5544.                 Domain::DOMAIN_ID_RT_HU, Domain::DOMAIN_ID_WS_HU,
  5545.                 Domain::DOMAIN_ID_RT_CZ, Domain::DOMAIN_ID_WS_CZ,
  5546.                 Domain::DOMAIN_ID_RT_EU, Domain::DOMAIN_ID_WS_EU,
  5547.                 Domain::DOMAIN_ID_B2C_EU,
  5548.             ]
  5549.             ),
  5550.             'status' => Order::STATUS_FINISHED,
  5551.             'dateStart' => $filterDateStart,
  5552.             'dateEnd' => $filterDateEnd,
  5553.         ];
  5554.         // Results
  5555.         $results = $entityManager->createQuery('SELECT oi.price, d.id AS domainId, o.created AS date FROM AtechNetCoreBundle:OrderItem oi JOIN oi.order o JOIN o.domain d WHERE oi.product = :product AND o.status = :status AND o.created >= :dateStart AND o.created <= :dateEnd AND o.domain IN (:domains) ORDER BY o.created ASC')
  5556.             ->setParameters($filter)
  5557.             ->getResult();
  5558.         // Determine Date Interval
  5559.         $minDate = $maxDate = [];
  5560.         foreach ($results as $result) {
  5561.             $dateTime = $result['date']->setTime(0, 0)->getTimestamp();
  5562.             if (!isset($minDate[$result['domainId']])) {
  5563.                 $minDate[$result['domainId']] = $dateTime;
  5564.             }
  5565.             if ($minDate[$result['domainId']] > $dateTime) {
  5566.                 $minDate[$result['domainId']] = $dateTime;
  5567.             }
  5568.             if (!isset($maxDate[$result['domainId']])) {
  5569.                 $maxDate[$result['domainId']] = $dateTime;
  5570.             }
  5571.             if ($maxDate[$result['domainId']] < $dateTime) {
  5572.                 $maxDate[$result['domainId']] = $dateTime;
  5573.             }
  5574.         }
  5575.         // Chart Data
  5576.         $data = [];
  5577.         foreach ($minDate as $domainId => $dateTime) {
  5578.             // Domain Data
  5579.             if (!isset($data[$domainId])) {
  5580.                 $data[$domainId] = [];
  5581.             }
  5582.             // Domain Min Date
  5583.             $domainMinDate = $dateTime;
  5584.             // Domain Max Date
  5585.             $domainMaxDate = $maxDate[$domainId];
  5586.             // Previous Price
  5587.             $previousPrice = 0;
  5588.             // Review Dates
  5589.             while ($domainMinDate <= $domainMaxDate) {
  5590.                 foreach ($results as $result) {
  5591.                     if ($result['domainId'] == $domainId) {
  5592.                         $originalValue = false;
  5593.                         $dateTime = $result['date']->setTime(0, 0)->getTimestamp();
  5594.                         if ($dateTime == $domainMinDate) {
  5595.                             $originalValue = true;
  5596.                             $price = $previousPrice = $result['price'];
  5597.                         } else {
  5598.                             $price = $previousPrice;
  5599.                         }
  5600.                         $data[$domainId][] = [
  5601.                             'date' => date('Y-m-d', $domainMinDate),
  5602.                             'tooltipDate' => date('M d, Y', $domainMinDate),
  5603.                             'price' => $price,
  5604.                             'originalValue' => $originalValue,
  5605.                         ];
  5606.                     }
  5607.                 }
  5608.                 $domainMinDate = strtotime('+1 day', $domainMinDate);
  5609.             }
  5610.         }
  5611.         return $data;
  5612.     }
  5613.     /**
  5614.      * @param Product $product
  5615.      * @return array
  5616.      */
  5617.     public function getChartPurchasePrices(Product $product): array
  5618.     {
  5619.         $results = $this->getEntityManager()->createQuery('SELECT smi.price, d.priceFactor, smi.quantity, sm.id AS stockMovementId, sm.quantity AS stockMovementQuantity, sm.created, sm.extraPrice, c.businessName
  5620.                 FROM AtechNetCoreBundle:Product p
  5621.                 LEFT JOIN p.stockMovementItems smi
  5622.                 LEFT JOIN smi.movement sm
  5623.                 LEFT JOIN sm.merchant c
  5624.                 LEFT JOIN c.domain d
  5625.                 WHERE smi.product = :product AND sm.subType = :movementSubTypeInBasic
  5626.                 ORDER BY sm.created ASC')
  5627.             ->setParameters(
  5628.                 [
  5629.                     'product' => $product,
  5630.                     'movementSubTypeInBasic' => StockMovement::SUB_TYPE_IN_SHIPMENT,
  5631.                 ]
  5632.             )
  5633.             ->getResult();
  5634.         $data = [];
  5635.         foreach ($results as $r) {
  5636.             if ($r['priceFactor'] <= 0) {
  5637.                 $r['priceFactor'] = 1;
  5638.             }
  5639.             $pricePerItem = $r['price'] != 0 ? ($r['price'] / $r['priceFactor']) / $r['quantity'] : 0;
  5640.             $extraPricePerItem = $r['extraPrice'] > 0 ? ($r['extraPrice'] / $r['priceFactor']) / $r['stockMovementQuantity'] : 0;
  5641.             $purchasePricePerItem = number_format($pricePerItem + $extraPricePerItem, 2, '.', '');
  5642.             $data[$r['stockMovementId']]['stockMovementId'] = $r['stockMovementId'];
  5643.             $data[$r['stockMovementId']]['merchant'] = $r['businessName'];
  5644.             $data[$r['stockMovementId']]['purchasePrice'] = $purchasePricePerItem;
  5645.             $data[$r['stockMovementId']]['date'] = $r['created']->format('d.m.Y');
  5646.         }
  5647.         return $data;
  5648.     }
  5649.     /**
  5650.      * @param Product $product
  5651.      * @param bool $flush
  5652.      * @return ProductModel|null
  5653.      */
  5654.     public function handleProductModelCreation(Product $product, bool $flush = true): ?ProductModel
  5655.     {
  5656.         if (!in_array($product->getType()->getId(), ProductType::getTypesWithProductModels())) {
  5657.             return null;
  5658.         }
  5659.         if ($product->isContainer() && !$this->containerContainsOnlyNotebookAndBattery($product)) {
  5660.             return null;
  5661.         }
  5662.         $entityManager = $this->getEntityManager();
  5663.         $productModelTitle = $this->getProductModelTitle($product, $this->getExcludableExpressionsForProductModelCreate());
  5664.         $productModel = $entityManager->getRepository(ProductModel::class)->findOneBy([
  5665.             'title' => $productModelTitle,
  5666.             'isProductV2' => false,
  5667.         ]);
  5668.         if ($productModel) {
  5669.             $product->setProductModel($productModel);
  5670.         } else {
  5671.             $productModel = new ProductModel();
  5672.             $productModel->setTitle($productModelTitle);
  5673.             /** @var User $currentUser */
  5674.             $currentUser = $this->getUser();
  5675.             $productModel->setCreatedBy($currentUser);
  5676.             $entityManager->persist($productModel);
  5677.             if ($flush) {
  5678.                 $entityManager->flush();
  5679.             }
  5680.             $product->setProductModel($productModel);
  5681.         }
  5682.         if ($flush) {
  5683.             $entityManager->flush();
  5684.         }
  5685.         return $productModel;
  5686.     }
  5687.     /**
  5688.      * @param Product $product
  5689.      * @param bool $onlyNew
  5690.      *
  5691.      * @return bool
  5692.      */
  5693.     public function containerContainsOnlyNotebookAndBattery(Product $product, bool $onlyNew = false): bool
  5694.     {
  5695.         if ($product->getProductContainers()->count() != 2) {
  5696.             return false;
  5697.         }
  5698.         $hasNotebookType = $hasBatteryType = false;
  5699.         /** @var ProductContainer $containerProduct */
  5700.         foreach ($product->getProductContainers() as $containerProduct) {
  5701.             if (is_null($containerProduct->getChild())) {
  5702.                 continue;
  5703.             }
  5704.             if ($containerProduct->getChild()->getType()->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK) {
  5705.                 $hasNotebookType = true;
  5706.                 continue;
  5707.             }
  5708.             if ($containerProduct->getChild()->getType()->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK_BATTERY) {
  5709.                 if ($onlyNew) {
  5710.                     if ($containerProduct->getChild()->getQualityStatus() == Product::QualityStatus_New) {
  5711.                         $hasBatteryType = true;
  5712.                     }
  5713.                 } else {
  5714.                     $hasBatteryType = true;
  5715.                 }
  5716.             }
  5717.         }
  5718.         return $hasNotebookType && $hasBatteryType;
  5719.     }
  5720.     /**
  5721.      * @param Product $product
  5722.      * @param array $textToExcludeFromName
  5723.      * @return string
  5724.      */
  5725.     public function getProductModelTitle(Product $product, array $textToExcludeFromName = []): string
  5726.     {
  5727.         $productModelTitle = $product->getName();
  5728.         foreach ($textToExcludeFromName as $excludeableText) {
  5729.             $productModelTitle = trim(str_replace($excludeableText, '', $productModelTitle));
  5730.             $excludeableTextUcFirst = implode(' ', array_map('ucfirst', explode(' ', $excludeableText)));
  5731.             $productModelTitle = trim(str_replace($excludeableTextUcFirst, '', $productModelTitle));
  5732.             $productModelTitle = preg_replace('/\s+/', ' ', $productModelTitle);
  5733.             $productModelTitle = str_replace(['á', 'é', 'í', 'ó', 'ő', 'ú', 'ű'], ['a', 'e', 'i', 'o', 'o', 'u', 'u'], $productModelTitle);
  5734.         }
  5735.         if ($product->getType()->getId() == ProductType::PRODUCT_TYPE_ID_NOTEBOOK) {
  5736.             $processorFamily = '';
  5737.             $processorGen = '';
  5738.             $attributeOptionCpuNB = '';
  5739.             foreach ($product->getSpecificationProductAttributeOptions() as $productAttributeOption) {
  5740.                 $option = $productAttributeOption->getOption();
  5741.                 if ($option->getAttribute()->getId() == Attribute::ATTRIBUTE_ID_CPU_NB) {
  5742.                     $processorFamily = $option->getParent()->getTitle();
  5743.                     $attributeOptionCpuNB = $option->getTitle();
  5744.                 }
  5745.                 if ($option->getAttribute()->getId() == Attribute::ATTRIBUTE_ID_CPU_GENERATION) {
  5746.                     $processorGen = $option->getTitle();
  5747.                 }
  5748.             }
  5749.             $productModelTitle = $productModelTitle . ($processorFamily != '' ? '-' . $processorFamily : '') . ($processorGen != '' ? '-' . str_replace('. Generation', '', $processorGen) : $this->getCPUGenfromAttributeOptionCPU($attributeOptionCpuNB));
  5750.         }
  5751.         if (in_array($product->getType()->getId(), [ProductType::PRODUCT_TYPE_ID_PC, ProductType::PRODUCT_TYPE_ID_ALL_IN_ONE])) {
  5752.             $processorFamily = '';
  5753.             $processorGen = '';
  5754.             $attributeOptionCpuPC = '';
  5755.             foreach ($product->getSpecificationProductAttributeOptions() as $productAttributeOption) {
  5756.                 $option = $productAttributeOption->getOption();
  5757.                 if ($option->getAttribute()->getId() == Attribute::ATTRIBUTE_ID_CPU_PC) {
  5758.                     $processorFamily = $option->getParent()->getTitle();
  5759.                     $attributeOptionCpuPC = $option->getTitle();
  5760.                 }
  5761.                 if ($option->getAttribute()->getId() == Attribute::ATTRIBUTE_ID_CPU_GENERATION) {
  5762.                     $processorGen = $option->getTitle();
  5763.                 }
  5764.             }
  5765.             $productModelTitle = $productModelTitle . ($processorFamily != '' ? '-' . $processorFamily : '') . ($processorGen != '' ? '-' . str_replace('. Generation', '', $processorGen) : $this->getCPUGenfromAttributeOptionCPU($attributeOptionCpuPC));
  5766.         }
  5767.         return $productModelTitle;
  5768.     }
  5769.     /**
  5770.      * @param string $attributeOptionCpu
  5771.      * @return string
  5772.      */
  5773.     private function getCPUGenfromAttributeOptionCPU(string $attributeOptionCpu): string
  5774.     {
  5775.         if (!str_starts_with($attributeOptionCpu, 'i')) {
  5776.             return '';
  5777.         }
  5778.         $cpuGenFirstNumber = substr($attributeOptionCpu, 3, 1);
  5779.         $cpuGenNumber = $cpuGenFirstNumber == 1 ? substr($attributeOptionCpu, 3, 2) : substr($attributeOptionCpu, 3, 1);
  5780.         return strlen($attributeOptionCpu) > 0 ? "-$cpuGenNumber" : '';
  5781.     }
  5782.     /**
  5783.      * @return array
  5784.      */
  5785.     public function getExcludableExpressionsForProductModelCreate(): array
  5786.     {
  5787.         $textToExcludeFromProductModelTitle = [];
  5788.         $notebookSkinList = $this->getEntityManager()->getRepository(AttributeOption::class)->findBy([
  5789.             'attribute' => Attribute::ATTRIBUTE_ID_NOTEBOOK_SKIN,
  5790.         ]);
  5791.         /**
  5792.          * @var AttributeOption $notebookSkin
  5793.          */
  5794.         foreach ($notebookSkinList as $notebookSkin) {
  5795.             $textToExcludeFromProductModelTitle[] = $notebookSkin->getTitle();
  5796.         }
  5797.         return array_merge([], $textToExcludeFromProductModelTitle, [
  5798.             'Satin kirby pink',
  5799.             'White',
  5800.             'Metallic rose gold',
  5801.             'Wave',
  5802.             'Metalic Rosegold',
  5803.             // '(Quality: Bazár)',
  5804.             'Candy Red',
  5805.             'Brushed Antracite',
  5806.             'Gloss Pink',
  5807.             'Gunmetal Gray',
  5808.             'Blue',
  5809.             'Matte Dark Gray',
  5810.             'Matte',
  5811.             'Matte Black',
  5812.             'Red',
  5813.             'Black',
  5814.             'Ultramatte Burgundy',
  5815.             'Gloss Burgundy',
  5816.             'Gloss Amethyst',
  5817.             'WHITE STARLIGHT',
  5818.             'WAVE 3D',
  5819.             'Metallic Rosengold',
  5820.             'Lime metalic',
  5821.             'Lime green',
  5822.             'white starlight',
  5823.             'Purple blue',
  5824.             'Satin metal mint',
  5825.             'starlight',
  5826.             'Grey',
  5827.             'BLUE',
  5828.             'RED',
  5829.             'Metal',
  5830.             'Pink',
  5831.             'Rugged',
  5832.             'GOLD',
  5833.             'Antracit',
  5834.             'Silver',
  5835.             'Space grey',
  5836.             'Glossy',
  5837.             'Brushed',
  5838.             'Magenta',
  5839.             'Satin  Kirby pink',
  5840.             'Satin Kirby pink',
  5841.             'Burgundy',
  5842.             'Light',
  5843.             'Purple',
  5844.             'Royal',
  5845.             'JUNGLE',
  5846.             'MATTE METAL',
  5847.             'PLUM VIOLET',
  5848.             'SATIN METAL MINT',
  5849.             'Gloss',
  5850.             'Kirby',
  5851.             'Rush',
  5852.             'Lime matte metalic',
  5853.             'brown',
  5854.             'Cement',
  5855.             'chrome blue',
  5856.             'matt lightning',
  5857.             'Camouflage',
  5858.             'Carbon Fiber',
  5859.             'Carbon',
  5860.         ]);
  5861.     }
  5862.     /**
  5863.      * @param string $listingTitle
  5864.      * @return string|null
  5865.      */
  5866.     public function setProductModelNameByExpressions(string $listingTitle): ?string
  5867.     {
  5868.         $listingTitle = strtolower($listingTitle);
  5869.         $expresionsToCheck = [
  5870.             't460' => 'ThinkPad T460-i5-6',
  5871.             '5490' => 'Latitude 5490-i5-8',
  5872.             '830 g5' => 'EliteBook 830 G5-i5-8',
  5873.             '840 g6' => 'EliteBook 840 G6-i5-8',
  5874.             'x390' => 'ThinkPad x390-i5-8',
  5875.             '840 g5' => 'EliteBook 840 G5-i5-8',
  5876.             '7490' => 'Latitude 7490-i5-8',
  5877.             '5470' => 'Latitude E5470-i5-6',
  5878.             // 'l560' => 'i5-8',
  5879.             '5480' => 'Latitude 5480-i5-6',
  5880.             't460s' => 'ThinkPad T460s-i5-6',
  5881.             '7450' => 'Latitude E7450-i5-5',
  5882.             '850 g5' => 'EliteBook 850 G5-i5-8',
  5883.             '840 g2' => 'EliteBook 840 G2-i5-5',
  5884.             't440' => 'ThinkPad T440-i5-4',
  5885.             '850 g6' => 'EliteBook 850 G6-i5-8',
  5886.             'x360 1030 g2' => 'EliteBook x360 1030 G2-i5-7',
  5887.             '820 g1' => 'EliteBook 820 G1-i5-4',
  5888.             'T495s' => 'ThinkPad T495s-AMD Ryzen',
  5889.             'T495' => 'ThinkPad T495-AMD Ryzen',
  5890.         ];
  5891.         foreach ($expresionsToCheck as $expression => $productModelTitle) {
  5892.             if (str_contains($listingTitle, $expression)) {
  5893.                 return $productModelTitle;
  5894.             }
  5895.         }
  5896.         $expresionsToCheck = [
  5897.             't490s;i7' => 'ThinkPad T490s-i7-8',
  5898.             't490s;i5' => 'ThinkPad T490s-i5-8',
  5899.             't480;i7' => 'ThinkPad T480-i7-8',
  5900.             't480;i5' => 'ThinkPad T480-i5-8',
  5901.             'yoga 370;i5' => 'ThinkPad Yoga 370-i5-7',
  5902.             'yoga 370;i7' => 'ThinkPad Yoga 370-i7-7',
  5903.             't480s;i5' => 'ThinkPad T480s-i5-8',
  5904.             't480s;i7' => 'ThinkPad T480s-i7-8',
  5905.             't490;i7' => 'ThinkPad T490-i7-8',
  5906.             't490;i5' => 'ThinkPad T490-i5-8',
  5907.             '7470;i5' => 'Latitude E7470-i5-6',
  5908.             '7470;i7' => 'Latitude E7470-i7-6',
  5909.             't470;i5' => 'ThinkPad T470-i5-7',
  5910.             't470;i7' => 'ThinkPad T470-i7-7',
  5911.             't470s;i5' => 'ThinkPad T470s-i5-7',
  5912.             't470s;i7' => 'ThinkPad T470s-i7-7',
  5913.             '840 g3;i3' => 'EliteBook 840 G3-i3-6',
  5914.             '840 g3;i5' => 'EliteBook 840 G3-i5-6',
  5915.             '840 g3;i7' => 'EliteBook 840 G3-i7-6',
  5916.             '840 g1;i5' => 'EliteBook 840 G1-i5-4',
  5917.             'l460;pentium' => 'ThinkPad L460-Celeron',
  5918.             '850 g3;i5' => 'EliteBook 850 G3-i5-6',
  5919.             '850 g3;i7' => 'EliteBook 850 G3-i7-6',
  5920.             'latitude 5510;i5' => 'Latitude 5510-i5-10',
  5921.             '820 g3;i3' => 'EliteBook 820 G3-i3-6',
  5922.             '820 g3;i5' => 'EliteBook 820 G3-i5-6',
  5923.             '820 g3;i7' => 'EliteBook 820 G3-i7-6',
  5924.             'l450;i3' => 'ThinkPad L450-i3-5',
  5925.             'l450;i5' => 'ThinkPad L450-i5-5',
  5926.             't560;i5' => 'ThinkPad T560-i5-6',
  5927.             't560;i7' => 'ThinkPad T560-i7-6',
  5928.             't580;i5' => 'ThinkPad T580-i5-8',
  5929.             't580;i7' => 'ThinkPad T580-i7-8',
  5930.             'p50s;i7' => 'ThinkPad P50s-i7-6',
  5931.             'p50;i7' => 'ThinkPad P50-i7-6',
  5932.             't570;i5' => 'ThinkPad T570-i5-7',
  5933.             't570;i7' => 'ThinkPad T570-i7-7',
  5934.             'thinkpad x1 yoga g2;i7-7' => 'ThinkPad X1 Yoga Gen 2-i7-7',
  5935.             'thinkpad x1 yoga g2;i5-7' => 'ThinkPad X1 Yoga Gen 2-i5-7',
  5936.             'thinkpad x1 yoga g2;i5-8' => "ThinkPad X1 Yoga Gen 2-i5-8",
  5937.             'optiplex 7020 sff;i5-4' => 'OptiPlex 7020 SFF-i5-4',
  5938.             'optiplex 3010 sff;i5-3' => 'OptiPlex 3010 SFF-i5-3',
  5939.             'elitedesk 800 g2 sff;i7-6' => 'EliteDesk 800 G2 SFF-i7-6',
  5940.             'elitedesk 800 g2 dm;i5-6' => 'EliteDesk 800 G2 DM-i5-6',
  5941.             'esprimo q920;i5-4' => 'Esprimo Q920 USFF-i5-4',
  5942.             'elitedesk 800 g1 usdt;i5-4' => 'EliteDesk 800 G1 USDT-i5-4',
  5943.             'elitedesk 800 g1 usdt;i3-4' => 'EliteDesk 800 G1 USDT-i3-4',
  5944.             'elitedesk 800 g1 usdt;i7-4' => 'EliteDesk 800 G1 USDT-i7-4',
  5945.         ];
  5946.         foreach ($expresionsToCheck as $expressions => $productModelTitle) {
  5947.             $expressionPairToCheck = explode(';', $expressions);
  5948.             if (str_contains($listingTitle, $expressionPairToCheck[0]) && str_contains($listingTitle, $expressionPairToCheck[1])) {
  5949.                 return $productModelTitle;
  5950.             }
  5951.         }
  5952.         return null;
  5953.     }
  5954.     /**
  5955.      * @param string $listingTitle
  5956.      * @return string
  5957.      */
  5958.     public function getModelNameFromListingTitle(string $listingTitle): string
  5959.     {
  5960.         $listingTitle = substr(strstr($listingTitle, ' '), 1);
  5961.         $cpuGen = '';
  5962.         if (str_contains($listingTitle, '| i')) {
  5963.             $cpuData = explode(' | ', $listingTitle)[1];
  5964.             $cpuGen = strlen($cpuData) == 8 ? substr_replace($cpuGen, '', 0, 4) : substr_replace($cpuGen, '', 0, 5);
  5965.             $cpuGen = "-$cpuGen";
  5966.         }
  5967.         $searchedCharacterIndex = strpos($listingTitle, ' |');
  5968.         $clearTitle = substr_replace($listingTitle, '', $searchedCharacterIndex, strlen($listingTitle) - $searchedCharacterIndex);
  5969.         $searchedInchIndex = strpos($listingTitle, '-inch');
  5970.         if ($searchedInchIndex) {
  5971.             $clearTitle = substr_replace($listingTitle, '', $searchedInchIndex, strlen($listingTitle) - $searchedInchIndex);
  5972.         }
  5973.         $searchedTumIndex = strpos($listingTitle, '-tum');
  5974.         if ($searchedTumIndex) {
  5975.             $clearTitle = substr_replace($listingTitle, '', $searchedTumIndex, strlen($listingTitle) - $searchedTumIndex);
  5976.         }
  5977.         return str_replace(['Latitude 14 5495', 'Business PC'], ['Latitude 5495', ''], $clearTitle) . $cpuGen;
  5978.     }
  5979.     /**
  5980.      * @param int $marginLevel
  5981.      * @return void
  5982.      * @throws Exception
  5983.      */
  5984.     public function updateMarginLevelForAllProducts(int $marginLevel): void
  5985.     {
  5986.         if ($marginLevel < Product::MARGIN_LEVEL_NO || $marginLevel > Product::MARGIN_LEVEL_E) {
  5987.             throw new InvalidParameterException('Invalid marginLevel');
  5988.         }
  5989.         $entityManager = $this->getEntityManager();
  5990.         $connection = $entityManager->getConnection();
  5991.         $sql = 'UPDATE products SET margin_level = ' . $marginLevel;
  5992.         $connection->executeQuery($sql);
  5993.     }
  5994.     /**
  5995.      * @param array<string> $csvFileContent returned by file()
  5996.      */
  5997.     public function updateMarginLevelFromCSV(array $csvFileContent): Generator
  5998.     {
  5999.         $csv = array_map('str_getcsv', $csvFileContent);
  6000.         array_walk($csv, function (&$a) use ($csv) {
  6001.             $a = array_combine($csv[0], $a);
  6002.         });
  6003.         array_shift($csv);
  6004.         $marginLevels = array_flip(Product::getMarginLevelChoices());
  6005.         foreach ($csv as $line) {
  6006.             $product = $this->findOneByCode($line['CODE']);
  6007.             if ($product && isset($marginLevels[$line['MARGIN_LEVEL']])) {
  6008.                 yield $product->getCode() => $line['MARGIN_LEVEL'];
  6009.                 $product->setMarginLevel($marginLevels[$line['MARGIN_LEVEL']]);
  6010.             }
  6011.         }
  6012.     }
  6013.     /**
  6014.      * @param Basket|null $basket
  6015.      * @return bool
  6016.      */
  6017.     public function isBasketIncludesOnlyGiftCart(?Basket $basket): bool
  6018.     {
  6019.         if (is_null($basket)) {
  6020.             return false;
  6021.         }
  6022.         foreach ($basket->getBasketItems() as $bi) {
  6023.             if (!in_array($bi->getProduct()->getId(), Product::giftCartIds)) {
  6024.                 return false;
  6025.             }
  6026.         }
  6027.         return true;
  6028.     }
  6029.     /**
  6030.      * @param int $attributeId
  6031.      * @param array $attributeOptionIdList
  6032.      * @param array $productIdList
  6033.      * @return float|int|mixed|string
  6034.      */
  6035.     public function getAttributeOptionList(int $attributeId, array $attributeOptionIdList = [], array $productIdList = [])
  6036.     {
  6037.         return $this->getOptionListByQuery($attributeId, $attributeOptionIdList, $productIdList);
  6038.     }
  6039.     /**
  6040.      * @param int $attributeId
  6041.      * @return string
  6042.      */
  6043.     public function getAttributeOptionElementList(int $attributeId): string
  6044.     {
  6045.         $optionList = $this->getOptionListByQuery($attributeId);
  6046.         return $this->createHtmlElementsByOptionList($optionList);
  6047.     }
  6048.     /**
  6049.      * @param array $optionList
  6050.      * @return string
  6051.      */
  6052.     public function getAttributeOptionElementListByOptionList(array $optionList): string
  6053.     {
  6054.         return $this->createHtmlElementsByOptionList($optionList);
  6055.     }
  6056.     /**
  6057.      * @param $attributeId
  6058.      * @param array $attributeOptionIdList
  6059.      * @param array $productIdList
  6060.      * @return float|int|mixed|string
  6061.      */
  6062.     private function getOptionListByQuery($attributeId, array $attributeOptionIdList = [], array $productIdList = [])
  6063.     {
  6064.         if (count($attributeOptionIdList) > 0) {
  6065.             $attributeOptionIdListTextFormat = implode(',', $attributeOptionIdList);
  6066.             $productIdListTextFormat = implode(',', $productIdList);
  6067.             return $this->getEntityManager()->createQuery("SELECT ao.id AS optionId, ao.title AS optionTitle, a.id AS attributeId, a.title AS attributeTitle
  6068.                     FROM AtechNetCoreBundle:AttributeOption ao
  6069.                     INNER JOIN ao.attribute a
  6070.                     LEFT JOIN AtechNetCoreBundle:ProductAttribute pa WITH pa.option = ao.id
  6071.                     WHERE a.id = :attributeId AND ao.archived = 0 AND (ao.id IN ($attributeOptionIdListTextFormat) OR IDENTITY(pa.product) IN ($productIdListTextFormat))
  6072.                     GROUP BY ao.id
  6073.                     ORDER BY ao.parent ASC, ao.sortIndex ASC")
  6074.                 ->setParameters([
  6075.                     'attributeId' => $attributeId,
  6076.                 ])
  6077.                 ->getResult();
  6078.         }
  6079.         return $this->getEntityManager()->createQuery('SELECT ao
  6080.                 FROM AtechNetCoreBundle:AttributeOption ao
  6081.                 LEFT JOIN ao.attribute a
  6082.                 WHERE a.id = :attributeId AND ao.archived = 0
  6083.                 ORDER BY ao.parent ASC, ao.sortIndex ASC')
  6084.             ->setParameters([
  6085.                 'attributeId' => $attributeId,
  6086.             ])
  6087.             ->getResult();
  6088.     }
  6089.     /**
  6090.      * @param $optionList
  6091.      * @return string
  6092.      */
  6093.     private function createHtmlElementsByOptionList($optionList): string
  6094.     {
  6095.         $returnOptions = '';
  6096.         $parent = 0;
  6097.         $removeOptionsWithParents = false;
  6098.         foreach ($optionList as $option) {
  6099.             if ($option->getParent() != null) {
  6100.                 $removeOptionsWithParents = true;
  6101.             }
  6102.         }
  6103.         foreach ($optionList as $option) {
  6104.             if ($removeOptionsWithParents && $option->getParent() == null && $option->getId() != 1132 && substr($option->getTitle(), 0, 3) !== 'NO ') {
  6105.                 continue;
  6106.             }
  6107.             $separator = false;
  6108.             if ($option->getParent() != null) {
  6109.                 if ($parent == 0) {
  6110.                     $parent = $option->getParent()->getId();
  6111.                     $separator = true;
  6112.                 } else {
  6113.                     if ($parent != $option->getParent()->getId()) {
  6114.                         $parent = $option->getParent()->getId();
  6115.                         $separator = true;
  6116.                     }
  6117.                 }
  6118.             }
  6119.             if ($separator) {
  6120.                 $returnOptions .= '<option disabled>------------- ' . $option->getParent()->getTitle() . ' -------------</option>';
  6121.             }
  6122.             $returnOptions .= '<option value="' . $option->getId() . '" data-attribute-option-id="' . $option->getId() . '">' . $option->getTitle() . '</option>';
  6123.         }
  6124.         return $returnOptions;
  6125.     }
  6126.     /**
  6127.      * @param Request $request
  6128.      * @return float|int|mixed|string
  6129.      */
  6130.     public function searchProductsByRequest(Request $request): mixed
  6131.     {
  6132.         $term = $request->query->get('term');
  6133.         $domainId = $request->query->get('domainId');
  6134.         $onlyFlashSales = $request->query->get('onlyFlashSales');
  6135.         $maxResults = $request->query->get('maxResults') ? $request->query->get('maxResults') : 1;
  6136.         if ($maxResults == 1) {
  6137.             $select = ' p.code = ' . $term;
  6138.         } else {
  6139.             $select = " CONCAT(p.code, ' ', pt.title, ' ', v.name, ' ', p.name, ' ', p.cacheDescription) LIKE '%" . $term . "%'";
  6140.         }
  6141.         $flashSaleWhere = '';
  6142.         if ($onlyFlashSales == '1') {
  6143.             $flashSaleWhere .= ' AND (pm.saleStart IS NOT NULL OR pm.saleEnd IS NOT NULL)';
  6144.         }
  6145.         return $this->getEntityManager()->createQuery("SELECT p.id, p.code, CONCAT(pt.title, ' ', v.name,' ', p.name) AS title, p.cacheDescription, p.available, p.inStock
  6146.                 FROM AtechNetCoreBundle:Product p
  6147.                 LEFT JOIN p.vendor v
  6148.                 LEFT JOIN p.type pt
  6149.                 LEFT JOIN AtechNetCoreBundle:ProductMeta pm WITH pm.product = p AND pm.domain = $domainId 
  6150.                 WHERE pm.active = 1 AND pm.orderable = 1 AND pm.listed = 1 AND p.available > 0 AND" . $select . $flashSaleWhere)
  6151.             ->setMaxResults($maxResults)
  6152.             ->getResult();
  6153.     }
  6154.     /**
  6155.      * @param ProductModel $productModel
  6156.      * @return QueryBuilder
  6157.      */
  6158.     public function getProductModels(ProductModel $productModel): QueryBuilder
  6159.     {
  6160.         $entityManager = $this->getEntityManager();
  6161.         return $entityManager->getRepository(Product::class)
  6162.             ->createQueryBuilder('p')
  6163.             ->where('p.productModel = :productModel')
  6164.             ->setParameter(
  6165.                 'productModel',
  6166.                 $productModel->getId()
  6167.             );
  6168.     }
  6169.     /**
  6170.      * @param string $term
  6171.      * @return float|int|mixed|string
  6172.      */
  6173.     public function searchProduct(string $term): mixed
  6174.     {
  6175.         return $this->getEntityManager()
  6176.             ->createQuery('SELECT p.id, p.name
  6177.                 FROM AtechNetCoreBundle:Product p
  6178.                 LEFT JOIN p.productModel pm
  6179.                 WHERE p.name LIKE :term AND pm.id IS NULL
  6180.                 GROUP BY p.name 
  6181.                 ORDER BY p.name ASC')
  6182.             ->setParameters([
  6183.                 'term' => '%' . $term . '%',
  6184.             ])
  6185.             ->getResult();
  6186.     }
  6187.     /**
  6188.      * @param array $modelIds
  6189.      * @param bool $onlyIds
  6190.      * @param array $oldProductAttributeOptionFilters
  6191.      * @return array
  6192.      */
  6193.     public function findProductsByModelIds(array $modelIds, bool $onlyIds = false, array $oldProductAttributeOptionFilters = []): array
  6194.     {
  6195.         $result = $this->createQueryBuilder('p')
  6196.             ->innerJoin('p.productModel', 'pm')
  6197.             ->where('pm.id IN (:ids)')
  6198.             ->setParameter('ids', $modelIds);
  6199.         if (!empty($oldProductAttributeOptionFilters)) {
  6200.             $result->innerJoin('p.productAttributes', 'pa')
  6201.                 ->innerJoin('pa.option', 'ao')
  6202.                 ->andWhere('ao.id IN (:filterIds)')
  6203.                 ->setParameter('filterIds', $oldProductAttributeOptionFilters)
  6204.                 ->groupBy('p.id')
  6205.                 ->having('COUNT(DISTINCT ao.id) = :filterCount')
  6206.                 ->setParameter('filterCount', count($oldProductAttributeOptionFilters));
  6207.         }
  6208.         if ($onlyIds) {
  6209.             $result = $result->select('p.id');
  6210.             return $result->getQuery()->getSingleColumnResult();
  6211.         }
  6212.         return $result->getQuery()->getResult();
  6213.     }
  6214.     /**
  6215.      * @param array $productIds
  6216.      * @param bool $onlyIds
  6217.      * @param array $oldProductAttributeOptionFilters
  6218.      * @return array
  6219.      */
  6220.     public function findProductsByIds(array $productIds, bool $onlyIds = false, array $oldProductAttributeOptionFilters = []): array
  6221.     {
  6222.         $result = $this->createQueryBuilder('p')
  6223.             ->where('p.id IN (:ids)')
  6224.             ->setParameter('ids', $productIds);
  6225.         if (!empty($oldProductAttributeOptionFilters)) {
  6226.             $result->innerJoin('p.productAttributes', 'pa')
  6227.                 ->innerJoin('pa.option', 'ao')
  6228.                 ->andWhere('ao.id IN (:filterIds)')
  6229.                 ->setParameter('filterIds', $oldProductAttributeOptionFilters)
  6230.                 ->groupBy('p.id')
  6231.                 ->having('COUNT(DISTINCT ao.id) = :filterCount')
  6232.                 ->setParameter('filterCount', count($oldProductAttributeOptionFilters));
  6233.         }
  6234.         if ($onlyIds) {
  6235.             $result = $result->select('p.id');
  6236.             return $result->getQuery()->getSingleColumnResult();
  6237.         }
  6238.         return $result->getQuery()->getResult();
  6239.     }
  6240.     /**
  6241.      * @param array $productIds
  6242.      * @param bool $onlyIds
  6243.      * @param array $oldProductAttributeOptionFilters
  6244.      * @return array
  6245.      */
  6246.     public function findAllProductsByProductIds(array $productIds, bool $onlyIds = false, array $oldProductAttributeOptionFilters = []): array
  6247.     {
  6248.         $modelIds = $this->getEntityManager()->getRepository(ProductModel::class)->createQueryBuilder('pm')
  6249.             ->select('pm.id')
  6250.             ->innerJoin('pm.products', 'p')
  6251.             ->where('p.id IN (:ids)')
  6252.             ->setParameter('ids', $productIds)
  6253.             ->getQuery()->getSingleColumnResult();
  6254.         $result = $this->createQueryBuilder('p')
  6255.             ->innerJoin('p.productModel', 'pm')
  6256.             ->where('pm.id IN (:ids)')
  6257.             ->setParameter('ids', $modelIds);
  6258.         if (!empty($oldProductAttributeOptionFilters)) {
  6259.             $result->innerJoin('p.productAttributes', 'pa')
  6260.                 ->innerJoin('pa.option', 'ao')
  6261.                 ->andWhere('ao.id IN (:filterIds)')
  6262.                 ->setParameter('filterIds', $oldProductAttributeOptionFilters)
  6263.                 ->groupBy('p.id')
  6264.                 ->having('COUNT(DISTINCT ao.id) = :filterCount')
  6265.                 ->setParameter('filterCount', count($oldProductAttributeOptionFilters));
  6266.         }
  6267.         if ($onlyIds) {
  6268.             $result = $result->select('p.id');
  6269.             return $result->getQuery()->getSingleColumnResult();
  6270.         }
  6271.         return $result->getQuery()->getResult();
  6272.     }
  6273.     /**
  6274.      * @param int $modelId
  6275.      * @param array $attributeOptionIds
  6276.      * @return array
  6277.      */
  6278.     public function findProductsByModelIdsAndAttributeOptionIds(int $modelId, array $attributeOptionIds): array
  6279.     {
  6280.         return $this->createQueryBuilder('p')
  6281.             ->innerJoin('p.fixedAttributeOptionCombination', 'fac')
  6282.             ->innerJoin('fac.attributeOptions', 'ao')
  6283.             ->where('ao.id IN (:attributeOptionIds)')
  6284.             ->andWhere('p.productModel = :productModelId')
  6285.             ->groupBy('p.id')
  6286.             ->having('COUNT(DISTINCT ao.id) = :count')
  6287.             ->setParameter('attributeOptionIds', $attributeOptionIds)
  6288.             ->setParameter('productModelId', $modelId)
  6289.             ->setParameter('count', count($attributeOptionIds))
  6290.             ->getQuery()->getResult();
  6291.     }
  6292.     /**
  6293.      * @param int $type
  6294.      * @return iterable
  6295.      */
  6296.     public function getProductsFroUnasExport(int $type): iterable
  6297.     {
  6298.         $qb = $this->createQueryBuilder('p');
  6299.         $sub = $this->getEntityManager()->createQueryBuilder()
  6300.             ->select('1')
  6301.             ->from(ProductMeta::class, 'pm2')
  6302.             ->where('pm2.product = p')
  6303.             ->andWhere('pm2.domain = :domain')
  6304.             ->andWhere('pm2.active = 1')
  6305.             ->andWhere('pm2.orderable = 1')
  6306.             ->andWhere('pm2.price > 0');
  6307.         $qb->where('p.type = :type')
  6308.             ->andWhere('p.inStock > 0 AND p.available > 0')
  6309.             ->andWhere('p.archived = 0')
  6310.             ->andWhere('p.qualityStatus != :bazaarQualityStatus')
  6311.             ->andWhere($qb->expr()->exists($sub->getDQL()))
  6312.             ->orderBy('p.id', 'ASC')
  6313.             ->setParameters([
  6314.                 'bazaarQualityStatus' => Product::QualityStatus_Bazaar,
  6315.                 'domain' => Domain::DOMAIN_ID_RT_EU,
  6316.                 'type' => $type,
  6317.             ]);
  6318.         return $qb->getQuery()->toIterable([], Query::HYDRATE_OBJECT);
  6319.     }
  6320.     /**
  6321.      * @param ProductType|string|array $productType
  6322.      *
  6323.      * @return array
  6324.      */
  6325.     public function findByProductType(ProductType|string|array $productType): array
  6326.     {
  6327.         if ($productType === 'cable') {
  6328.             $queryBuilder = $this->createQueryBuilder('p')
  6329.                 ->innerJoin('p.type', 'pt');
  6330.             $queryBuilder->where(
  6331.                 $queryBuilder->expr()->like(
  6332.                     'pt.title',
  6333.                     $queryBuilder->expr()->literal('Cable%')
  6334.                 )
  6335.             );
  6336.             $queryBuilder->andWhere('p.available > 0');
  6337.             $queryBuilder->andWhere('p.scannable = 0');
  6338.             $query = $queryBuilder->setFirstResult(0)->getQuery();
  6339.             return $query->getResult();
  6340.         } elseif ($productType === 'packing-material') {
  6341.             $queryBuilder = $this->createQueryBuilder('p');
  6342.             $queryBuilder->andWhere('p.type = 211');
  6343.             $queryBuilder->andWhere('p.available > 0');
  6344.             $query = $queryBuilder->setFirstResult(0)->getQuery();
  6345.             return $query->getResult();
  6346.         } elseif ($productType === 'cable-material') {
  6347.             $queryBuilder = $this->createQueryBuilder('p');
  6348.             $queryBuilder->andWhere('p.id IN ( 68732, 2285, 2485, 12637, 12638, 26954, 64928, 17834, 2486, 2488, 2697 )');
  6349.             $queryBuilder->andWhere('p.available > 0');
  6350.             $query = $queryBuilder->setFirstResult(0)->getQuery();
  6351.             return $query->getResult();
  6352.         } elseif ($productType === 'other-material') {
  6353.             $queryBuilder = $this->createQueryBuilder('p');
  6354.             $queryBuilder->andWhere('p.id IN ( 191, 2656 )');
  6355.             $queryBuilder->andWhere('p.available > 0');
  6356.             $query = $queryBuilder->setFirstResult(0)->getQuery();
  6357.             return $query->getResult();
  6358.         }
  6359.         return $this->findBy([
  6360.             'type' => $productType
  6361.         ]);
  6362.     }
  6363.     public function getLastInMovementShipmentForStockItems(array $stockItems): array
  6364.     {
  6365.         if (empty($stockItems)) {
  6366.             return [];
  6367.         }
  6368.         $ids = array_map(fn($si) => $si->getId(), $stockItems);
  6369.         $sql = '
  6370.             SELECT ranked.si_id, ranked.shipment_code, ranked.s_shipment_id
  6371.             FROM (
  6372.                 SELECT
  6373.                     smisi.stockitem_id            AS si_id,
  6374.                     sm.shipment_code              AS shipment_code,
  6375.                     s.shipment_id                 AS s_shipment_id,
  6376.                     ROW_NUMBER() OVER (PARTITION BY smisi.stockitem_id ORDER BY sm.created DESC) AS rn
  6377.                 FROM stock_movement_items_stock_items smisi
  6378.                 INNER JOIN stock_movement_item smi ON smi.id = smisi.stockmovementitem_id
  6379.                 INNER JOIN stock_movements sm ON sm.id = smi.stock_movement_id
  6380.                 LEFT JOIN shipments s ON s.id = sm.shipment_id
  6381.                 WHERE smisi.stockitem_id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')
  6382.                   AND sm.type = 1
  6383.                   AND (sm.shipment_id IS NOT NULL OR sm.shipment_code IS NOT NULL)
  6384.             ) ranked
  6385.             WHERE ranked.rn = 1
  6386.         ';
  6387.         $rows = $this->getEntityManager()
  6388.             ->getConnection()
  6389.             ->executeQuery($sql, $ids)
  6390.             ->fetchAllAssociative();
  6391.         $result = [];
  6392.         foreach ($rows as $row) {
  6393.             $result[(int)$row['si_id']] = [
  6394.                 'shipmentId'   => $row['s_shipment_id'],
  6395.                 'shipmentCode' => $row['shipment_code'],
  6396.             ];
  6397.         }
  6398.         return $result;
  6399.     }
  6400. }