Covivo/mobicoop

View on GitHub
api/src/Solidary/Repository/SolidaryUserRepository.php

Summary

Maintainability
D
2 days
Test Coverage
<?php
/**
 * Copyright (c) 2020, MOBICOOP. All rights reserved.
 * This project is dual licensed under AGPL and proprietary licence.
 ***************************
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <gnu.org/licenses>.
 ***************************
 *    Licence MOBICOOP described in the file
 *    LICENSE
 */

namespace App\Solidary\Repository;

use App\Carpool\Entity\Criteria;
use App\Solidary\Entity\SolidarySearch;
use App\Solidary\Entity\SolidaryUser;
use App\Solidary\Exception\SolidaryException;
use App\Solidary\Service\SolidaryMatcher;
use App\User\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;

/**
 * @author Maxime Bardot <maxime.bardot@mobicoop.org>
 */
class SolidaryUserRepository
{
    public const USE_DAY_RESTRICTION = true; // Restriction by the time range slot by slot (m,a,e) of a SolidaryVolunteer in matching
    public const USE_TIME_RESTRICTION = true; // Restriction by the time range slot by slot (m,a,e) of a SolidaryVolunteer in matching
    public const USE_GEOGRAPHIC_RESTRICTION = true; // Restriction by the maxDistance of a SolidaryVolunteer in matching

    /**
     * @var EntityRepository
     */
    private $repository;

    private $entityManager;
    private $solidaryMatcher;

    public function __construct(EntityManagerInterface $entityManager, SolidaryMatcher $solidaryMatcher)
    {
        $this->entityManager = $entityManager;
        $this->repository = $entityManager->getRepository(SolidaryUser::class);
        $this->solidaryMatcher = $solidaryMatcher;
    }

    public function find(int $id): ?SolidaryUser
    {
        return $this->repository->find($id);
    }

    public function findAll(): ?array
    {
        return $this->repository->findAll();
    }

