update some queries in the interface to take into account history of user's scope and user's job

This commit is contained in:
Julien Fastré 2023-10-11 15:14:27 +02:00
parent 363785b779
commit 978db5a5c5
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
14 changed files with 210 additions and 57 deletions

View File

@ -122,9 +122,30 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos
->leftJoin('a.user', 'activity_u') ->leftJoin('a.user', 'activity_u')
->andWhere( ->andWhere(
$qb->expr()->orX( $qb->expr()->orX(
'creator.userJob IN (:jobs)', $qb->expr()->exists(
'activity_u.userJob IN (:jobs)', sprintf(
'EXISTS (SELECT 1 FROM ' . User::class . ' activity_user WHERE activity_user MEMBER OF a.users AND activity_user.userJob IN (:jobs))' "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); ->setParameter('jobs', $jobs);
@ -175,13 +196,14 @@ final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepos
public function findUserJobByAssociated(Person|AccompanyingPeriod $associated): array public function findUserJobByAssociated(Person|AccompanyingPeriod $associated): array
{ {
$in = $this->em->createQueryBuilder(); $in = $this->em->createQueryBuilder();
$in->select('IDENTITY(u.userJob)') $in->select('IDENTITY(u.job)')
->from(User::class, 'u') ->distinct()
->from(User\UserJobHistory::class, 'u')
->join( ->join(
Activity::class, Activity::class,
'a', 'a',
Join::WITH, 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) { if ($associated instanceof Person) {

View File

@ -59,10 +59,10 @@ final readonly class UserExportController
'label', 'label',
'mainCenter_id' , 'mainCenter_id' ,
'mainCenter_name', 'mainCenter_name',
'mainScope_id', /// 'mainScope_id',
'mainScope_name', /// 'mainScope_name',
'userJob_id', /// 'userJob_id',
'userJob_name', /// 'userJob_name',
'currentLocation_id', 'currentLocation_id',
'currentLocation_name', 'currentLocation_name',
'mainLocation_id', 'mainLocation_id',

View File

@ -13,7 +13,8 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\GroupCenter; use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Doctrine\ORM\AbstractQuery; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException; use Doctrine\ORM\NoResultException;
@ -27,7 +28,11 @@ final readonly class UserRepository implements UserRepositoryInterface
{ {
private EntityRepository $repository; 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); $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 public function findAllAsArray(string $lang): iterable
{ {
$dql = sprintf(<<<'DQL' $sql = sprintf(<<<'SQL'
SELECT SELECT
u.id AS id, u.id,
u.username AS username, u.username AS username,
u.email, u.email AS email,
u.enabled, u.enabled,
IDENTITY(u.civility) AS civility_id, u.civility_id,
JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation, civility.abbreviation->>:lang AS civility_abbreviation,
JSON_EXTRACT(civility.name, :lang) AS civility_name, civility.name->>:lang AS civility_name,
u.label, u.label,
mainCenter.id AS mainCenter_id, mainCenter.id AS mainCenter_id,
mainCenter.name AS mainCenter_name, mainCenter.name AS mainCenter_name,
IDENTITY(u.mainScope) AS mainScope_id, mainScope.id AS mainScope_id,
JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name, mainScope.name->>:lang AS mainScope_name,
IDENTITY(u.userJob) AS userJob_id, userJob.id AS userJob_id,
JSON_EXTRACT(userJob.label, :lang) AS userJob_name, userJob.label->>:lang AS userJob_name,
currentLocation.id AS currentLocation_id, currentLocation.id AS currentLocation_id,
currentLocation.name AS currentLocation_name, currentLocation.name AS currentLocation_name,
mainLocation.id AS mainLocation_id, mainLocation.id AS mainLocation_id,
mainLocation.name AS mainLocation_name, mainLocation.name AS mainLocation_name,
u.absenceStart u.absenceStart
FROM Chill\MainBundle\Entity\User u FROM users u
LEFT JOIN u.civility civility LEFT JOIN chill_main_civility civility ON u.civility_id = civility.id
LEFT JOIN u.currentLocation currentLocation LEFT JOIN centers mainCenter ON u.maincenter_id = mainCenter.id
LEFT JOIN u.mainLocation mainLocation LEFT JOIN chill_main_user_job_history userJobHistory ON u.id = userJobHistory.user_id
LEFT JOIN u.mainCenter mainCenter LEFT JOIN chill_main_user_job userJob ON userJobHistory.job_id = userJob.id
LEFT JOIN u.mainScope mainScope LEFT JOIN chill_main_user_scope_history userScopeHistory ON u.id = userScopeHistory.user_id
LEFT JOIN u.userJob userJob LEFT JOIN scopes mainScope ON userScopeHistory.scope_id = mainScope.id
ORDER BY u.label LEFT JOIN chill_main_location currentLocation ON u.currentlocation_id = currentLocation.id
DQL); /// mainScope userJob 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) $query = $this->connection->prepare($sql);
->setHydrationMode(AbstractQuery::HYDRATE_ARRAY)
->setParameter('lang', $lang)
;
foreach ($query->toIterable() as $u) { foreach ($query->executeQuery(['lang' => $lang])->iterateAssociative() as $u) {
yield $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;
} }
} }

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Service\EntityInfo; namespace Chill\MainBundle\Service\EntityInfo;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
class ViewEntityInfoManager class ViewEntityInfoManager
{ {
@ -21,6 +22,7 @@ class ViewEntityInfoManager
*/ */
private readonly iterable $vienEntityInfoProviders, private readonly iterable $vienEntityInfoProviders,
private readonly Connection $connection, private readonly Connection $connection,
private readonly LoggerInterface $logger,
) {} ) {}
public function synchronizeOnDB(): void public function synchronizeOnDB(): void
@ -28,6 +30,8 @@ class ViewEntityInfoManager
$this->connection->transactional(function (Connection $conn): void { $this->connection->transactional(function (Connection $conn): void {
foreach ($this->vienEntityInfoProviders as $viewProvider) { foreach ($this->vienEntityInfoProviders as $viewProvider) {
foreach ($this->createOrReplaceViewSQL($viewProvider, $viewProvider->getViewName()) as $sql) { 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); $conn->executeQuery($sql);
} }
} }
@ -41,7 +45,7 @@ class ViewEntityInfoManager
{ {
return [ return [
"DROP VIEW IF EXISTS {$viewName}", "DROP VIEW IF EXISTS {$viewName}",
sprintf("CREATE VIEW {$viewName} AS %s", $viewProvider->getViewQuery()) sprintf("CREATE OR REPLACE VIEW {$viewName} AS %s", $viewProvider->getViewQuery())
]; ];
} }
} }

View File

@ -157,7 +157,6 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
/** /**
* @var Collection<int, AccompanyingPeriodWorkReferrerHistory> * @var Collection<int, AccompanyingPeriodWorkReferrerHistory>
* @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkReferrerHistory::class, cascade={"persist", "remove"}, mappedBy="accompanyingPeriodWork", orphanRemoval=true) * @ORM\OneToMany(targetEntity=AccompanyingPeriodWorkReferrerHistory::class, cascade={"persist", "remove"}, mappedBy="accompanyingPeriodWork", orphanRemoval=true)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer")
*/ */
private Collection $referrersHistory; private Collection $referrersHistory;

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Repository\AccompanyingPeriod; namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; 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()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')),
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->eq('period.user', ':user'), $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'
)
) )
) )
) )

View File

@ -132,10 +132,10 @@ final readonly class AccompanyingPeriodWorkRepository implements ObjectRepositor
// set limit and offset // set limit and offset
$sql .= " ORDER BY $sql .= " ORDER BY
CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC, CASE WHEN w.enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC,
startdate DESC, w.startdate DESC,
enddate DESC, w.enddate DESC,
id DESC"; w.id DESC";
$sql .= " LIMIT :limit OFFSET :offset"; $sql .= " LIMIT :limit OFFSET :offset";
@ -239,7 +239,9 @@ final readonly class AccompanyingPeriodWorkRepository implements ObjectRepositor
$qb->expr()->lte('w.startDate', ':until'), $qb->expr()->lte('w.startDate', ':until'),
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->eq('period.user', ':user'), $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'
)
) )
) )
) )

