* * @method Assignment|null find($id, $lockMode = null, $lockVersion = null) * @method Assignment|null findOneBy(array $criteria, array $orderBy = null) * @method Assignment[] findAll() * @method Assignment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ class AssignmentRepository extends ServiceEntityRepository { use QueryHelperTrait; public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Assignment::class); } public function getListQuery(AssignmentFilterDto $filterDto, ?string $orderBy = null, ?array $hotelCodes = null): Query { $qb = $this->createQueryBuilder('assignment'); $qb ->select('assignment', 'destination', 'job_profile', 'application', 'disposition', 'teamer', 'user') ->addSelect('feedback') ->innerJoin('assignment.destination', 'destination') ->innerJoin('assignment.jobProfile', 'job_profile') ->where($qb->expr()->isNull('assignment.deletedAt')) ->leftJoin('assignment.applications', 'application', Join::WITH, $qb->expr()->neq('application.status', ':applicationStatus')) ->leftJoin('assignment.dispositions', 'disposition') ->leftJoin('disposition.teamer', 'teamer') ->leftJoin('teamer.user', 'user') ->leftJoin('teamer.feedback', 'feedback') ->setParameter('applicationStatus', Application::STATUS_REJECTED) ; if (null !== $orderBy) { $qb->orderBy($qb->expr()->asc($orderBy)); } $this->applyFilterSettings($filterDto, $qb); $this->applyHotelCodeRestriction($qb, $hotelCodes); return $qb->getQuery(); } public function getListQueryForTeamer(Teamer $teamer, AssignmentFilterDto $filterDto): Query { // Find ids of assignments with available slots first $availableAssignmentsIds = $this->getAvailableAssignmentsIds(); $qb = $this->createQueryBuilder('assignment'); $qb ->select('assignment', 'destination', 'job_profile', 'application', 'disposition') ->innerJoin('assignment.destination', 'destination') ->innerJoin('assignment.jobProfile', 'job_profile') ->where($qb->expr()->andX( $qb->expr()->orX( $qb->expr()->gt('destination.dateFrom', ':now'), $qb->expr()->andX( $qb->expr()->isNotNull('assignment.dateFrom'), $qb->expr()->gt('assignment.dateFrom', ':now') ) ), $qb->expr()->isNull('assignment.deletedAt'), $qb->expr()->notIn('assignment.status', ':status'), $qb->expr()->in('assignment.id', $availableAssignmentsIds) )) ->leftJoin('assignment.applications', 'application', Join::WITH, $qb->expr()->eq('application.teamer', ':teamer')) ->leftJoin('assignment.dispositions', 'disposition', Join::WITH, $qb->expr()->eq('disposition.teamer', ':teamer')) ->setParameter('teamer', $teamer) ->setParameter('status', [Assignment::STATUS_DRAFT, Assignment::STATUS_CALLED_OFF]) ->setParameter('now', new \DateTimeImmutable()) ; // limit result for teamers not having any trainings AND licences if (0 === $teamer->getTrainingAttendances()->count() && 0 === $teamer->getLicenses()->count()) { // Use NOT EXISTS subquery instead of JOIN to avoid duplicates requiring GROUP BY $subQb = $this->getEntityManager()->createQueryBuilder(); $subQb ->select('1') ->from(JobProfile::class, 'jp2') ->join('jp2.requiredTrainings', 'rt') ->where('jp2 = job_profile') ; $qb ->andWhere($qb->expr()->not($qb->expr()->exists($subQb->getDQL()))) ->andWhere($qb->expr()->eq('job_profile.requiredLicenses', ':requiredLicenses')) ->setParameter('requiredLicenses', '[]') ; } $this->applyFilterSettings($filterDto, $qb); return $qb->getQuery(); } private function applyFilterSettings(AssignmentFilterDto $filterDto, QueryBuilder $qb): void { if (null !== $filterDto->getId()) { $qb ->andWhere($qb->expr()->eq('assignment.id', ':id')) ->setParameter('id', $filterDto->getId()) ; } if (false === $filterDto->isIncludePast()) { $qb ->andWhere($qb->expr()->gte('destination.dateFrom', ':now')) ->setParameter('now', new \DateTimeImmutable()) ; } if (0 < count($filterDto->getStatus())) { $qb ->andWhere($qb->expr()->in('assignment.staffingStatus', ':status')) ->setParameter('status', $filterDto->getStatus()) ; } if (null !== $dateFrom = $filterDto->getDateFrom()) { $qb ->andWhere($qb->expr()->gte('destination.dateFrom', ':dateFrom')) ->setParameter('dateFrom', $dateFrom) ; } if (null !== $dateTo = $filterDto->getDateTo()) { $qb ->andWhere($qb->expr()->lte('destination.dateTo', ':dateTo')) ->setParameter('dateTo', $dateTo) ; } if ($jobProfiles = $filterDto->getJobProfiles()) { $qb ->andWhere($qb->expr()->in('assignment.jobProfile', ':jobProfiles')) ->setParameter('jobProfiles', $jobProfiles) ; } if (0 < count($filterDto->getHotels())) { $constraints = []; foreach ($filterDto->getHotels() as $index => $hotel) { $constraints[] = $qb->expr()->like('destination.hotel', ':hotel'.$index); } $qb->andWhere($qb->expr()->orX(...$constraints)); foreach ($filterDto->getHotels() as $index => $hotel) { $qb->setParameter('hotel'.$index, '%'.$hotel.'%'); } } if (null !== $duration = $filterDto->getDuration()) { $days = match ($duration) { AssignmentFilterDto::DURATION_WEEKEND => [2, 4], AssignmentFilterDto::DURATION_MID_WEEK => [5, 6], AssignmentFilterDto::DURATION_FULL_WEEK => [7, 7], default => [8, 0], }; [$minDays, $maxDays] = $days; $qb ->andWhere($qb->expr()->gte('DATEDIFF(destination.dateTo, destination.dateFrom)', ':minDays')) ->setParameter('minDays', $minDays) ; if (0 < $maxDays) { $qb ->andWhere($qb->expr()->lte('DATEDIFF(destination.dateTo, destination.dateFrom)', ':maxDays')) ->setParameter('maxDays', $maxDays) ; } } if (null !== $country = $filterDto->getCountry()) { $qb ->andWhere($qb->expr()->eq('destination.country', ':country')) ->setParameter('country', $country) ; } $teamerName = $filterDto->getTeamerName(); if (null !== $teamerName && '' !== trim($teamerName)) { $subQb = $this->getEntityManager()->createQueryBuilder(); $subQb ->select('1') ->from(Disposition::class, 'disposition_filter') ->innerJoin('disposition_filter.teamer', 'teamer_filter') ->innerJoin('teamer_filter.user', 'user_filter') ->where($subQb->expr()->andX( $subQb->expr()->eq('disposition_filter.assignment', 'assignment'), $subQb->expr()->neq('disposition_filter.status', ':dispositionCalledOffStatus'), $subQb->expr()->orX( $subQb->expr()->like('user_filter.firstName', ':teamerName'), $subQb->expr()->like('user_filter.lastName', ':teamerName') ) )) ; $qb ->andWhere($qb->expr()->exists($subQb->getDQL())) ->setParameter('dispositionCalledOffStatus', Disposition::STATUS_CALLED_OFF) ->setParameter('teamerName', '%'.$this->escapeLikeWildcards($teamerName).'%') ; } if (true === $filterDto->isNotSyncedWithBusProNet()) { $qb ->andWhere($qb->expr()->eq('disposition.syncedWithBusProNet', ':syncedWithBusProNet')) ->setParameter('syncedWithBusProNet', false) ; } } private function applyHotelCodeRestriction(QueryBuilder $qb, ?array $hotelCodes = null): void { if (null === $hotelCodes) { return; } if ([] === $hotelCodes) { $qb->andWhere('1 = 0'); return; } $qb ->andWhere($qb->expr()->orX( $qb->expr()->in($qb->expr()->substring('destination.hotelCode', 1, 3), ':hotelCodes'), $qb->expr()->in($qb->expr()->substring('destination.hotelCode', -3, 3), ':hotelCodes'), )) ->setParameter('hotelCodes', $hotelCodes) ; } public function getBookmarkQueryForTeamer(Teamer $teamer): Query { // Find ids of assignments with available slots first $availableAssignmentsIds = $this->getAvailableAssignmentsIds(); $qb = $this->createQueryBuilder('assignment'); $qb ->select('assignment', 'destination', 'job_profile', 'application', 'disposition') ->innerJoin('assignment.destination', 'destination') ->innerJoin('assignment.jobProfile', 'job_profile') ->innerJoin('assignment.teamers', 'teamer', Join::WITH, $qb->expr()->eq('teamer', ':teamer')) ->leftJoin('assignment.applications', 'application', Join::WITH, $qb->expr()->eq('application.teamer', ':teamer')) ->leftJoin('assignment.dispositions', 'disposition', Join::WITH, $qb->expr()->eq('disposition.teamer', ':teamer')) ->where($qb->expr()->andX( $qb->expr()->isNull('assignment.deletedAt'), $qb->expr()->in('assignment.id', $availableAssignmentsIds) )) ->setParameter('teamer', $teamer) ; return $qb->getQuery(); } public function getFilterOptions(): array { $options = []; // Date range $qb = $this->createQueryBuilder('assignment'); $result = $qb ->select('MIN(destination.dateFrom) minDate', 'MAX(destination.dateTo) maxDate') ->innerJoin('assignment.destination', 'destination') ->where($qb->expr()->isNull('assignment.deletedAt')) ->getQuery() ->getSingleResult() ; $options['minDate'] = new \DateTimeImmutable($result['minDate']); $options['maxDate'] = new \DateTimeImmutable($result['maxDate']); // Job-profiles $qb = $this->createQueryBuilder('assignment'); $result = $qb ->select('assignment', 'job_profile') ->innerJoin('assignment.jobProfile', 'job_profile') ->orderBy('job_profile.name', 'ASC') ->where($qb->expr()->isNull('assignment.deletedAt')) ->getQuery() ->getResult() ; $options['jobProfiles'] = array_map(function (Assignment $assignment) { return $assignment->getJobProfile(); }, $result); return $options; } public function getNew(int $limit = 5): array { $qb = $this->createQueryBuilder('assignment'); return $qb ->select('assignment', 'destination', 'job_profile', 'owner') ->innerJoin('assignment.destination', 'destination') ->innerJoin('assignment.jobProfile', 'job_profile') ->innerJoin('assignment.owner', 'owner') ->orderBy('assignment.createdAt', 'DESC') ->setMaxResults($limit) ->getQuery() ->getResult() ; } public function getNewMatchingTeamerProfile(Teamer $teamer, int $limit = 5): array { $availableAssignmentsIds = $this->getAvailableAssignmentsIds(); $qb = $this->createQueryBuilder('assignment'); $qb ->select('assignment', 'destination', 'job_profile') ->innerJoin('assignment.destination', 'destination') ->innerJoin('assignment.jobProfile', 'job_profile') ->leftJoin('job_profile.requiredTrainings', 'required_training') ->leftJoin('assignment.applications', 'application', Join::WITH, $qb->expr()->eq('application.teamer', ':teamer')) ->leftJoin('assignment.dispositions', 'disposition', Join::WITH, $qb->expr()->eq('disposition.teamer', ':teamer')) ->where($qb->expr()->andX( $qb->expr()->neq('assignment.staffingStatus', ':staffingStatus'), $qb->expr()->in('assignment.jobProfile', ':jobProfiles'), $qb->expr()->isNull('application'), $qb->expr()->isNull('disposition'), )) ->setParameter('staffingStatus', Assignment::STATUS_STAFFED) ->setParameter('jobProfiles', $teamer->getJobProfiles()) ->setParameter('teamer', $teamer) ; if ([] !== $availableAssignmentsIds) { $qb->andWhere($qb->expr()->in('assignment.id', $availableAssignmentsIds)); } if (0 === $teamer->getTrainingAttendances()->count() && 0 === $teamer->getLicenses()->count()) { $qb ->andWhere($qb->expr()->eq('job_profile.requiredLicenses', ':requiredLicenses')) ->setParameter('requiredLicenses', '[]') ->andWhere($qb->expr()->isNull('required_training')) ; } return $qb ->orderBy('destination.dateFrom', 'ASC') ->setMaxResults($limit) ->getQuery() ->getResult() ; } private function getAvailableAssignmentsIds(): array { $qb = $this->createQueryBuilder('assignment'); $availableAssignments = $qb ->select('assignment.id', 'assignment.availableDispositions') ->leftJoin('assignment.dispositions', 'disposition') ->innerJoin('assignment.destination', 'destination') ->where($qb->expr()->andX( $qb->expr()->orX( $qb->expr()->gt('destination.dateFrom', ':now'), $qb->expr()->andX( $qb->expr()->isNotNull('assignment.dateFrom'), $qb->expr()->gt('assignment.dateFrom', ':now') ) ), $qb->expr()->notIn('assignment.status', ':status'), $qb->expr()->isNull('assignment.deletedAt') )) ->groupBy('assignment.id', 'assignment.availableDispositions') ->having($qb->expr()->gt('assignment.availableDispositions', $qb->expr()->count('disposition.id'))) ->setParameter('status', [Assignment::STATUS_DRAFT, Assignment::STATUS_CALLED_OFF]) ->setParameter('now', new \DateTimeImmutable()) ->getQuery() ->getArrayResult() ; return array_column($availableAssignments, 'id'); } public function getSelectableHotelCodes(): array { $qb = $this->createQueryBuilder('assignment'); $codes = []; $result = $qb ->select('destination.hotelCode') ->innerJoin('assignment.destination', 'destination') ->groupBy('destination.hotelCode') ->orderBy('destination.hotelCode', 'ASC') ->getQuery() ->getArrayResult(); foreach ($result as $row) { if (str_starts_with($row['hotelCode'], 'SER')) { $codes[] = substr($row['hotelCode'], 2, 3); } else { $codes[] = substr($row['hotelCode'], 0, 3); } } return array_unique($codes); } }