src/Repository/StoreRepository.php line 126

Open in your IDE?
  1. <?php
  2. namespace App\Repository;
  3. use App\Entity\Enum\BusinessModelEnum;
  4. use App\Entity\Freelancer;
  5. use App\Entity\HearingBrand;
  6. use App\Entity\Partner;
  7. use App\Entity\Store;
  8. use App\Entity\User;
  9. use DateTime;
  10. use Doctrine\ORM\QueryBuilder;
  11. use Doctrine\Persistence\ManagerRegistry;
  12. use InvalidArgumentException;
  13. use Symfony\Bridge\Doctrine\RegistryInterface;
  14. /**
  15.  * @method Store|null find($id, $lockMode = null, $lockVersion = null)
  16.  * @method Store|null findOneBy(array $criteria, array $orderBy = null)
  17.  * @method Store[]    findAll()
  18.  * @method Store[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
  19.  */
  20. class StoreRepository extends AbstractEntityRepository
  21. {
  22.     public function __construct(ManagerRegistry $registry)
  23.     {
  24.         parent::__construct($registryStore::class);
  25.     }
  26.     /**
  27.      * Return stores managed by the user with estimatedReach
  28.      * @param User $user
  29.      * @return mixed
  30.      */
  31.     public function findStoresOfUserWithEstimatedReach(User $user)
  32.     {
  33.         $qb $this->findQBStoreOfUser($user);
  34.         $qb->leftJoin('s.estimatedReach''er');
  35.         $qb->addSelect('er');
  36.         return $qb->getQuery()->getResult();
  37.     }
  38.     /**
  39.      * Return QB of the store managed by the user
  40.      * @param User $user
  41.      * @return \Doctrine\ORM\QueryBuilder
  42.      */
  43.     public function findQBStoreOfUser(User $user)
  44.     {
  45.         return $this->createQueryBuilder('s')
  46.             ->join('s.administrator''ad')
  47.             ->andWhere('ad.user = :user')
  48.             ->setParameter('user'$user);
  49.     }
  50.     /**
  51.      * Return nb of stores disabled
  52.      * @param User $user
  53.      * @return mixed
  54.      * @throws \Doctrine\ORM\NoResultException
  55.      * @throws \Doctrine\ORM\NonUniqueResultException
  56.      */
  57.     public function findNbOfDisabledStores(User $user)
  58.     {
  59.         $qb $this->findQBStoreOfUser($user);
  60.         $qb
  61.             ->select('count(s)')
  62.             ->andWhere('s.acquisitionEnabled = false')
  63.             ->andWhere('s.deletedAt is not null')
  64.             ->andWhere('s.state = :valid')
  65.             ->setParameter('valid'Store::STATE_VALID);
  66.         return $qb->getQuery()->getSingleScalarResult();
  67.     }
  68.     /**
  69.      * Return stores not associated to a Admin
  70.      * @return mixed
  71.      */
  72.     public function findQBStore()
  73.     {
  74.         $qb $this->createQueryBuilder('s');
  75.         return $qb;
  76.     }
  77.     /**
  78.      * Return store around specific given point
  79.      *
  80.      * @param $latitude
  81.      * @param $longitude
  82.      * @param $distance
  83.      * @return mixed
  84.      * @throws \Doctrine\ORM\NonUniqueResultException
  85.      */
  86.     public function findNearestStoreAroundLocation($latitude$longitude$distance)
  87.     {
  88.         $qb $this->createQueryBuilder('s');
  89.         $qb $this->addPositionCriteria($qb$latitude$longitude$distance1);
  90.         return $qb->getQuery()->getOneOrNullResult();
  91.     }
  92.     /**
  93.      *
  94.      * Trouver les stores actifs présentables aux utilisateurs selon le rayon $distance
  95.      *
  96.      * @param $latitude
  97.      * @param $longitude
  98.      * @param $distance
  99.      * @return mixed
  100.      */
  101.     public function findActiveStoresAroundLocation($latitude$longitude$distance)
  102.     {
  103.         // only valid stores
  104.         $qb $this->createQueryBuilder('s')
  105.             ->andWhere('s.state = :state')
  106.             ->andWhere('s.deletedAt is NULL')
  107.             ->setParameters(['state' => 'valid'])
  108.             ->leftJoin('s.hearingBrand''hb')->addSelect('hb')
  109.             ->leftJoin('s.administrator''ad')->addSelect('ad')
  110.             ->leftJoin('s.storeCentralPurchasingRelations''scpr')->addSelect('scpr');
  111.         $qb $this->addPositionCriteria($qb$latitude$longitude$distance);
  112.         $this->addStoreIndexationCriteria($qb's''ad');
  113.         return $qb->getQuery()->getResult();
  114.     }
  115.     private function addStoreIndexationCriteria(QueryBuilder $qbstring $storeAlias 'S'string $adminAlias 'A')
  116.     {
  117.         $qb->leftJoin($storeAlias '.storeIndexations''si''WITH''si.startAt <= :now and si.endAt >= :now')->setParameter('now', new \DateTime());
  118.         $qb->andWhere('( (' $adminAlias ' instance of App\Entity\Partner and ' $adminAlias '.isCustomer = true' ' ) or (si.id is not null) or ' $adminAlias '.businessModelDeadline is not null )');
  119.     }
  120.     /**
  121.      * Return store around a specific given point for hearingBrand
  122.      *
  123.      * @return mixed
  124.      */
  125.     public function findStoreAroundLocationForHearingBrand($latitude$longitude$distanceHearingBrand $hearingBrand)
  126.     {
  127.         $qb $this->createQueryBuilder('s')
  128.             ->join('s.hearingBrand''ad')
  129.             ->andWhere('ad.id = :hearingBrand')
  130.             ->setParameter('hearingBrand'$hearingBrand->getId());
  131.         $qb $this->addPositionCriteria($qb$latitude$longitude$distance1);
  132.         return $qb->getQuery()->getOneOrNullResult();
  133.     }
  134.     /**
  135.      * Return uniq store around specific given point for all partner
  136.      * @return mixed
  137.      */
  138.     public function findStoreAroundLocationExceptPartner($latitude$longitude$distance)
  139.     {
  140.         $qb $this->createQueryBuilder('s')
  141.             ->where('s.administrator IS NULL');
  142.         //->where('ad NOT INSTANCE OF :class' )
  143.         //->setParameter('class',  $this->getEntityManager()->getClassMetadata('App\Entity\Partner'));
  144.         //->setParameter('partner', $partner->getId());
  145.         $qb $this->addPositionCriteria($qb$latitude$longitude$distance);
  146.         return $qb;
  147.     }
  148.     public function findPreferredStore($latitude$longitude$distance)
  149.     {
  150.     }
  151.     /**
  152.      * Return uniq store around specific given point for customer MCA
  153.      * @return mixed
  154.      */
  155.     public function findNoCustomerStoreAroundLocation($latitude$longitude$distance)
  156.     {
  157.         $qb $this->createQueryBuilder('s')
  158.             ->join('App\Entity\Administrator''a')
  159.             ->where('a.isCustomer = false');
  160.         $qb $this->addPositionCriteria($qb$latitude$longitude$distance);
  161.         return $qb;
  162.     }
  163.     /**
  164.      * Return uniq store around specific given point for all partner
  165.      * @return mixed
  166.      */
  167.     public function findStoreAroundLocationForPartner($latitude$longitude$distance)
  168.     {
  169.         $qb $this->createQueryBuilder('s')
  170.             ->join('s.administrator''ad')
  171.             ->where('ad.isCustomer = true');
  172. //            ->setParameter('class',  $this->getEntityManager()->getClassMetadata('App\Entity\Partner'));
  173.         //->setParameter('partner', $partner->getId());
  174.         $qb $this->addPositionCriteria($qb$latitude$longitude$distance);
  175.         return $qb;
  176.     }
  177.     /**
  178.      * Return stores not associated to a Admin around a given point
  179.      * //TODO add geoloc query
  180.      * @return mixed
  181.      */
  182.     public function findStoreWithoutAdminAroundLocation($latitude$longitude)
  183.     {
  184.         $qb $this->findQBStoreWithoutAdmin();
  185.         $qb $this->addPositionCriteria($qb$latitude$longitude);
  186.         $qb
  187.             ->andWhere('s.administrator is null')
  188.             ->setMaxResults(20);;
  189.         return $qb->getQuery()->getResult();
  190.     }
  191.     /**
  192.      * Return stores not associated to a Admin
  193.      * @return mixed
  194.      */
  195.     public function findQBStoreWithoutAdmin()
  196.     {
  197.         $qb $this->createQueryBuilder('s')
  198.             ->andWhere('s.administrator is null')
  199.             ->setMaxResults(20);;
  200.         return $qb;
  201.     }
  202.     /**
  203.      * Selectionner les centres éligibles à la livraison de prospect (algorithme de tri : SAW)
  204.      * Conditions :
  205.      * - ratio capping non atteint
  206.      * - est client MCA
  207.      * - est en acquisition enabled
  208.      * - pas de soft delete
  209.      * - est dans le reach par défaut (distanceMAX) et le reach imposé pour le centre (REACH)
  210.      * - (si geocoding est égal sur les 2 points, DISTANCE vaut null)
  211.      *
  212.      * @param $latitude
  213.      * @param $longitude
  214.      * @param int $distanceMax
  215.      * @param bool $isDealWithCapping
  216.      * @param bool $isDealWithFreelancer
  217.      * @param array $excludedAdmin array of admins to exclude
  218.      * @return mixed
  219.      */
  220.     public function findStoreBySawAnalysis(
  221.         $latitude,
  222.         $longitude,
  223.         $distanceMax Store::AREA_SEARCH,
  224.         $isDealWithCapping true,
  225.         $isDealWithFreelancer true,
  226.         array $excludedAdmin = [],
  227.         $includeBusinessModelPerformance true,
  228.         int $ageLimit null
  229.     )
  230.     {
  231.         $qb $this->createQueryBuilder('S')
  232.             ->addSelect(self::earthRadius ' * acos(cos(radians(:latitude)) * cos(radians(S.latitude)) * cos(radians(S.longitude) - radians(:longitude)) + sin(radians(:latitude)) * sin(radians(S.latitude))) as DISTANCE')
  233.             ->addSelect('A.monthlyCapping AS CAPPINGLIMIT')
  234.             ->addSelect('A.leadCost AS LEADCOST')
  235.             ->addSelect('A.numberOfLeadMonthly AS NBLEAD')
  236.             ->join('S.administrator''A')
  237.             ->leftJoin('S.storeCentralPurchasingRelations''scpr')
  238.             ->leftJoin('scpr.centralPurchasing''cp')
  239.             ->andWhere('A.isCustomer = true or (scpr.enabled = true and cp.nbCredit > 0) or (A.businessModel = :model_perf)')->setParameter('model_perf'BusinessModelEnum::PERFORMANCE)
  240.             ->andWhere('S.acquisitionEnabled = true');
  241.         $this->addStoreIndexationCriteria($qb'S''A');
  242.         // Exclure les freelancer de la recherche
  243.         if (!$isDealWithFreelancer) {
  244.             $qb->andWhere('A NOT INSTANCE OF ' Freelancer::class);
  245.         }
  246.         $qb
  247.             ->groupBy('S')
  248.             ->having('DISTANCE <= :distanceMax and (DISTANCE <= S.reach
  249.                                   OR S.reach is null)
  250.                                   OR DISTANCE is null')
  251.             ->orderBy('DISTANCE');
  252.         if ($isDealWithCapping) {
  253.             $qb->andHaving('CAPPINGLIMIT is null
  254.                                   OR NBLEAD is null
  255.                                   OR NBLEAD < CAPPINGLIMIT');
  256.         }
  257.         if (!empty($excludedAdmin)) {
  258.             $qb->andWhere('A NOT IN (:admins) ')->setParameter('admins'$excludedAdmin);
  259.         }
  260.         if (!$includeBusinessModelPerformance) {
  261.             $qb
  262.                 ->andWhere('A.businessModel is null or A.businessModel != :model_perf')
  263.                 ->setParameter('model_perf'BusinessModelEnum::PERFORMANCE);
  264.         }
  265.         if ($ageLimit !== null) {
  266.             $qb
  267.                 ->andWhere('A.ageCapping is null or A.ageCapping <= :ageLimit')
  268.                 ->setParameter('ageLimit'$ageLimit);
  269.         }
  270.         $qb->setParameter('latitude'$latitude);
  271.         $qb->setParameter('longitude'$longitude);
  272.         $qb->setParameter('distanceMax'$distanceMax);
  273.         $results $qb->getQuery()->getResult();
  274.         return $results;
  275.     }
  276.     /**
  277.      * Selectionner les centres éligibles à la livraison de prospect sans capping (algorithme de tri : SAW)
  278.      * Conditions :
  279.      * - est client MCA
  280.      * - est en acquisition enabled
  281.      * - pas de soft delete
  282.      * - est dans le reach par défaut (distanceMAX) et le reach imposé pour le centre (REACH)
  283.      * - (si geocoding est égal sur les 2 points, DISTANCE vaut null)
  284.      *
  285.      * @param $latitude
  286.      * @param $longitude
  287.      * @param int $distanceMax
  288.      * @return mixed
  289.      * @deprecated
  290.      */
  291.     public function findStoreBySawAnalysisWithoutCapping($latitude$longitude$distanceMax Store::AREA_SEARCH)
  292.     {
  293.         $em $this->getEntityManager();
  294.         // OR S.reach is null permet de selectionner tous les stores sans reach
  295.         /*
  296.         $query = $em->createQuery('SELECT S, (' . self::earthRadius . ' * acos(cos(radians(:latitude)) * cos(radians(S.latitude)) * cos(radians(S.longitude) - radians(:longitude)) + sin(radians(:latitude)) * sin(radians(S.latitude)))) AS DISTANCE,
  297.                                   A.monthlyCapping AS CAPPINGLIMIT, A.leadCost AS LEADCOST,
  298.                                   A.numberOfLeadMonthly AS NBLEAD
  299.                                   FROM App\Entity\Store S
  300.                                   INNER JOIN App\Entity\Administrator A WITH A.id = S.administrator
  301.                                   LEFT JOIN App\Entity\CentralPurchasing CP WITH CP MEMBER OF S.centralPurchasingBodies
  302.                                   WHERE (A.isCustomer = true or CP.nbCredit > 0)
  303.                                   AND S.acquisitionEnabled = true
  304.                                   AND S.deletedAt IS NULL
  305.                                   GROUP BY S
  306.                                   HAVING (DISTANCE <= :distanceMax
  307.                                   AND (DISTANCE <= S.reach
  308.                                   OR S.reach is null)
  309.                                   OR DISTANCE is null)
  310.                                   ORDER BY DISTANCE
  311.         ')->setParameters([
  312.             'latitude' => $latitude,
  313.             'longitude' => $longitude,
  314.             'distanceMax' => $distanceMax,
  315.         ]);
  316.         $results = $query->getResult();
  317.         */
  318.         $qb $this->createQueryBuilder('S')
  319.             ->addSelect(self::earthRadius ' * acos(cos(radians(:latitude)) * cos(radians(S.latitude)) * cos(radians(S.longitude) - radians(:longitude)) + sin(radians(:latitude)) * sin(radians(S.latitude))) as DISTANCE')
  320.             ->addSelect('A.monthlyCapping AS CAPPINGLIMIT')
  321.             ->addSelect('A.leadCost AS LEADCOST')
  322.             ->addSelect('A.numberOfLeadMonthly AS NBLEAD')
  323.             ->join('S.administrator''A')
  324.             ->leftJoin('S.centralPurchasingBodies''CP')
  325.             ->andWhere('A.isCustomer = true or CP.nbCredit > 0')
  326.             ->andWhere('S.acquisitionEnabled = true')
  327.             ->groupBy('S')
  328.             ->having('DISTANCE <= :distanceMax and (DISTANCE <= S.reach
  329.                                   OR S.reach is null)
  330.                                   OR DISTANCE is null')
  331.             ->orderBy('DISTANCE');
  332.         $qb->setParameters([
  333.             'latitude' => $latitude,
  334.             'longitude' => $longitude,
  335.             'distanceMax' => $distanceMax,
  336.         ]);
  337.         $results $qb->getQuery()->getResult();
  338.         return $results;
  339.     }
  340.     /**
  341.      * Return all store order by estimated reach computation date
  342.      * @return mixed
  343.      */
  344.     public function findStoreByDateTo()
  345.     {
  346.         $qb $this->createQueryBuilder('s');
  347.         $qb
  348.             ->leftJoin('s.estimatedReach''er')
  349.             ->orderBy('er.id''desc')
  350.             ->addOrderBy('er.dateTo''desc');;
  351.         return $qb->getQuery()->getResult();
  352.     }
  353.     /**
  354.      * MCA-784: Force delivery - Find stores for specific administrator IDs
  355.      * Ignores business rules (credits, capping, isCustomer, acquisitionEnabled)
  356.      * Only filters by distance and administrator ID
  357.      *
  358.      * @param array $administratorIds IDs des administrateurs ciblés
  359.      * @param float $latitude Latitude du prospect
  360.      * @param float $longitude Longitude du prospect
  361.      * @param int $distanceMax Distance maximale en km
  362.      * @return array|null Array de stores avec distances ou null si aucun résultat
  363.      */
  364.     public function findStoresByAdministratorIds(
  365.         array $administratorIds,
  366.         $latitude,
  367.         $longitude,
  368.         $distanceMax Store::AREA_SEARCH
  369.     )
  370.     {
  371.         $qb $this->createQueryBuilder('S')
  372.             ->select('S''A')
  373.             ->innerJoin('S.administrator''A')
  374.             ->addSelect(self::earthRadius ' * acos(cos(radians(:latitude)) * cos(radians(S.latitude)) *
  375.                          cos(radians(S.longitude) - radians(:longitude)) +
  376.                          sin(radians(:latitude)) * sin(radians(S.latitude))) as DISTANCE')
  377.             ->where('A.id IN (:adminIds)')
  378.             ->andWhere('S.deletedAt IS NULL')
  379.             ->setParameter('adminIds'$administratorIds)
  380.             ->setParameter('latitude'$latitude)
  381.             ->setParameter('longitude'$longitude)
  382.             ->groupBy('S.id')
  383.             ->having('DISTANCE <= :distanceMax')
  384.             ->setParameter('distanceMax'$distanceMax)
  385.             ->orderBy('DISTANCE''ASC');
  386.         return $qb->getQuery()->getResult();
  387.     }
  388.     /**
  389.      * @param string $phoneNumber
  390.      * @param string $email
  391.      * @return mixed
  392.      * @deprecated use findPosLinkedByProspectEmailOrTel
  393.      * Return all store requested by prospect with email or tel
  394.      * - for partner, only store created in the last 6 months
  395.      * - for freelancer, all stores
  396.      */
  397.     public function findStoreLinkedToProspectWithEmailOrTel(string $phoneNumberstring $email)
  398.     {
  399.         $dateTreshold = new DateTime();
  400.         $dateTreshold->modify('-6 month');
  401.         $qb $this->createQueryBuilder('s');
  402.         $qb
  403.             ->join('s.prospectsOnStore''pos')
  404.             ->join('pos.prospect''p')
  405.             ->innerJoin('s.administrator''ad')
  406.             ->andWhere('p.email = :email or p.phoneNumber = :phone')
  407.             ->andWhere('( (ad INSTANCE OF ' Partner::class . ' and pos.created > :date) OR ad INSTANCE OF ' Freelancer::class . ')')->setParameter('date'$dateTreshold)
  408.             ->setParameter('email'$email)
  409.             ->setParameter('phone'$phoneNumber)
  410.             ->orderBy('p.created''desc');
  411.         return $qb->getQuery()->getResult();
  412.     }
  413.     /**
  414.      * Return stores associated with active freelancer (having an account)
  415.      * @return QueryBuilder
  416.      */
  417.     public function findQBStoresOfFreelancers(): QueryBuilder
  418.     {
  419.         $qb $this->createQueryBuilder('s');
  420.         $qb
  421.             ->innerJoin('s.administrator''a')
  422.             ->andWhere('a INSTANCE OF ' Freelancer::class)
  423.             ->orderBy('s.id''asc');;
  424.         return $qb;
  425.     }
  426.     /**
  427.      * Return stores for sitemap
  428.      * @return mixed
  429.      */
  430.     public function findStoresForSitemap()
  431.     {
  432.         $qb $this->createQueryBuilder('s');
  433.         $qb
  434.             ->andWhere('s.slug is not null')
  435.             ->andWhere('s.storePage is not null')
  436.             ->orderBy('s.id''desc');
  437.         return $qb->getQuery()->getResult();
  438.     }
  439.     /**
  440.      * Find store with Store Page
  441.      * @return array
  442.      */
  443.     public function findStoresWithStorePage(): array
  444.     {
  445.         return $this->createQueryBuilder('s')
  446.             ->where('s.storePage IS NOT NULL')
  447.             ->orderBy('s.zipCode''ASC')
  448.             ->getQuery()
  449.             ->getResult();
  450.     }
  451.     /**
  452.      * Find store by departments code.
  453.      *
  454.      * @param string $departmentCode department code (13, 75, etc...)
  455.      * @param int|null $limit Number of store to search.
  456.      * @return array        list of stores
  457.      */
  458.     public function findStoreByDepartment(string $departmentCode, ?int $limit null): array
  459.     {
  460.         $qb $this->createQueryBuilder('s');
  461.         $qb->andWhere('SUBSTRING(s.zipCode, 1, 2)  = :department')
  462.             ->setParameter('department'$departmentCode);
  463.         if ($limit !== null) {
  464.             $qb->setMaxResults($limit);
  465.         }
  466.         return $qb
  467.             ->getQuery()
  468.             ->getResult();
  469.     }
  470.     /**
  471.      * Find store by departments code.
  472.      *
  473.      * @param array $departments list of departments code (13, 75, etc...)
  474.      * @param int|null $limit Number of store to search.
  475.      * @return array        list of stores
  476.      */
  477.     public function findStoreByCity(array $departments, ?int $limit null): array
  478.     {
  479.         $qb $this->createQueryBuilder('s');
  480.         $qb->andWhere('SUBSTRING(s.zipCode, 1, 2) IN (:departments)')
  481.             ->setParameter('departments'$departments);
  482.         $qb->groupBy('s.city');
  483.         if ($limit !== null) {
  484.             $qb->setMaxResults($limit);
  485.         }
  486.         return $qb
  487.             ->getQuery()
  488.             ->getResult();
  489.     }
  490.     /**
  491.      * add criteria to limit to 'active' store:
  492.      *  - store must have an admin
  493.      *  - store must have 'acquisition enabled' (admin can disable acquisition on store basis)
  494.      *  - if admin is a partner, this partner must be enabled (manually)
  495.      *  - if admin is a freelancer, he must have credit
  496.      * @param QueryBuilder $qb
  497.      * @param string $storeAlias
  498.      */
  499.     private function addEnabledStoresCriteria(QueryBuilder $qbstring $storeAlias 's')
  500.     {
  501.         $qb->andWhere($storeAlias '.acquisitionEnabled = true');
  502.         $qb->innerJoin($storeAlias '.administrator''a');
  503.         $qb->andWhere('a.isCustomer = true');
  504.     }
  505.     /**
  506.      * add criteria to exlude stores out of their reach
  507.      * MUST be used with  'addPositionCriteria' to specify the origin of the distance
  508.      * @param QueryBuilder $qb
  509.      * @param string $storeAlias
  510.      */
  511.     private function addStoreReachCriteria(QueryBuilder $qbstring $storeAlias 's')
  512.     {
  513.         $qb->andHaving($storeAlias '.reach > distance or ' $storeAlias '.reach is null');
  514.     }
  515. }