diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index f9db5c158..6babecc55 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -122,9 +122,30 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos ->leftJoin('a.user', 'activity_u') ->andWhere( $qb->expr()->orX( - 'creator.userJob IN (:jobs)', - 'activity_u.userJob IN (:jobs)', - 'EXISTS (SELECT 1 FROM ' . User::class . ' activity_user WHERE activity_user MEMBER OF a.users AND activity_user.userJob IN (:jobs))' + $qb->expr()->exists( + sprintf( + "SELECT 1 FROM %s ujh_creator WHERE ujh_creator.user = a.createdBy " + . "AND ujh_creator.job IN (:jobs) AND a.createdAt > ujh_creator.startDate " + . "AND (ujh_creator.endDate IS NULL or ujh_creator.endDate > a.date)", + User\UserJobHistory::class + ) + ), + $qb->expr()->exists( + sprintf( + "SELECT 1 FROM %s ujh_u WHERE ujh_u.user = a.user " + . "AND ujh_u.job IN (:jobs) AND a.createdAt > ujh_u.startDate " + . "AND (ujh_u.endDate IS NULL or ujh_u.endDate > a.date)", + User\UserJobHistory::class + ) + ), + $qb->expr()->exists( + sprintf( + "SELECT 1 FROM %s ujh_users WHERE ujh_users.user MEMBER OF a.users " + . "AND ujh_users.job IN (:jobs) AND a.createdAt > ujh_users.startDate " + . "AND (ujh_users.endDate IS NULL or ujh_users.endDate > a.date)", + User\UserJobHistory::class + ) + ), ) ) ->setParameter('jobs', $jobs); @@ -175,13 +196,14 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos public function findUserJobByAssociated(Person|AccompanyingPeriod $associated): array { $in = $this->em->createQueryBuilder(); - $in->select('IDENTITY(u.userJob)') - ->from(User::class, 'u') + $in->select('IDENTITY(u.job)') + ->distinct() + ->from(User\UserJobHistory::class, 'u') ->join( Activity::class, 'a', Join::WITH, - 'a.createdBy = u OR a.user = u OR u MEMBER OF a.users' + 'a.createdBy = u.user OR a.user = u.user OR u.user MEMBER OF a.users AND a.date >= u.startDate ANd (u.endDate IS NULL or u.endDate > a.date)' ); if ($associated instanceof Person) { diff --git a/src/Bundle/ChillMainBundle/Controller/UserExportController.php b/src/Bundle/ChillMainBundle/Controller/UserExportController.php index 04778dfae..450be8dd7 100644 --- a/src/Bundle/ChillMainBundle/Controller/UserExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/UserExportController.php @@ -59,10 +59,10 @@ final readonly class UserExportController 'label', 'mainCenter_id' , 'mainCenter_name', - 'mainScope_id', /// - 'mainScope_name', /// - 'userJob_id', /// - 'userJob_name', /// + 'mainScope_id', + 'mainScope_name', + 'userJob_id', + 'userJob_name', 'currentLocation_id', 'currentLocation_name', 'mainLocation_id', diff --git a/src/Bundle/ChillMainBundle/Repository/UserRepository.php b/src/Bundle/ChillMainBundle/Repository/UserRepository.php index a59ddc399..222dae2b5 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/UserRepository.php @@ -13,7 +13,8 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\GroupCenter; use Chill\MainBundle\Entity\User; -use Doctrine\ORM\AbstractQuery; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\NoResultException; @@ -27,7 +28,11 @@ final readonly class UserRepository implements UserRepositoryInterface { private EntityRepository $repository; - public function __construct(private EntityManagerInterface $entityManager) + private const FIELDS = ['id', 'email', 'enabled', 'civility_id', 'civility_abbreviation', 'civility_name', 'label', 'mainCenter_id', + 'mainCenter_name', 'mainScope_id', 'mainScope_name', 'userJob_id', 'userJob_name', 'currentLocation_id', 'currentLocation_name', + 'mainLocation_id', 'mainLocation_name']; + + public function __construct(private EntityManagerInterface $entityManager, private Connection $connection) { $this->repository = $entityManager->getRepository(User::class); } @@ -75,48 +80,58 @@ final readonly class UserRepository implements UserRepositoryInterface } /** - * @param string $lang + * @throws Exception */ public function findAllAsArray(string $lang): iterable { - $dql = sprintf(<<<'DQL' + $sql = sprintf(<<<'SQL' SELECT - u.id AS id, + u.id, u.username AS username, - u.email, + u.email AS email, u.enabled, - IDENTITY(u.civility) AS civility_id, - JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation, - JSON_EXTRACT(civility.name, :lang) AS civility_name, + u.civility_id, + civility.abbreviation->>:lang AS civility_abbreviation, + civility.name->>:lang AS civility_name, u.label, mainCenter.id AS mainCenter_id, mainCenter.name AS mainCenter_name, - IDENTITY(u.mainScope) AS mainScope_id, - JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name, - IDENTITY(u.userJob) AS userJob_id, - JSON_EXTRACT(userJob.label, :lang) AS userJob_name, + mainScope.id AS mainScope_id, + mainScope.name->>:lang AS mainScope_name, + userJob.id AS userJob_id, + userJob.label->>:lang AS userJob_name, currentLocation.id AS currentLocation_id, currentLocation.name AS currentLocation_name, mainLocation.id AS mainLocation_id, mainLocation.name AS mainLocation_name, u.absenceStart - FROM Chill\MainBundle\Entity\User u - LEFT JOIN u.civility civility - LEFT JOIN u.currentLocation currentLocation - LEFT JOIN u.mainLocation mainLocation - LEFT JOIN u.mainCenter mainCenter - LEFT JOIN u.mainScope mainScope - LEFT JOIN u.userJob userJob - ORDER BY u.label - DQL); /// mainScope userJob + FROM users u + LEFT JOIN chill_main_civility civility ON u.civility_id = civility.id + LEFT JOIN centers mainCenter ON u.maincenter_id = mainCenter.id + LEFT JOIN chill_main_user_job_history userJobHistory ON u.id = userJobHistory.user_id + LEFT JOIN chill_main_user_job userJob ON userJobHistory.job_id = userJob.id + LEFT JOIN chill_main_user_scope_history userScopeHistory ON u.id = userScopeHistory.user_id + LEFT JOIN scopes mainScope ON userScopeHistory.scope_id = mainScope.id + LEFT JOIN chill_main_location currentLocation ON u.currentlocation_id = currentLocation.id + LEFT JOIN chill_main_location mainLocation ON u.mainlocation_id = mainLocation.id + WHERE + tstzrange(userScopeHistory.startdate, userScopeHistory.enddate) @> NOW() + AND + tstzrange(userJobHistory.startdate, userJobHistory.enddate) @> NOW() + SQL); - $query = $this->entityManager->createQuery($dql) - ->setHydrationMode(AbstractQuery::HYDRATE_ARRAY) - ->setParameter('lang', $lang) - ; + $query = $this->connection->prepare($sql); - foreach ($query->toIterable() as $u) { - yield $u; + foreach ($query->executeQuery(['lang' => $lang])->iterateAssociative() as $u) { + $converted = []; + foreach (self::FIELDS as $f) { + $converted[$f] = $u[strtolower($f)]; + } + + $converted['absenceStart'] = null !== $u['absencestart'] ? new \DateTimeImmutable($u['absencestart']) : null; + + /** @phpstan-ignore-next-line phpstan does not take into account that all required keys will be present */ + yield $converted; } } diff --git a/src/Bundle/ChillMainBundle/Service/EntityInfo/ViewEntityInfoManager.php b/src/Bundle/ChillMainBundle/Service/EntityInfo/ViewEntityInfoManager.php index 5057d76e5..c5ef96d0b 100644 --- a/src/Bundle/ChillMainBundle/Service/EntityInfo/ViewEntityInfoManager.php +++ b/src/Bundle/ChillMainBundle/Service/EntityInfo/ViewEntityInfoManager.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Service\EntityInfo; use Doctrine\DBAL\Connection; +use Psr\Log\LoggerInterface; class ViewEntityInfoManager { @@ -21,6 +22,7 @@ class ViewEntityInfoManager */ private readonly iterable $vienEntityInfoProviders, private readonly Connection $connection, + private readonly LoggerInterface $logger, ) {} public function synchronizeOnDB(): void @@ -28,6 +30,8 @@ class ViewEntityInfoManager $this->connection->transactional(function (Connection $conn): void { foreach ($this->vienEntityInfoProviders as $viewProvider) { foreach ($this->createOrReplaceViewSQL($viewProvider, $viewProvider->getViewName()) as $sql) { + $this->logger->debug("Will execute create view sql", ['sql' => $sql]); + $this->logger->debug($sql); $conn->executeQuery($sql); } } @@ -41,7 +45,7 @@ class ViewEntityInfoManager { return [ "DROP VIEW IF EXISTS {$viewName}", - sprintf("CREATE VIEW {$viewName} AS %s", $viewProvider->getViewQuery()) + sprintf("CREATE OR REPLACE VIEW {$viewName} AS %s", $viewProvider->getViewQuery()) ]; } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index bf87bf7ee..0c17276ae 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -157,7 +157,6 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues /** * @var Collection * @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkReferrerHistory::class, cascade={"persist", "remove"}, mappedBy="accompanyingPeriodWork", orphanRemoval=true) - * @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer") */ private Collection $referrersHistory; diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php index 28cbca270..fb1bb4ffa 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkEvaluationRepository.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\MainBundle\Entity\User; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; @@ -91,7 +92,9 @@ class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository $qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')), $qb->expr()->orX( $qb->expr()->eq('period.user', ':user'), - $qb->expr()->isMemberOf(':user', 'work.referrers') + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodWork::class . ' subw JOIN subw.referrersHistory subw_ref_history WHERE subw.id = work.id AND subw_ref_history.user = :user' + ) ) ) ) diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php index 04ce8928e..4cbbab9fc 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php @@ -132,10 +132,10 @@ final readonly class AccompanyingPeriodWorkRepository implements ObjectRepositor // set limit and offset $sql .= " ORDER BY - CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC, - startdate DESC, - enddate DESC, - id DESC"; + CASE WHEN w.enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC, + w.startdate DESC, + w.enddate DESC, + w.id DESC"; $sql .= " LIMIT :limit OFFSET :offset"; @@ -239,7 +239,9 @@ final readonly class AccompanyingPeriodWorkRepository implements ObjectRepositor $qb->expr()->lte('w.startDate', ':until'), $qb->expr()->orX( $qb->expr()->eq('period.user', ':user'), - $qb->expr()->isMemberOf(':user', 'w.referrers') + $qb->expr()->exists( + 'SELECT 1 FROM ' . AccompanyingPeriodWork::class . ' subw JOIN subw.referrersHistory subw_ref_history WHERE subw.id = w.id AND subw_ref_history.user = :user and subw.ref_history.endDate IS NULL' + ) ) ) ) diff --git a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo.php b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo.php index 8ba9b9640..f0b38b635 100644 --- a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo.php +++ b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo.php @@ -54,7 +54,7 @@ class AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo implements Acc public function getFromStatement(): string { return 'chill_person_accompanying_period_work w - LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id'; + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON w.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> w.endDate'; } public function getWhereClause(): string diff --git a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo.php b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationCreationQueryPartForAccompanyingPeriodInfo.php similarity index 73% rename from src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo.php rename to src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationCreationQueryPartForAccompanyingPeriodInfo.php index a71fe4d29..53b5e31ad 100644 --- a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo.php +++ b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationCreationQueryPartForAccompanyingPeriodInfo.php @@ -11,10 +11,11 @@ declare(strict_types=1); namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart; +use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface; -class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface +class AccompanyingPeriodWorkEvaluationCreationQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface { public function getAccompanyingPeriodIdColumn(): string { @@ -33,12 +34,12 @@ class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodI public function getUserIdColumn(): string { - return 'cpapwr.user_id'; + return 'e.createdBy_id'; } public function getDateTimeColumn(): string { - return 'e.maxDate'; + return 'e.createdAt'; } public function getMetadataColumn(): string @@ -48,18 +49,18 @@ class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodI public function getDiscriminator(): string { - return 'accompanying_period_work_evaluation_max'; + return 'accompanying_period_work_evaluation_creation'; } public function getFromStatement(): string { return 'chill_person_accompanying_period_work_evaluation e JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id - LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id'; + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> e.startDate'; } public function getWhereClause(): string { - return 'e.maxDate IS NOT NULL'; + return 'e.createdAt IS NOT NULL'; } } diff --git a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo.php b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo.php index b89f77460..843602249 100644 --- a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo.php +++ b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo.php @@ -56,7 +56,7 @@ class AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo impl { return 'chill_person_accompanying_period_work_evaluation e JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id - LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id'; + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> e.maxDate'; } public function getWhereClause(): string diff --git a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo.php b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo.php index 8ea3e579c..cc82fc9ab 100644 --- a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo.php +++ b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo.php @@ -56,11 +56,11 @@ class AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo im { return 'chill_person_accompanying_period_work_evaluation e JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id - LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id'; + LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id AND daterange(cpapwr.startDate, cpapwr.endDate) @> e.startDate'; } public function getWhereClause(): string { - return ''; + return 'e.startDate IS NOT NULL'; } } diff --git a/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkNewReferrerQueryPartForAccompanyingPeriodInfo.php b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkNewReferrerQueryPartForAccompanyingPeriodInfo.php new file mode 100644 index 000000000..2208c2975 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkNewReferrerQueryPartForAccompanyingPeriodInfo.php @@ -0,0 +1,64 @@ + w.startDate'; } public function getWhereClause(): string diff --git a/src/Bundle/ChillPersonBundle/Tests/Repository/UserRepositoryTest.php b/src/Bundle/ChillPersonBundle/Tests/Repository/UserRepositoryTest.php new file mode 100644 index 000000000..c7d8e67cc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Repository/UserRepositoryTest.php @@ -0,0 +1,43 @@ +userRepository = self::$container->get(UserRepository::class); + } + + public function testFindAllAsArray(): void + { + $userIterator = $this->userRepository->findAllAsArray('fr'); + + self::assertIsIterable($userIterator); + $i = 0; + foreach ($userIterator as $u) { + self::assertIsArray($u); + } + self::assertGreaterThan(0, $i); + } +}