View File

@ -54,7 +54,7 @@ class AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo implements Acc
public function getFromStatement(): string public function getFromStatement(): string
{ {
return 'chill_person_accompanying_period_work w 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 public function getWhereClause(): string

View File

@ -11,10 +11,11 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart; namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface; use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface class AccompanyingPeriodWorkEvaluationCreationQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
{ {
public function getAccompanyingPeriodIdColumn(): string public function getAccompanyingPeriodIdColumn(): string
{ {
@ -33,12 +34,12 @@ class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodI
public function getUserIdColumn(): string public function getUserIdColumn(): string
{ {
return 'cpapwr.user_id'; return 'e.createdBy_id';
} }
public function getDateTimeColumn(): string public function getDateTimeColumn(): string
{ {
return 'e.maxDate'; return 'e.createdAt';
} }
public function getMetadataColumn(): string public function getMetadataColumn(): string
@ -48,18 +49,18 @@ class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodI
public function getDiscriminator(): string public function getDiscriminator(): string
{ {
return 'accompanying_period_work_evaluation_max'; return 'accompanying_period_work_evaluation_creation';
} }
public function getFromStatement(): string public function getFromStatement(): string
{ {
return 'chill_person_accompanying_period_work_evaluation e return 'chill_person_accompanying_period_work_evaluation e
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id 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 public function getWhereClause(): string
{ {
return 'e.maxDate IS NOT NULL'; return 'e.createdAt IS NOT NULL';
} }
} }

View File

@ -56,7 +56,7 @@ class AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo impl
{ {
return 'chill_person_accompanying_period_work_evaluation e return 'chill_person_accompanying_period_work_evaluation e
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id 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 public function getWhereClause(): string

View File

@ -56,11 +56,11 @@ class AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo im
{ {
return 'chill_person_accompanying_period_work_evaluation e return 'chill_person_accompanying_period_work_evaluation e
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id 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 public function getWhereClause(): string
{ {
return ''; return 'e.startDate IS NOT NULL';
} }
} }

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
class AccompanyingPeriodWorkNewReferrerQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
{
public function getAccompanyingPeriodIdColumn(): string
{
return 'w.accompanyingperiod_id';
}
public function getRelatedEntityColumn(): string
{
return AccompanyingPeriodWork::class;
}
public function getRelatedEntityIdColumn(): string
{
return 'w.id';
}
public function getUserIdColumn(): string
{
return 'cpapwr.user_id';
}
public function getDateTimeColumn(): string
{
return 'cpapwr.startDate';
}
public function getMetadataColumn(): string
{
return "'{}'::jsonb";
}
public function getDiscriminator(): string
{
return 'accompanying_period_work_referrer_new';
}
public function getFromStatement(): string
{
return 'chill_person_accompanying_period_work w
JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id';
}
public function getWhereClause(): string
{
return '';
}
}

View File

@ -54,7 +54,7 @@ class AccompanyingPeriodWorkStartQueryPartForAccompanyingPeriodInfo implements A
public function getFromStatement(): string public function getFromStatement(): string
{ {
return 'chill_person_accompanying_period_work w 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.startDate';
} }
public function getWhereClause(): string public function getWhereClause(): string

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Repository;
use Chill\MainBundle\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
class UserRepositoryTest extends KernelTestCase
{
private UserRepository $userRepository;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->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);
}
}