    public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): ?array
    {
        return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
    }

    public function findOneBy(array $criteria): ?SolidaryUser
    {
        return $this->repository->findOneBy($criteria);
    }

    /**
     * Get a SolidaryUser by its User id.
     *
     * @param int $id The user id
     *
     * @return null|SolidaryUser The SolidaryUser if found, null if not found
     */
    public function findByUserId(int $id)
    {
        $query = $this->repository->createQueryBuilder('su')
            ->join('su.user', 'u')
            ->where('u.id = :id')
            ->setParameter('id', $id)
        ;

        return $query->getQuery()->getOneOrNullResult();
    }

    /**
     * Get a SolidaryUser by its email.
     *
     * @return null|SolidaryUser
     */
    public function findByEmail(string $email)
    {
        $query = $this->repository->createQueryBuilder('v')
            ->join('v.user', 'u')
            ->where('u.email = :email')
            ->setParameter('email', $email)
        ;

        return $query->getQuery()->getResult();
    }

    /**
     * Find the matching SolidaryUser for a Solidary Transport Search.
     *
     * @return null|array
     */
    public function findForASolidaryTransportSearch(SolidarySearch $solidaryTransportSearch): array
    {
        // Get the criteria of the beneficiary's proposal
        $criteria = $solidaryTransportSearch->getSolidary()->getProposal()->getCriteria();
        $parameters = [];

        // Only the volunteer
        $query = $this->repository->createQueryBuilder('su')
            ->join('su.address', 'a')
            ->innerJoin('su.user', 'u', 'WITH', 'u.status != :pseudonymizedStatus')
            ->where('su.volunteer = 1')
        ;

        $parameters['pseudonymizedStatus'] = User::STATUS_PSEUDONYMIZED;

        // Get the Structure
        $structure = $solidaryTransportSearch->getSolidary()->getSolidaryUserStructure()->getStructure();

        if (Criteria::FREQUENCY_PUNCTUAL == $criteria->getFrequency()) {
            // Punctual journey

            // Date

            // Get the slot of the MinTime and MaxTime for the current structure
            $slot = $this->solidaryMatcher->getHourSlot($criteria->getMinTime(), $criteria->getMaxTime(), $structure);

            // We need to determine the weekday of the fromDate and take only the volunteer available that day on the slot
            if (self::USE_DAY_RESTRICTION) {
                $weekDay = $criteria->getFromDate()->format('w');

                switch ($weekDay) {
                    case 0:$query->andWhere('su.'.$slot.'Sun = 1');

                        break;

                    case 1: $query->andWhere('su.'.$slot.'Mon = 1');

                        break;

                    case 2: $query->andWhere('su.'.$slot.'Tue = 1');

                        break;

                    case 3: $query->andWhere('su.'.$slot.'Wed = 1');

                        break;

                    case 4: $query->andWhere('su.'.$slot.'Thu = 1');

                        break;

                    case 5: $query->andWhere('su.'.$slot.'Fri = 1');

                        break;

                    case 6: $query->andWhere('su.'.$slot.'Sat = 1');

                        break;
                }
            }
            if (self::USE_TIME_RESTRICTION) {
                $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getMinTime()->format('H:i:s').'\'');
                $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getMaxTime()->format('H:i:s').'\'');
            }
        } else {
            // Regular journey

            // If the SolidaryUser can drive on the particular days of the critera
            // We look also if the SolidaryUser has set a maching min and max time for the maching hour slot
            if ($criteria->isMonCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getMonMinTime(), $criteria->getMonMaxTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Mon = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getMonMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getMonMaxTime()->format('H:i:s').'\'');
                }
            }
            if ($criteria->isTueCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getTueMinTime(), $criteria->getTueMaxTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Tue = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getTueMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getTueMaxTime()->format('H:i:s').'\'');
                }
            }
            if ($criteria->isWedCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getWedMinTime(), $criteria->getWedMaxTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Wed = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getWedMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getWedMaxTime()->format('H:i:s').'\'');
                }
            }
            if ($criteria->isThuCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getThuMinTime(), $criteria->getThuMaxTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Thu = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getThuMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getThuMaxTime()->format('H:i:s').'\'');
                }
            }
            if ($criteria->isFriCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getFriMinTime(), $criteria->getFriMinTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Fri = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getFriMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getFriMaxTime()->format('H:i:s').'\'');
                }
            }
            if ($criteria->isSatCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getSatMinTime(), $criteria->getSatMaxTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Sat = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getSatMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getSatMaxTime()->format('H:i:s').'\'');
                }
            }
            if ($criteria->isSunCheck()) {
                $slot = $this->solidaryMatcher->getHourSlot($criteria->getSunMinTime(), $criteria->getSunMaxTime(), $structure);
                if (self::USE_DAY_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'Sun = 1');
                }
                if (self::USE_TIME_RESTRICTION) {
                    $query->andWhere('su.'.$slot.'MinTime <= \''.$criteria->getSunMinTime()->format('H:i:s').'\'');
                    $query->andWhere('su.'.$slot.'MaxTime >= \''.$criteria->getSunMaxTime()->format('H:i:s').'\'');
                }
            }
        }// end if punctual/regular

        // Geographic criteria
        if (self::USE_GEOGRAPHIC_RESTRICTION) {
            // The origin of the Proposal used in this search must be under the maxDistance of the SolidaryUser volunteer
            // If the search is on the return, we use the destination instead
            $waypoints = $solidaryTransportSearch->getSolidary()->getProposal()->getWaypoints();
            $address = null;
            if ('outward' == $solidaryTransportSearch->getWay()) {
                $address = $waypoints[0]->getAddress();
            } else {
                foreach ($waypoints as $waypoint) {
                    if ($waypoint->isDestination()) {
                        $address = $waypoint->getAddress();
                    }
                }
            }
            if (is_null($address)) {
                throw new SolidaryException(SolidaryException::NO_VALID_ADDRESS);
            }
            $sqlDistance = '(6378000 * acos(cos(radians('.$address->getLatitude().')) * cos(radians(a.latitude)) * cos(radians(a.longitude) - radians('.$address->getLongitude().')) + sin(radians('.$address->getLatitude().')) * sin(radians(a.latitude))))';
            $query->andWhere($sqlDistance.' <= su.maxDistance');
        }

        $query->setParameters($parameters);

        $queryResults = $query->getQuery()->getResult();

        // We need to build and persist all the new results as SolidaryMatching.
        $solidaryMatchings = $this->solidaryMatcher->buildSolidaryMatchingsForTransport($solidaryTransportSearch->getSolidary(), $queryResults);

        // We build the array of SolidaryResult
        $results = [];
        foreach ($solidaryMatchings as $solidaryMatching) {
            $results[] = $this->solidaryMatcher->buildSolidaryResultTransport($solidaryMatching);
        }

        return $results;
    }

    /**
     * Find all solidary users.
     *
     * @param array $filters Optionnal Filters on SolidaryUser
     *
     * @return SolidaryUser[]
     */
    public function findSolidaryUsers(array $filters)
    {
        $parameters = [];

        $query = $this->repository->createQueryBuilder('su')
            ->join('su.user', 'u')
            ->andWhere('u.status != :pseudonymizedStatus')
        ;

        $parameters['pseudonymizedStatus'] = User::STATUS_PSEUDONYMIZED;

        // Filters
        if (!is_null($filters)) {
            foreach ($filters as $filter => $value) {
                if ('q' !== $filter) {
                    $query->andWhere('u.'.$filter." like '%".$value."%'");
                } else {
                    $query->andWhere("u.givenName like '%".$value."%' or u.familyName like '%".$value."%' or CONCAT(u.givenName,' ',u.familyName) like '%".$value."%'");
                }
            }
        }

        $query->setParameters($parameters);

        return $query->getQuery()->getResult();
    }
}