From afcd6e0605674312c7b9ce557bed4b9330947ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 23 May 2023 22:12:18 +0200 Subject: [PATCH 01/40] bootstrap generic doc manager and associated services --- ...ericDocForAccompanyingPeriodController.php | 48 +++++ .../GenericDoc/FetchQuery.php | 168 ++++++++++++++++++ .../GenericDoc/FetchQueryInterface.php | 45 +++++ .../GenericDoc/FetchQueryToSqlBuilder.php | 48 +++++ .../GenericDoc/GenericDocDTO.php | 22 +++ .../GenericDoc/Manager.php | 102 +++++++++++ ...ProviderForAccompanyingPeriodInterface.php | 33 ++++ .../GenericDoc/FetchQueryToSqlBuilderTest.php | 57 ++++++ .../Tests/GenericDoc/ManagerTest.php | 56 ++++++ .../ChillDocStoreBundle/config/services.yaml | 6 + 10 files changed, 585 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php new file mode 100644 index 000000000..32a7cc1c9 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php @@ -0,0 +1,48 @@ +security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) { + throw new AccessDeniedHttpException("not allowed to see the documents for accompanying period"); + } + + $nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod); + + return new Response($nb); + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php new file mode 100644 index 000000000..42e0fd335 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php @@ -0,0 +1,168 @@ + + */ + private array $joins = []; + + /** + * @var list + */ + private array $joinParams = []; + + /** + * @var list + */ + private array $wheres = []; + + /** + * @var list + */ + private array $whereParams = []; + + public function __construct( + private readonly string $selectKeyString, + private readonly string $selectIdentifierJsonB, + private readonly string $selectDate, + private string $from = '', + private array $selectIdentifierParams = [], + private array $selectDateParams = [], + ) { + } + + public function addJoinClause(string $sql, array $params = []): int + { + $this->joins[] = $sql; + $this->joinParams[] = $params; + + return count($this->joins) - 1; + } + + public function addWhereClause(string $sql, array $params = []): int + { + $this->wheres[] = $sql; + $this->whereParams[] = $params; + + return count($this->wheres) - 1; + } + + public function removeWhereClause(int $index): void + { + if (!array_key_exists($index, $this->wheres)) { + throw new \UnexpectedValueException("this index does not exists"); + } + + unset($this->wheres[$index], $this->whereParams[$index]); + + } + + public function removeJoinClause(int $index): void + { + if (!array_key_exists($index, $this->joins)) { + throw new \UnexpectedValueException("this index does not exists"); + } + + unset($this->joins[$index], $this->joinParams[$index]); + + } + + public function getSelectKeyString(): string + { + return $this->selectKeyString; + } + + public function getSelectIdentifierJsonB(): string + { + return $this->selectIdentifierJsonB; + } + + /** + * @inheritDoc + */ + public function getSelectIdentifierParams(): array + { + return $this->selectIdentifierParams; + } + + public function getSelectDate(): string + { + return $this->selectDate; + } + + /** + * @inheritDoc + */ + public function getSelectDateParams(): array + { + return $this->selectDateParams; + } + + public function getFromQuery(): string + { + return $this->from . " " . implode(' ', $this->joins); + } + + /** + * @inheritDoc + */ + public function getFromQueryParams(): array + { + $result = []; + + foreach ($this->joinParams as $params) { + $result = [...$result, ...$params]; + } + + return $result; + } + + public function getWhereQuery(): string + { + return implode(' AND ', $this->wheres); + } + + /** + * @inheritDoc + */ + public function getWhereQueryParams(): array + { + $result = []; + + foreach ($this->whereParams as $params) { + $result = [...$result, ...$params]; + } + + return $result; + } + + /** + * @param array $selectIdentifierParams + */ + public function setSelectIdentifierParams(array $selectIdentifierParams): void + { + $this->selectIdentifierParams = $selectIdentifierParams; + } + + /** + * @param array $selectDateParams + */ + public function setSelectDateParams(array $selectDateParams): void + { + $this->selectDateParams = $selectDateParams; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php new file mode 100644 index 000000000..c262d2a5d --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php @@ -0,0 +1,45 @@ + + */ + public function getSelectIdentifierParams(): array; + + public function getSelectDate(): string; + + /** + * @return list + */ + public function getSelectDateParams(): array; + + public function getFromQuery(): string; + + /** + * @return list + */ + public function getFromQueryParams(): array; + + public function getWhereQuery(): string; + + /** + * @return list + */ + public function getWhereQueryParams(): array; +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php new file mode 100644 index 000000000..881435da7 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php @@ -0,0 +1,48 @@ +} + */ + public function toSql(FetchQueryInterface $query): array + { + $sql = strtr(self::SQL, [ + '{{ key }}' => $query->getSelectKeyString(), + '{{ identifiers }}' => $query->getSelectIdentifierJsonB(), + '{{ date }}' => $query->getSelectDate(), + '{{ from }}' => $query->getFromQuery(), + '{{ where }}' => $query->getWhereQuery(), + ]); + + $params = [ + ...$query->getSelectIdentifierParams(), + ...$query->getSelectDateParams(), + ...$query->getFromQueryParams(), + ...$query->getWhereQueryParams() + ]; + + return ['sql' => $sql, 'params' => $params]; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php new file mode 100644 index 000000000..6fb6139aa --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php @@ -0,0 +1,22 @@ + + */ + private readonly iterable $providersForAccompanyingPeriod, + private readonly Connection $connection, + ) { + $this->builder = new FetchQueryToSqlBuilder(); + } + + /** + * @throws Exception + */ + public function countDocForAccompanyingPeriod( + AccompanyingPeriod $accompanyingPeriod, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + ?string $origin = null + ): int { + ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + $countSql = "SELECT count(*) AS c FROM {$sql} AS sq"; + $result = $this->connection->executeQuery($countSql, $params); + + $number = $result->fetchOne(); + + if (false === $number) { + throw new \UnexpectedValueException("number of documents failed to load"); + } + + return $number['c']; + } + + /** + * @throws Exception + */ + public function findDocForAccompanyingPeriod( + AccompanyingPeriod $accompanyingPeriod, + int $offset = 0, + int $limit = 20, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + ?string $origin = null + ): iterable { + ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + + $runSql = "{$sql} LIMIT ? OFFSET ?"; + $runParams = [...$params, ...[$limit, $offset]]; + + foreach($this->connection->iterateAssociative($runSql, $runParams) as $row) { + yield new GenericDocDTO($row['key'], $row['identifiers'], $row['date_doc']); + } + } + + /** + */ + private function buildUnionQuery( + AccompanyingPeriod|Person $linked, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + ?string $origin = null + ): array { + $sql = []; + $params = []; + + if ($linked instanceof AccompanyingPeriod) { + foreach ($this->providersForAccompanyingPeriod as $provider) { + ['sql' => $q, 'params' => $p ] = $this->builder + ->toSql($provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content, $origin)); + $params = [...$params, ...$p]; + $sql[] = $q; + } + } + + return ['sql' => implode(' UNION ', $sql), 'params' => $params]; + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php new file mode 100644 index 000000000..07ca83694 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php @@ -0,0 +1,33 @@ +addJoinClause('LEFT JOIN other b ON a.id = b.foreign_id', ['foo']); + $index = $query->addJoinClause('LEFT JOIN other c ON a.id = c.foreign_id', ['bar']); + $query->addJoinClause('LEFT JOIN other d ON a.id = d.foreign_id', ['bar_baz']); + $query->removeJoinClause($index); + $query->addWhereClause('b.item = ?', ['baz']); + $index = $query->addWhereClause('b.cancel', [ 'foz']); + $query->removeWhereClause($index); + + ['sql' => $sql, 'params' => $params] = (new FetchQueryToSqlBuilder())->toSql($query); + + $filteredSql = + implode(" ", array_filter( + explode(" ", str_replace("\n", "", $sql)), + fn (string $tok) => $tok !== "" + )) + ; + + self::assertEquals( + "SELECT 'test' AS key, jsonb_build_object('id', a.column) AS identifiers, ". + "a.datecolumn AS doc_date FROM my_table a LEFT JOIN other b ON a.id = b.foreign_id LEFT JOIN other d ON a.id = d.foreign_id WHERE b.item = ?", + $filteredSql + ); + self::assertEquals(['foo', 'bar_baz', 'baz'], $params); + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php new file mode 100644 index 000000000..523e08c40 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php @@ -0,0 +1,56 @@ +em = self::$container->get(EntityManagerInterface::class); + + if (null !== $manager = self::$container->get(Manager::class)) { + $this->manager = $manager; + } else { + throw new \UnexpectedValueException("the manager was not found in the kernel"); + } + } + + public function testCountByAccompanyingPeriod(): void + { + $period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $period) { + throw new \UnexpectedValueException("period not found"); + } + + $nb = $this->manager->countDocForAccompanyingPeriod($period); + + self::assertIsInt($nb); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 860495677..22161d2f6 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -45,3 +45,9 @@ services: autoconfigure: true resource: '../Service/' + Chill\DocStoreBundle\GenericDoc\Manager: + autowire: true + autoconfigure: true + arguments: + $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider + From 8dbe2d6ec20e166f3259f41e08798842b4c184bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 24 May 2023 11:42:30 +0200 Subject: [PATCH 02/40] GenericDoc: add provider for AccompanyingCourseDocument, without filtering --- .../ChillDocStoreBundle.php | 7 ++ ...ericDocForAccompanyingPeriodController.php | 4 + .../GenericDoc/FetchQuery.php | 99 +++++++++++++++---- .../GenericDoc/FetchQueryInterface.php | 22 +++++ .../GenericDoc/FetchQueryToSqlBuilder.php | 19 +++- .../GenericDoc/Manager.php | 32 ++++-- ...ProviderForAccompanyingPeriodInterface.php | 2 +- .../AccompanyingCourseDocumentProvider.php | 84 ++++++++++++++++ .../GenericDoc/FetchQueryToSqlBuilderTest.php | 46 +++++++-- .../Tests/GenericDoc/ManagerTest.php | 63 ++++++++++-- ...AccompanyingCourseDocumentProviderTest.php | 78 +++++++++++++++ .../ChillDocStoreBundle/config/services.yaml | 4 + 12 files changed, 414 insertions(+), 46 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php diff --git a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php index 81c71f45f..44bc7d70d 100644 --- a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php +++ b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php @@ -11,8 +11,15 @@ declare(strict_types=1); namespace Chill\DocStoreBundle; +use Chill\DocStoreBundle\GenericDoc\ProviderForAccompanyingPeriodInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class ChillDocStoreBundle extends Bundle { + public function build(ContainerBuilder $container) + { + $container->registerForAutoconfiguration(ProviderForAccompanyingPeriodInterface::class) + ->addTag('chill_doc_store.generic_doc_accompanying_period_provider'); + } } diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php index 32a7cc1c9..0a9175087 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php @@ -42,6 +42,10 @@ final readonly class GenericDocForAccompanyingPeriodController $nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod); + foreach ($this->manager->findDocForAccompanyingPeriod($accompanyingPeriod) as $dto) { + dump($dto); + } + return new Response($nb); } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php index 42e0fd335..30e07a841 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php @@ -11,7 +11,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\GenericDoc; -use Nelmio\Alice\Throwable\Exception\FixtureBuilder\Denormalizer\UnexpectedValueException; +use Doctrine\DBAL\Types\Types; class FetchQuery implements FetchQueryInterface { @@ -21,42 +21,56 @@ class FetchQuery implements FetchQueryInterface private array $joins = []; /** - * @var list + * @var list> */ private array $joinParams = []; /** - * @var list + * @var array> + */ + private array $joinTypes = []; + + /** + * @var array */ private array $wheres = []; /** - * @var list + * @var array> */ private array $whereParams = []; + /** + * @var array> + */ + private array $whereTypes = []; + public function __construct( private readonly string $selectKeyString, private readonly string $selectIdentifierJsonB, private readonly string $selectDate, private string $from = '', private array $selectIdentifierParams = [], + private array $selectIdentifierTypes = [], private array $selectDateParams = [], + private array $selectDateTypes = [], ) { } - public function addJoinClause(string $sql, array $params = []): int + public function addJoinClause(string $sql, array $params = [], array $types = []): int { $this->joins[] = $sql; $this->joinParams[] = $params; + $this->joinTypes[] = $types; return count($this->joins) - 1; } - public function addWhereClause(string $sql, array $params = []): int + public function addWhereClause(string $sql, array $params = [], array $types = []): int { $this->wheres[] = $sql; $this->whereParams[] = $params; + $this->whereTypes[] = $types; return count($this->wheres) - 1; } @@ -67,7 +81,7 @@ class FetchQuery implements FetchQueryInterface throw new \UnexpectedValueException("this index does not exists"); } - unset($this->wheres[$index], $this->whereParams[$index]); + unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]); } @@ -77,7 +91,7 @@ class FetchQuery implements FetchQueryInterface throw new \UnexpectedValueException("this index does not exists"); } - unset($this->joins[$index], $this->joinParams[$index]); + unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]); } @@ -99,11 +113,21 @@ class FetchQuery implements FetchQueryInterface return $this->selectIdentifierParams; } + public function getSelectIdentifiersTypes(): array + { + return $this->selectIdentifierTypes; + } + public function getSelectDate(): string { return $this->selectDate; } + public function getSelectDateTypes(): array + { + return $this->selectDateTypes; + } + /** * @inheritDoc */ @@ -131,6 +155,17 @@ class FetchQuery implements FetchQueryInterface return $result; } + public function getFromQueryTypes(): array + { + $result = []; + + foreach ($this->joinTypes as $types) { + $result = [...$result, ...$types]; + } + + return $result; + } + public function getWhereQuery(): string { return implode(' AND ', $this->wheres); @@ -150,19 +185,49 @@ class FetchQuery implements FetchQueryInterface return $result; } - /** - * @param array $selectIdentifierParams - */ - public function setSelectIdentifierParams(array $selectIdentifierParams): void + public function getWhereQueryTypes(): array { - $this->selectIdentifierParams = $selectIdentifierParams; + $result = []; + + foreach ($this->whereTypes as $types) { + $result = [...$result, ...$types]; + } + + return $result; } - /** - * @param array $selectDateParams - */ - public function setSelectDateParams(array $selectDateParams): void + public function setSelectIdentifierParams(array $selectIdentifierParams): self + { + $this->selectIdentifierParams = $selectIdentifierParams; + + return $this; + } + + public function setSelectDateParams(array $selectDateParams): self { $this->selectDateParams = $selectDateParams; + + return $this; + } + + public function setFrom(string $from): self + { + $this->from = $from; + + return $this; + } + + public function setSelectIdentifierTypes(array $selectIdentifierTypes): self + { + $this->selectIdentifierTypes = $selectIdentifierTypes; + + return $this; + } + + public function setSelectDateTypes(array $selectDateTypes): self + { + $this->selectDateTypes = $selectDateTypes; + + return $this; } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php index c262d2a5d..e46795457 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\GenericDoc; +use Doctrine\DBAL\Types\Types; + interface FetchQueryInterface { public function getSelectKeyString(): string; @@ -22,6 +24,11 @@ interface FetchQueryInterface */ public function getSelectIdentifierParams(): array; + /** + * @return list + */ + public function getSelectIdentifiersTypes(): array; + public function getSelectDate(): string; /** @@ -29,6 +36,11 @@ interface FetchQueryInterface */ public function getSelectDateParams(): array; + /** + * @return list + */ + public function getSelectDateTypes(): array; + public function getFromQuery(): string; /** @@ -36,10 +48,20 @@ interface FetchQueryInterface */ public function getFromQueryParams(): array; + /** + * @return list + */ + public function getFromQueryTypes(): array; + public function getWhereQuery(): string; /** * @return list */ public function getWhereQueryParams(): array; + + /** + * @return list + */ + public function getWhereQueryTypes(): array; } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php index 881435da7..2c0c59cff 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php @@ -11,20 +11,22 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\GenericDoc; +use Doctrine\DBAL\Types\Types; + final readonly class FetchQueryToSqlBuilder { private const SQL = <<<'SQL' SELECT '{{ key }}' AS key, {{ identifiers }} AS identifiers, - {{ date }} AS doc_date + {{ date }}::date AS doc_date FROM {{ from }} - WHERE {{ where }} + {{ where }} SQL; /** * @param FetchQueryInterface $query - * @return array{sql: string, params: list} + * @return array{sql: string, params: list, types: list} */ public function toSql(FetchQueryInterface $query): array { @@ -33,7 +35,7 @@ final readonly class FetchQueryToSqlBuilder '{{ identifiers }}' => $query->getSelectIdentifierJsonB(), '{{ date }}' => $query->getSelectDate(), '{{ from }}' => $query->getFromQuery(), - '{{ where }}' => $query->getWhereQuery(), + '{{ where }}' => '' === ($w = $query->getWhereQuery()) ? '' : 'WHERE ' . $w, ]); $params = [ @@ -43,6 +45,13 @@ final readonly class FetchQueryToSqlBuilder ...$query->getWhereQueryParams() ]; - return ['sql' => $sql, 'params' => $params]; + $types = [ + ...$query->getSelectIdentifiersTypes(), + ...$query->getSelectDateTypes(), + ...$query->getFromQueryTypes(), + ...$query->getWhereQueryTypes(), + ]; + + return ['sql' => $sql, 'params' => $params, 'types' => $types]; } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php index bbc46c367..d67ff6d0d 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php @@ -15,6 +15,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Types\Types; class Manager { @@ -41,7 +42,12 @@ class Manager ?string $origin = null ): int { ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); - $countSql = "SELECT count(*) AS c FROM {$sql} AS sq"; + + if ($sql === '') { + return 0; + } + + $countSql = "SELECT count(*) AS c FROM ({$sql}) AS sq"; $result = $this->connection->executeQuery($countSql, $params); $number = $result->fetchOne(); @@ -50,7 +56,7 @@ class Manager throw new \UnexpectedValueException("number of documents failed to load"); } - return $number['c']; + return $number; } /** @@ -65,13 +71,18 @@ class Manager ?string $content = null, ?string $origin = null ): iterable { - ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); $runSql = "{$sql} LIMIT ? OFFSET ?"; $runParams = [...$params, ...[$limit, $offset]]; + $runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]]; - foreach($this->connection->iterateAssociative($runSql, $runParams) as $row) { - yield new GenericDocDTO($row['key'], $row['identifiers'], $row['date_doc']); + foreach($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) { + yield new GenericDocDTO( + $row['key'], + json_decode($row['identifiers'], true, JSON_THROW_ON_ERROR), + new \DateTimeImmutable($row['doc_date']) + ); } } @@ -86,17 +97,24 @@ class Manager ): array { $sql = []; $params = []; + $types = []; if ($linked instanceof AccompanyingPeriod) { foreach ($this->providersForAccompanyingPeriod as $provider) { - ['sql' => $q, 'params' => $p ] = $this->builder + if (!$provider->isAllowedForAccompanyingPeriod($linked)) { + continue; + } + + ['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder ->toSql($provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content, $origin)); $params = [...$params, ...$p]; + $types = [...$types, ...$t]; + $sql[] = $q; } } - return ['sql' => implode(' UNION ', $sql), 'params' => $params]; + return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types]; } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php index 07ca83694..935fe48a9 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php @@ -28,6 +28,6 @@ interface ProviderForAccompanyingPeriodInterface * * @return bool */ - public function isAllowed(): bool; + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool; } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php new file mode 100644 index 000000000..8f6a9fdad --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php @@ -0,0 +1,84 @@ +entityManager->getClassMetadata(AccompanyingCourseDocument::class); + + $query = new FetchQuery( + 'accompanying_course_document', + sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), + sprintf($classMetadata->getColumnName('date')), + $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() + ); + + $query->addWhereClause( + sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')), + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('? >= %s', $classMetadata->getColumnName('date')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('? < %s', $classMetadata->getColumnName('date')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf( + '(%s ilike ? OR %s ilike ?)', + $classMetadata->getColumnName('title'), + $classMetadata->getColumnName('description') + ), + [$content, $content], + [Types::STRING, Types::STRING] + ); + } + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php index c6db09513..02be9460f 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/FetchQueryToSqlBuilderTest.php @@ -13,6 +13,7 @@ namespace Chill\DocStoreBundle\Tests\GenericDoc; use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder; use Chill\DocStoreBundle\GenericDoc\FetchQuery; +use Doctrine\DBAL\Types\Types; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; /** @@ -29,15 +30,15 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase 'a.datecolumn', 'my_table a' ); - $query->addJoinClause('LEFT JOIN other b ON a.id = b.foreign_id', ['foo']); - $index = $query->addJoinClause('LEFT JOIN other c ON a.id = c.foreign_id', ['bar']); - $query->addJoinClause('LEFT JOIN other d ON a.id = d.foreign_id', ['bar_baz']); + $query->addJoinClause('LEFT JOIN other b ON a.id = b.foreign_id', ['foo'], [Types::STRING]); + $index = $query->addJoinClause('LEFT JOIN other c ON a.id = c.foreign_id', ['bar'], [Types::STRING]); + $query->addJoinClause('LEFT JOIN other d ON a.id = d.foreign_id', ['bar_baz'], [Types::STRING]); $query->removeJoinClause($index); - $query->addWhereClause('b.item = ?', ['baz']); - $index = $query->addWhereClause('b.cancel', [ 'foz']); + $query->addWhereClause('b.item = ?', ['baz'], [Types::STRING]); + $index = $query->addWhereClause('b.cancel', [ 'foz'], [Types::STRING]); $query->removeWhereClause($index); - ['sql' => $sql, 'params' => $params] = (new FetchQueryToSqlBuilder())->toSql($query); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); $filteredSql = implode(" ", array_filter( @@ -48,10 +49,41 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase self::assertEquals( "SELECT 'test' AS key, jsonb_build_object('id', a.column) AS identifiers, ". - "a.datecolumn AS doc_date FROM my_table a LEFT JOIN other b ON a.id = b.foreign_id LEFT JOIN other d ON a.id = d.foreign_id WHERE b.item = ?", + "a.datecolumn::date AS doc_date FROM my_table a LEFT JOIN other b ON a.id = b.foreign_id LEFT JOIN other d ON a.id = d.foreign_id WHERE b.item = ?", $filteredSql ); self::assertEquals(['foo', 'bar_baz', 'baz'], $params); + self::assertEquals([Types::STRING, Types::STRING, Types::STRING], $types); + + + + } + + public function testToSqlWithoutWhere(): void + { + $query = new FetchQuery( + 'test', + 'jsonb_build_object(\'id\', a.column)', + 'a.datecolumn', + 'my_table a' + ); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $filteredSql = + implode(" ", array_filter( + explode(" ", str_replace("\n", "", $sql)), + fn (string $tok) => $tok !== "" + )) + ; + + self::assertEquals( + "SELECT 'test' AS key, jsonb_build_object('id', a.column) AS identifiers, ". + "a.datecolumn::date AS doc_date FROM my_table a", + $filteredSql + ); + self::assertEquals([], $params); + self::assertEquals([], $types); } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php index 523e08c40..59cc24bf2 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php @@ -11,9 +11,15 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Tests\GenericDoc; +use Chill\DocStoreBundle\GenericDoc\FetchQuery; +use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Manager; +use Chill\DocStoreBundle\GenericDoc\ProviderForAccompanyingPeriodInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; +use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; /** @@ -22,21 +28,17 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; */ class ManagerTest extends KernelTestCase { - private Manager $manager; - + use ProphecyTrait; private EntityManagerInterface $em; + private Connection $connection; + protected function setUp(): void { self::bootKernel(); $this->em = self::$container->get(EntityManagerInterface::class); - - if (null !== $manager = self::$container->get(Manager::class)) { - $this->manager = $manager; - } else { - throw new \UnexpectedValueException("the manager was not found in the kernel"); - } + $this->connection = self::$container->get(Connection::class); } public function testCountByAccompanyingPeriod(): void @@ -49,8 +51,51 @@ class ManagerTest extends KernelTestCase throw new \UnexpectedValueException("period not found"); } - $nb = $this->manager->countDocForAccompanyingPeriod($period); + $manager = new Manager( + [new SimpleProvider()], + $this->connection, + ); + + $nb = $manager->countDocForAccompanyingPeriod($period); self::assertIsInt($nb); } + + public function testFindDocByAccompanyingPeriod(): void + { + $period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $period) { + throw new \UnexpectedValueException("period not found"); + } + + $manager = new Manager( + [new SimpleProvider()], + $this->connection, + ); + + foreach ($manager->findDocForAccompanyingPeriod($period) as $doc) { + self::assertInstanceOf(GenericDocDTO::class, $doc); + } + } +} + +final readonly class SimpleProvider implements ProviderForAccompanyingPeriodInterface +{ + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + return new FetchQuery( + 'accompanying_course_document', + sprintf('jsonb_build_object(\'id\', %s)', 'id'), + 'd', + '(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (id, d)', + ); + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return true; + } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php new file mode 100644 index 000000000..b88f8e36f --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php @@ -0,0 +1,78 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @dataProvider provideSearchArguments + */ + public function testWithoutAnyArgument(?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content = null): void + { + $period = $this->entityManager->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $period) { + throw new \UnexpectedValueException("period not found"); + } + + $security = $this->prophesize(Security::class); + $security->isGranted(AccompanyingCourseDocumentVoter::SEE, $period) + ->willReturn(true); + + $provider = new AccompanyingCourseDocumentProvider( + $security->reveal(), + $this->entityManager + ); + + $query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $this->entityManager->getConnection()->executeQuery($sql, $params, $types); + + self::assertTrue(true, "test that no errors occurs"); + } + + public function provideSearchArguments(): iterable + { + yield [null, null, null]; + yield [new \DateTimeImmutable('1 month ago'), null, null]; + yield [new \DateTimeImmutable('1 month ago'), new \DateTimeImmutable('now'), null]; + yield [null, null, 'test']; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 22161d2f6..16b0365d4 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -51,3 +51,7 @@ services: arguments: $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider + Chill\DocStoreBundle\GenericDoc\Providers\: + autowire: true + autoconfigure: true + resource: '../GenericDoc/Providers/' From e550817ded2d4fc38f3c04913f672be4626bf0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 24 May 2023 21:57:20 +0200 Subject: [PATCH 03/40] Render for generic doc --- .../ChillDocStoreBundle.php | 7 +- ...ericDocForAccompanyingPeriodController.php | 26 ++++- .../GenericDoc/GenericDocDTO.php | 6 +- ...orAccompanyingPeriodProviderInterface.php} | 2 +- .../GenericDoc/Manager.php | 5 +- ...nyingProviderCourseDocumentGenericDoc.php} | 8 +- ...anyingCourseDocumentGenericDocRenderer.php | 44 ++++++++ .../GenericDoc/Twig/GenericDocExtension.php | 28 +++++ .../Twig/GenericDocExtensionRuntime.php | 50 +++++++++ .../Twig/GenericDocRendererInterface.php | 24 ++++ .../accompanying_period_list.html.twig | 48 ++++++++ .../Tests/GenericDoc/ManagerTest.php | 8 +- ...AccompanyingCourseDocumentProviderTest.php | 4 +- .../ChillDocStoreBundle/config/services.yaml | 105 ++++++++++-------- 14 files changed, 299 insertions(+), 66 deletions(-) rename src/Bundle/ChillDocStoreBundle/GenericDoc/{ProviderForAccompanyingPeriodInterface.php => GenericDocForAccompanyingPeriodProviderInterface.php} (93%) rename src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/{AccompanyingCourseDocumentProvider.php => AccompanyingProviderCourseDocumentGenericDoc.php} (90%) create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig diff --git a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php index 44bc7d70d..bc659d146 100644 --- a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php +++ b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php @@ -11,7 +11,8 @@ declare(strict_types=1); namespace Chill\DocStoreBundle; -use Chill\DocStoreBundle\GenericDoc\ProviderForAccompanyingPeriodInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; +use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -19,7 +20,9 @@ class ChillDocStoreBundle extends Bundle { public function build(ContainerBuilder $container) { - $container->registerForAutoconfiguration(ProviderForAccompanyingPeriodInterface::class) + $container->registerForAutoconfiguration(GenericDocForAccompanyingPeriodProviderInterface::class) ->addTag('chill_doc_store.generic_doc_accompanying_period_provider'); + $container->registerForAutoconfiguration(GenericDocRendererInterface::class) + ->addTag('chill_doc_store.generic_doc_renderer'); } } diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php index 0a9175087..7403e2ebc 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php @@ -13,17 +13,22 @@ namespace Chill\DocStoreBundle\Controller; use Chill\DocStoreBundle\GenericDoc\Manager; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; +use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Templating\EngineInterface; final readonly class GenericDocForAccompanyingPeriodController { public function __construct( private Security $security, - private Manager $manager + private Manager $manager, + private PaginatorFactory $paginator, + private EngineInterface $twig, ) { } @@ -41,12 +46,21 @@ final readonly class GenericDocForAccompanyingPeriodController } $nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod); + $paginator = $this->paginator->create($nb); + $documents = $this->manager->findDocForAccompanyingPeriod( + $accompanyingPeriod, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage() + ); - foreach ($this->manager->findDocForAccompanyingPeriod($accompanyingPeriod) as $dto) { - dump($dto); - } - - return new Response($nb); + return new Response($this->twig->render( + '@ChillDocStore/GenericDoc/accompanying_period_list.html.twig', + [ + 'accompanyingCourse' => $accompanyingPeriod, + 'pagination' => $paginator, + 'documents' => iterator_to_array($documents), + ] + )); } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php index 6fb6139aa..e47814685 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php @@ -11,12 +11,16 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\GenericDoc; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; + class GenericDocDTO { public function __construct( public readonly string $key, public readonly array $identifiers, - public readonly \DateTimeImmutable $docDate + public readonly \DateTimeImmutable $docDate, + public AccompanyingPeriod|Person $linked, ) { } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php similarity index 93% rename from src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php rename to src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php index 935fe48a9..4702e74c7 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php @@ -13,7 +13,7 @@ namespace Chill\DocStoreBundle\GenericDoc; use Chill\PersonBundle\Entity\AccompanyingPeriod; -interface ProviderForAccompanyingPeriodInterface +interface GenericDocForAccompanyingPeriodProviderInterface { public function buildFetchQueryForAccompanyingPeriod( AccompanyingPeriod $accompanyingPeriod, diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php index d67ff6d0d..21189f55e 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php @@ -23,7 +23,7 @@ class Manager public function __construct( /** - * @var iterable + * @var iterable */ private readonly iterable $providersForAccompanyingPeriod, private readonly Connection $connection, @@ -81,7 +81,8 @@ class Manager yield new GenericDocDTO( $row['key'], json_decode($row['identifiers'], true, JSON_THROW_ON_ERROR), - new \DateTimeImmutable($row['doc_date']) + new \DateTimeImmutable($row['doc_date']), + $accompanyingPeriod, ); } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php similarity index 90% rename from src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php rename to src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php index 8f6a9fdad..fc0eb3b5f 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php @@ -14,15 +14,17 @@ namespace Chill\DocStoreBundle\GenericDoc\Providers; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; -use Chill\DocStoreBundle\GenericDoc\ProviderForAccompanyingPeriodInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; -final readonly class AccompanyingCourseDocumentProvider implements ProviderForAccompanyingPeriodInterface +final readonly class AccompanyingProviderCourseDocumentGenericDoc implements GenericDocForAccompanyingPeriodProviderInterface { + public const KEY = 'accompanying_course_document'; + public function __construct( private Security $security, private EntityManagerInterface $entityManager, @@ -34,7 +36,7 @@ final readonly class AccompanyingCourseDocumentProvider implements ProviderForAc $classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class); $query = new FetchQuery( - 'accompanying_course_document', + self::KEY, sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), sprintf($classMetadata->getColumnName('date')), $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php new file mode 100644 index 000000000..4e27f6335 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php @@ -0,0 +1,44 @@ +key === AccompanyingProviderCourseDocumentGenericDoc::KEY; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillDocStore/List/list_item.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), + 'accompanyingCourse' => $genericDocDTO->linked, + 'options' => $options, + ]; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php new file mode 100644 index 000000000..308d85cd7 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php @@ -0,0 +1,28 @@ + true, + 'is_safe' => ['html'], + ]) + ]; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php new file mode 100644 index 000000000..2dee0ed0b --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php @@ -0,0 +1,50 @@ + + */ + private iterable $renderers, + ) { + } + + /** + * @throws RuntimeError + * @throws SyntaxError + * @throws LoaderError + */ + public function renderGenericDoc(Environment $twig, GenericDocDTO $genericDocDTO, array $options = []): string + { + foreach ($this->renderers as $renderer) { + if ($renderer->supports($genericDocDTO)) { + return $twig->render( + $renderer->getTemplate($genericDocDTO, $options), + $renderer->getTemplateData($genericDocDTO, $options), + ); + } + } + + throw new \LogicException("no renderer found"); + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php new file mode 100644 index 000000000..940001f4a --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php @@ -0,0 +1,24 @@ + +

{{ 'Documents' }}

+ + {% if documents|length == 0 %} +

{{ 'No documents'|trans }}

+ {% else %} +
+ {% for document in documents %} + {{ document|chill_generic_doc_render }} + {% endfor %} +
+ {% endif %} + + {{ chill_pagination(pagination) }} + + {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %} + + {% endif %} + + +{% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php index 59cc24bf2..2bda8e2ae 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php @@ -15,7 +15,7 @@ use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Manager; -use Chill\DocStoreBundle\GenericDoc\ProviderForAccompanyingPeriodInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; @@ -52,7 +52,7 @@ class ManagerTest extends KernelTestCase } $manager = new Manager( - [new SimpleProvider()], + [new SimpleGenericDocProvider()], $this->connection, ); @@ -72,7 +72,7 @@ class ManagerTest extends KernelTestCase } $manager = new Manager( - [new SimpleProvider()], + [new SimpleGenericDocProvider()], $this->connection, ); @@ -82,7 +82,7 @@ class ManagerTest extends KernelTestCase } } -final readonly class SimpleProvider implements ProviderForAccompanyingPeriodInterface +final readonly class SimpleGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface { public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php index b88f8e36f..ee9978735 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php @@ -12,7 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Tests\GenericDoc\Providers; use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder; -use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentProvider; +use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingProviderCourseDocumentGenericDoc; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Types\Types; @@ -54,7 +54,7 @@ class AccompanyingCourseDocumentProviderTest extends KernelTestCase $security->isGranted(AccompanyingCourseDocumentVoter::SEE, $period) ->willReturn(true); - $provider = new AccompanyingCourseDocumentProvider( + $provider = new AccompanyingProviderCourseDocumentGenericDoc( $security->reveal(), $this->entityManager ); diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 16b0365d4..f242acc1c 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -2,56 +2,71 @@ parameters: # cl_chill_person.example.class: Chill\PersonBundle\Example services: - Chill\DocStoreBundle\Repository\: - autowire: true - autoconfigure: true - resource: "../Repository/" + Chill\DocStoreBundle\Repository\: + autowire: true + autoconfigure: true + resource: "../Repository/" - Chill\DocStoreBundle\Form\DocumentCategoryType: - class: Chill\DocStoreBundle\Form\DocumentCategoryType - arguments: ["%kernel.bundles%"] - tags: - - { name: form.type } + Chill\DocStoreBundle\Form\DocumentCategoryType: + class: Chill\DocStoreBundle\Form\DocumentCategoryType + arguments: [ "%kernel.bundles%" ] + tags: + - { name: form.type } - Chill\DocStoreBundle\Form\PersonDocumentType: - class: Chill\DocStoreBundle\Form\PersonDocumentType - autowire: true - autoconfigure: true - # arguments: - # - "@chill.main.helper.translatable_string" - tags: - - { name: form.type, alias: chill_docstorebundle_form_document } + Chill\DocStoreBundle\Form\PersonDocumentType: + class: Chill\DocStoreBundle\Form\PersonDocumentType + autowire: true + autoconfigure: true + # arguments: + # - "@chill.main.helper.translatable_string" + tags: + - { name: form.type, alias: chill_docstorebundle_form_document } - Chill\DocStoreBundle\Security\Authorization\: - resource: "./../Security/Authorization" - autowire: true - autoconfigure: true - tags: - - { name: chill.role } + Chill\DocStoreBundle\Security\Authorization\: + resource: "./../Security/Authorization" + autowire: true + autoconfigure: true + tags: + - { name: chill.role } - Chill\DocStoreBundle\Workflow\: - resource: './../Workflow/' - autoconfigure: true - autowire: true + Chill\DocStoreBundle\Workflow\: + resource: './../Workflow/' + autoconfigure: true + autowire: true - Chill\DocStoreBundle\Serializer\Normalizer\: - autowire: true - resource: '../Serializer/Normalizer/' - tags: - - { name: 'serializer.normalizer', priority: 16 } + Chill\DocStoreBundle\Serializer\Normalizer\: + autowire: true + resource: '../Serializer/Normalizer/' + tags: + - { name: 'serializer.normalizer', priority: 16 } - Chill\DocStoreBundle\Service\: - autowire: true - autoconfigure: true - resource: '../Service/' + Chill\DocStoreBundle\Service\: + autowire: true + autoconfigure: true + resource: '../Service/' - Chill\DocStoreBundle\GenericDoc\Manager: - autowire: true - autoconfigure: true - arguments: - $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider + Chill\DocStoreBundle\GenericDoc\Manager: + autowire: true + autoconfigure: true + arguments: + $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider - Chill\DocStoreBundle\GenericDoc\Providers\: - autowire: true - autoconfigure: true - resource: '../GenericDoc/Providers/' + Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension: + autoconfigure: true + autowire: true + + Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtensionRuntime: + autoconfigure: true + autowire: true + arguments: + $renderers: !tagged_iterator chill_doc_store.generic_doc_renderer + + Chill\DocStoreBundle\GenericDoc\Providers\: + autowire: true + autoconfigure: true + resource: '../GenericDoc/Providers/' + + Chill\DocStoreBundle\GenericDoc\Renderer\: + autowire: true + autoconfigure: true + resource: '../GenericDoc/Renderer/' From 2b5d007fda4c548533ece3b3e47462182e650a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 25 May 2023 11:09:26 +0200 Subject: [PATCH 04/40] Remove old doc index page, replace by the generic doc index page --- .../DocumentAccompanyingCourseController.php | 44 +--------------- .../ChillDocStoreBundle/Menu/MenuBuilder.php | 4 +- .../delete.html.twig | 4 +- .../AccompanyingCourseDocument/edit.html.twig | 4 +- .../index.html.twig | 52 ------------------- .../AccompanyingCourseDocument/new.html.twig | 2 +- .../AccompanyingCourseDocument/show.html.twig | 2 +- 7 files changed, 9 insertions(+), 103 deletions(-) delete mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php index 1bc3db221..8762050aa 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php @@ -39,10 +39,6 @@ class DocumentAccompanyingCourseController extends AbstractController protected TranslatorInterface $translator; - private AccompanyingCourseDocumentRepository $courseRepository; - - private PaginatorFactory $paginatorFactory; - /** * DocumentAccompanyingCourseController constructor. */ @@ -50,14 +46,10 @@ class DocumentAccompanyingCourseController extends AbstractController TranslatorInterface $translator, EventDispatcherInterface $eventDispatcher, AuthorizationHelper $authorizationHelper, - PaginatorFactory $paginatorFactory, - AccompanyingCourseDocumentRepository $courseRepository ) { $this->translator = $translator; $this->eventDispatcher = $eventDispatcher; $this->authorizationHelper = $authorizationHelper; - $this->paginatorFactory = $paginatorFactory; - $this->courseRepository = $courseRepository; } /** @@ -82,7 +74,7 @@ class DocumentAccompanyingCourseController extends AbstractController return $this->redirect($request->query->get('returnPath')); } - return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]); + return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]); } return $this->render( @@ -136,40 +128,6 @@ class DocumentAccompanyingCourseController extends AbstractController ); } - /** - * @Route("/", name="accompanying_course_document_index", methods="GET") - */ - public function index(AccompanyingPeriod $course): Response - { - $em = $this->getDoctrine()->getManager(); - - if (null === $course) { - throw $this->createNotFoundException('Accompanying period not found'); - } - - $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course); - - $total = $this->courseRepository->countByCourse($course); - $pagination = $this->paginatorFactory->create($total); - - $documents = $this->courseRepository - ->findBy( - ['course' => $course], - ['date' => 'DESC', 'id' => 'DESC'], - $pagination->getItemsPerPage(), - $pagination->getCurrentPageFirstItemNumber() - ); - - return $this->render( - 'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig', - [ - 'documents' => $documents, - 'accompanyingCourse' => $course, - 'pagination' => $pagination, - ] - ); - } - /** * @Route("/new", name="accompanying_course_document_new", methods="GET|POST") */ diff --git a/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php b/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php index 41d31ced3..95b23904a 100644 --- a/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php +++ b/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php @@ -62,9 +62,9 @@ final class MenuBuilder implements LocalMenuBuilderInterface if ($this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $course)) { $menu->addChild($this->translator->trans('Documents'), [ - 'route' => 'accompanying_course_document_index', + 'route' => 'chill_docstore_generic-doc_by-period_index', 'routeParameters' => [ - 'course' => $course->getId(), + 'id' => $course->getId(), ], ]) ->setExtras([ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig index c9bb608cf..d6f23d09d 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig @@ -31,8 +31,8 @@ 'title' : 'Delete document ?'|trans, 'display_content' : block('docdescription'), 'confirm_question' : 'Are you sure you want to remove this document ?'|trans, - 'cancel_route' : 'accompanying_course_document_index', - 'cancel_parameters' : {'course' : accompanyingCourse.id, 'id': document.id}, + 'cancel_route' : 'chill_docstore_generic-doc_by-period_index', + 'cancel_parameters' : {'id' : accompanyingCourse.id}, 'form' : delete_form } ) }} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig index 0ca5661fc..326814502 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig @@ -21,7 +21,7 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig deleted file mode 100644 index 7a013260c..000000000 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} - -{% set activeRouteKey = '' %} - -{% block title %} - {{ 'Documents' }} -{% endblock %} - -{% block js %} - {{ parent() }} - {{ encore_entry_script_tags('mod_docgen_picktemplate') }} - {{ encore_entry_script_tags('mod_entity_workflow_pick') }} - {{ encore_entry_script_tags('mod_document_action_buttons_group') }} -{% endblock %} - -{% block css %} - {{ parent() }} - {{ encore_entry_link_tags('mod_docgen_picktemplate') }} - {{ encore_entry_link_tags('mod_entity_workflow_pick') }} - {{ encore_entry_link_tags('mod_document_action_buttons_group') }} -{% endblock %} - -{% block content %} -
-

{{ 'Documents' }}

- - {% if documents|length == 0 %} -

{{ 'No documents'|trans }}

- {% else %} -
- {% for document in documents %} - {% include '@ChillDocStore/List/list_item.html.twig' %} - {% endfor %} -
- {% endif %} - - {{ chill_pagination(pagination) }} - -
- - {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %} - - {% endif %} - -
-{% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig index 01be1a5d7..3fb692c78 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig @@ -25,7 +25,7 @@
  • - + {{ 'Back to the list' | trans }}
  • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig index 3c62451a9..9590dbb78 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig @@ -46,7 +46,7 @@
    • - + {{ 'Back to the list' | trans }}
    • From 08874d734ed08b6135fe91da1ad42e00e3e82f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 26 May 2023 21:58:52 +0200 Subject: [PATCH 05/40] [generic doc] add doc provider and renderer for evaluation document --- .../GenericDoc/Manager.php | 2 +- ...AccompanyingCourseDocumentProviderTest.php | 5 +- .../GenericDoc/evaluation_document.html.twig | 75 +++++++++++++ ...PeriodWorkEvaluationGenericDocProvider.php | 105 ++++++++++++++++++ ...PeriodWorkEvaluationGenericDocRenderer.php | 42 +++++++ ...odWorkEvaluationGenericDocProviderTest.php | 79 +++++++++++++ 6 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig create mode 100644 src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php create mode 100644 src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProviderTest.php diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php index 21189f55e..1a0287a71 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php @@ -73,7 +73,7 @@ class Manager ): iterable { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); - $runSql = "{$sql} LIMIT ? OFFSET ?"; + $runSql = "{$sql} ORDER BY doc_date DESC LIMIT ? OFFSET ?"; $runParams = [...$params, ...[$limit, $offset]]; $runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]]; diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php index ee9978735..8c6bd524d 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php @@ -63,9 +63,10 @@ class AccompanyingCourseDocumentProviderTest extends KernelTestCase ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); - $this->entityManager->getConnection()->executeQuery($sql, $params, $types); + $nb = $this->entityManager->getConnection()->executeQuery('SELECT COUNT(*) FROM ('.$sql.') AS sq', $params, $types) + ->fetchOne(); - self::assertTrue(true, "test that no errors occurs"); + self::assertIsInt($nb); } public function provideSearchArguments(): iterable diff --git a/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig new file mode 100644 index 000000000..255139bb4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig @@ -0,0 +1,75 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set w = document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork %} + +
      +
      +
      + {% if document.storedObject.isPending %} +
      {{ 'docgen.Doc generation is pending'|trans }}
      + {% elseif document.storedObject.isFailure %} +
      {{ 'docgen.Doc generation failed'|trans }}
      + {% endif %} +
      + {{ document.title }} +
      + {% if document.storedObject.type is not empty %} +
      + {{ mm.mimeIcon(document.storedObject.type) }} +
      + {% endif %} + {% if document.storedObject.hasTemplate %} +
      +

      {{ document.storedObject.template.name|localize_translatable_string }}

      +
      + {% endif %} +
      + +
      +
      +
      + {{ document.storedObject.createdAt|format_date('short') }} +
      +
      +
      +
      + +
      +
      +

      + + {{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }} + +

      +
      +
      + +
      +
      + {{ mmm.createdBy(document) }} +
      +
        +
      • + {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} +
      • + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_DOCUMENT_SHOW', document) %} +
      • + {{ document.storedObject|chill_document_button_group(document.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork)) }} +
      • + {% endif %} + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE', w)%} +
      • + +
      • + {% endif %} + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %} +
      • + +
      • + {% endif %} +
      + +
      +
      diff --git a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php new file mode 100644 index 000000000..834375bfc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php @@ -0,0 +1,105 @@ +entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument::class); + $storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class); + $evaluationMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWorkEvaluation::class); + $accompanyingPeriodWorkMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWork::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', apwed.%s)", $classMetadata->getColumnName('id')), + sprintf('apwed.'.$storedObjectMetadata->getColumnName('createdAt')), + $classMetadata->getTableName().' AS apwed' + ); + $query->addJoinClause(sprintf( + 'JOIN %s doc_store ON doc_store.%s = apwed.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + )); + $query->addJoinClause(sprintf( + 'JOIN %s evaluation ON apwed.%s = evaluation.%s', + $evaluationMetadata->getTableName(), + $classMetadata->getAssociationMapping('accompanyingPeriodWorkEvaluation')['joinColumns'][0]['name'], + $evaluationMetadata->getColumnName('id') + )); + $query->addJoinClause(sprintf( + 'JOIN %s action ON evaluation.%s = action.%s', + $accompanyingPeriodWorkMetadata->getTableName(), + $evaluationMetadata->getAssociationMapping('accompanyingPeriodWork')['joinColumns'][0]['name'], + $accompanyingPeriodWorkMetadata->getColumnName('id') + )); + + $query->addWhereClause( + sprintf('action.%s = ?', $accompanyingPeriodWorkMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s <= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s > ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf('apwed.%s ilike ?', $classMetadata->getColumnName('title')), + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $accompanyingPeriod); + } + + +} diff --git a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php new file mode 100644 index 000000000..5da8297fb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php @@ -0,0 +1,42 @@ +key === AccompanyingPeriodWorkEvaluationGenericDocProvider::KEY; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillPerson/GenericDoc/evaluation_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id']) + ]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProviderTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProviderTest.php new file mode 100644 index 000000000..c9ed96581 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProviderTest.php @@ -0,0 +1,79 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @dataProvider provideSearchArguments + */ + public function testBuildFetchQueryForAccompanyingPeriod( + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null + ): void { + $period = $this->entityManager->createQuery("SELECT a FROM " . AccompanyingPeriod::class . ' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $period) { + throw new \RuntimeException('no accompanying period in databasee'); + } + + $security = $this->prophesize(Security::class); + + $provider = new AccompanyingPeriodWorkEvaluationGenericDocProvider( + $security->reveal(), + $this->entityManager + ); + + $query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->executeQuery( + 'SELECT COUNT(*) FROM ('.$sql.') AS sq', + $params, + $types + )->fetchOne(); + + self::assertIsInt($nb, "Test that there are no errors"); + } + + public function provideSearchArguments(): iterable + { + yield [null, null, null]; + yield [new \DateTimeImmutable('1 month ago'), null, null]; + yield [new \DateTimeImmutable('1 month ago'), new \DateTimeImmutable('now'), null]; + yield [null, null, 'test']; + } +} From a3d3588b75d37d7573ffe9626aa94ac7a6b57f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 26 May 2023 22:01:02 +0200 Subject: [PATCH 06/40] DX: fix json_decode signature --- src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php index 1a0287a71..498693206 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php @@ -80,7 +80,7 @@ class Manager foreach($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) { yield new GenericDocDTO( $row['key'], - json_decode($row['identifiers'], true, JSON_THROW_ON_ERROR), + json_decode($row['identifiers'], true, 512, JSON_THROW_ON_ERROR), new \DateTimeImmutable($row['doc_date']), $accompanyingPeriod, ); From eb107f5a1506b60afeb28d535f0688b833ad6264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 30 May 2023 12:46:05 +0200 Subject: [PATCH 07/40] add filter for generic doc + fix issues in filter --- ...ericDocForAccompanyingPeriodController.php | 38 +++++++++++-- .../GenericDoc/GenericDocDTO.php | 8 +-- .../GenericDoc/Manager.php | 53 ++++++++++++++----- ...anyingProviderCourseDocumentGenericDoc.php | 6 +-- .../accompanying_period_list.html.twig | 2 + .../Tests/GenericDoc/ManagerTest.php | 7 ++- .../translations/messages.fr.yml | 6 +++ .../Templating/Listing/FilterOrderHelper.php | 5 ++ ...PeriodWorkEvaluationGenericDocProvider.php | 4 +- .../translations/messages.fr.yml | 5 ++ 10 files changed, 109 insertions(+), 25 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php index 7403e2ebc..70c41db50 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php @@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Controller; use Chill\DocStoreBundle\GenericDoc\Manager; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Component\HttpFoundation\Response; @@ -25,9 +26,10 @@ use Symfony\Component\Templating\EngineInterface; final readonly class GenericDocForAccompanyingPeriodController { public function __construct( - private Security $security, + private FilterOrderHelperFactory $filterOrderHelperFactory, private Manager $manager, private PaginatorFactory $paginator, + private Security $security, private EngineInterface $twig, ) { } @@ -45,12 +47,41 @@ final readonly class GenericDocForAccompanyingPeriodController throw new AccessDeniedHttpException("not allowed to see the documents for accompanying period"); } - $nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod); + $filterBuilder = $this->filterOrderHelperFactory + ->create(self::class) + ->addSearchBox() + ->addDateRange('dateRange', 'generic_doc.filter.date-range'); + + if ([] !== $places = $this->manager->placesForAccompanyingPeriod($accompanyingPeriod)) { + $filterBuilder->addCheckbox('places', $places, [], array_map( + static fn (string $k) => 'generic_doc.filter.keys.' . $k, + $places + )); + } + + $filter = $filterBuilder + ->build(); + + ['to' => $endDate, 'from' => $startDate ] = $filter->getDateRangeData('dateRange'); + $content = $filter->getQueryString(); + + $nb = $this->manager->countDocForAccompanyingPeriod( + $accompanyingPeriod, + $startDate, + $endDate, + $content, + $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : [] + ); $paginator = $this->paginator->create($nb); + $documents = $this->manager->findDocForAccompanyingPeriod( $accompanyingPeriod, $paginator->getCurrentPageFirstItemNumber(), - $paginator->getItemsPerPage() + $paginator->getItemsPerPage(), + $startDate, + $endDate, + $content, + $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : [] ); return new Response($this->twig->render( @@ -59,6 +90,7 @@ final readonly class GenericDocForAccompanyingPeriodController 'accompanyingCourse' => $accompanyingPeriod, 'pagination' => $paginator, 'documents' => iterator_to_array($documents), + 'filter' => $filter, ] )); } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php index e47814685..25a96750c 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php @@ -14,12 +14,12 @@ namespace Chill\DocStoreBundle\GenericDoc; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; -class GenericDocDTO +final readonly class GenericDocDTO { public function __construct( - public readonly string $key, - public readonly array $identifiers, - public readonly \DateTimeImmutable $docDate, + public string $key, + public array $identifiers, + public \DateTimeImmutable $docDate, public AccompanyingPeriod|Person $linked, ) { } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php index 498693206..dad432e3e 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php @@ -32,6 +32,7 @@ class Manager } /** + * @param list $places * @throws Exception */ public function countDocForAccompanyingPeriod( @@ -39,16 +40,16 @@ class Manager ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - ?string $origin = null + array $places = [] ): int { - ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places); if ($sql === '') { return 0; } $countSql = "SELECT count(*) AS c FROM ({$sql}) AS sq"; - $result = $this->connection->executeQuery($countSql, $params); + $result = $this->connection->executeQuery($countSql, $params, $types); $number = $result->fetchOne(); @@ -60,6 +61,7 @@ class Manager } /** + * @param list $places places to search. When empty, search in all places * @throws Exception */ public function findDocForAccompanyingPeriod( @@ -69,15 +71,19 @@ class Manager ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - ?string $origin = null + array $places = [] ): iterable { - ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places); + + if ($sql === '') { + return []; + } $runSql = "{$sql} ORDER BY doc_date DESC LIMIT ? OFFSET ?"; $runParams = [...$params, ...[$limit, $offset]]; $runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]]; - foreach($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) { + foreach ($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) { yield new GenericDocDTO( $row['key'], json_decode($row['identifiers'], true, 512, JSON_THROW_ON_ERROR), @@ -87,14 +93,34 @@ class Manager } } + public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array + { + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod); + + if ($sql === '') { + return []; + } + + $runSql = "SELECT DISTINCT key FROM ({$sql}) AS sq"; + + $keys = []; + + foreach ($this->connection->iterateAssociative($runSql, $params, $types) as $k) { + $keys[] = $k['key']; + } + + return $keys; + } + /** + * @param list $places places to search. When empty, search in all places */ private function buildUnionQuery( AccompanyingPeriod|Person $linked, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, - ?string $origin = null + array $places = [], ): array { $sql = []; $params = []; @@ -105,17 +131,20 @@ class Manager if (!$provider->isAllowedForAccompanyingPeriod($linked)) { continue; } + $query = $provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content); - ['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder - ->toSql($provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content, $origin)); - $params = [...$params, ...$p]; - $types = [...$types, ...$t]; + if ([] !== $places and !in_array($query->getSelectKeyString(), $places, true)) { + continue; + } + + ['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder->toSql($query); $sql[] = $q; + $params = [...$params, ...$p]; + $types = [...$types, ...$t]; } } return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types]; } - } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php index fc0eb3b5f..970ce646d 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php @@ -50,7 +50,7 @@ final readonly class AccompanyingProviderCourseDocumentGenericDoc implements Gen if (null !== $startDate) { $query->addWhereClause( - sprintf('? >= %s', $classMetadata->getColumnName('date')), + sprintf('? <= %s', $classMetadata->getColumnName('date')), [$startDate], [Types::DATE_IMMUTABLE] ); @@ -58,7 +58,7 @@ final readonly class AccompanyingProviderCourseDocumentGenericDoc implements Gen if (null !== $endDate) { $query->addWhereClause( - sprintf('? < %s', $classMetadata->getColumnName('date')), + sprintf('? >= %s', $classMetadata->getColumnName('date')), [$endDate], [Types::DATE_IMMUTABLE] ); @@ -71,7 +71,7 @@ final readonly class AccompanyingProviderCourseDocumentGenericDoc implements Gen $classMetadata->getColumnName('title'), $classMetadata->getColumnName('description') ), - [$content, $content], + ['%' . $content . '%', '%' . $content . '%'], [Types::STRING, Types::STRING] ); } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig index b2967d2f0..f76e4b984 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig @@ -22,6 +22,8 @@

      {{ 'Documents' }}

      + {{ filter|chill_render_filter_order_helper }} + {% if documents|length == 0 %}

      {{ 'No documents'|trans }}

      {% else %} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php index 2bda8e2ae..597aea84a 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php @@ -18,6 +18,7 @@ use Chill\DocStoreBundle\GenericDoc\Manager; use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -86,12 +87,16 @@ final readonly class SimpleGenericDocProvider implements GenericDocForAccompanyi { public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { - return new FetchQuery( + $query = new FetchQuery( 'accompanying_course_document', sprintf('jsonb_build_object(\'id\', %s)', 'id'), 'd', '(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (id, d)', ); + + $query->addWhereClause('d > ?', [new \DateTimeImmutable('2023-01-01')], [Types::DATE_IMMUTABLE]); + + return $query; } public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 1d06c58e4..1dde57eee 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -22,6 +22,12 @@ Any description: Aucune description document: Any title: Aucun titre +generic_doc: + filter: + keys: + accompanying_course_document: Document du parcours + date-range: Date du document + # delete Delete document ?: Supprimer le document ? Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ? diff --git a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php index 367cc0861..94aa3c50b 100644 --- a/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php +++ b/src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php @@ -91,6 +91,11 @@ class FilterOrderHelper return $this->checkboxes; } + public function hasCheckBox(string $name): bool + { + return array_key_exists($name, $this->checkboxes); + } + /** * @return array<'to': DateTimeImmutable, 'from': DateTimeImmutable> */ diff --git a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php index 834375bfc..883836547 100644 --- a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php +++ b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php @@ -71,7 +71,7 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen if (null !== $startDate) { $query->addWhereClause( - sprintf('doc_store.%s <= ?', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), [$startDate], [Types::DATE_IMMUTABLE] ); @@ -79,7 +79,7 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen if (null !== $endDate) { $query->addWhereClause( - sprintf('doc_store.%s > ?', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), [$endDate], [Types::DATE_IMMUTABLE] ); diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 341136f65..a344ac50d 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1231,3 +1231,8 @@ social_action: social_issue: and children: et dérivés + +generic_doc: + filter: + keys: + accompanying_period_work_evaluation_document: Document des actions d'accompagnement From 90a5a735aafebb58ba16d8ea17e2383fe0d2f13f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 14:38:16 +0200 Subject: [PATCH 08/40] FIX [route] adjust to using new route name in redirect --- .../Controller/DocumentAccompanyingCourseController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php index 8762050aa..384eeb510 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php @@ -160,7 +160,7 @@ class DocumentAccompanyingCourseController extends AbstractController $this->addFlash('success', $this->translator->trans('The document is successfully registered')); - return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]); + return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]); } if ($form->isSubmitted() && !$form->isValid()) { From 101cca8662e0aef2444cfd189975cec118fd98da Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 16:56:20 +0200 Subject: [PATCH 09/40] FEATURE [genericDoc] generic doc interface implemented for rendez-vous --- .../GenericDoc/calendar_document.html.twig | 72 +++++++++++++ ...anyingPeriodCalendarGenericDocProvider.php | 101 ++++++++++++++++++ ...anyingPeriodCalendarGenericDocRenderer.php | 44 ++++++++ .../translations/messages.fr.yml | 2 + .../translations/messages.fr.yml | 1 + 5 files changed, 220 insertions(+) create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php create mode 100644 src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig new file mode 100644 index 000000000..712b362f5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig @@ -0,0 +1,72 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set c = document.calendar %} + +
      +
      +
      + {% if document.storedObject.isPending %} +
      {{ 'docgen.Doc generation is pending'|trans }}
      + {% elseif document.storedObject.isFailure %} +
      {{ 'docgen.Doc generation failed'|trans }}
      + {% endif %} +
      + {{ document.storedObject.title }} +
      +
      + {{ 'chill_calendar.Document'|trans }} +
      + {% if document.storedObject.hasTemplate %} +
      +

      {{ document.storedObject.template.name|localize_translatable_string }}

      +
      + {% endif %} +
      + +
      +
      +
      + {{ document.storedObject.createdAt|format_date('short') }} +
      +
      +
      +
      + +
      +
      +

      + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} +

      +
      +
      + +
      +
      + {{ mmm.createdBy(document) }} +
      +
        +
      • + {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} +
      • + {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %} +
      • + {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} +
      • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %} +
      • + +
      • + {% endif %} +
      + +
      +
      diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php new file mode 100644 index 000000000..ff2cdbcf5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -0,0 +1,101 @@ +security = $security; + $this->em = $entityManager; + } + + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + 'JOIN chill_doc.stored_object doc_store ON doc_store.id = cd.storedobject_id' + ); + + $query->addJoinClause( + 'JOIN chill_calendar.calendar calendar ON calendar.id = cd.calendar_id' + ); + + $query->addWhereClause( + 'calendar.accompanyingperiod_id = ?', + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + 'doc_store.title ilike ?', + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); + } + + +} diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php new file mode 100644 index 000000000..0f680797c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php @@ -0,0 +1,44 @@ +repository = $calendarDocRepository; + } + + public function supports(GenericDocDTO $genericDocDTO, $options = []): bool + { + return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillCalendar/GenericDoc/calendar_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index eb02be280..5e1fc971a 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -43,6 +43,7 @@ crud: title_edit: Modifier le motif d'annulation chill_calendar: + Document: Document d'un rendez-vous form: The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement. Create for referrer: Créer pour le référent @@ -65,6 +66,7 @@ chill_calendar: Document outdated: La date et l'heure du rendez-vous ont été modifiés après la création du document + remote_ms_graph: freebusy_statuses: busy: Occupé diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 1dde57eee..4fa41f180 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -26,6 +26,7 @@ generic_doc: filter: keys: accompanying_course_document: Document du parcours + accompanying_period_calendar_document: Document des rendez-vous date-range: Date du document # delete From d09e5d33db2b3d2001208d1097b0915412b9137f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 16:59:16 +0200 Subject: [PATCH 10/40] WIP [genericDoc][activity] implementing generic doc for activities --- ...anyingPeriodActivityGenericDocProvider.php | 110 ++++++++++++++++++ ...anyingPeriodActivityGenericDocRenderer.php | 45 +++++++ 2 files changed, 155 insertions(+) create mode 100644 src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php create mode 100644 src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php new file mode 100644 index 000000000..fc7484edf --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -0,0 +1,110 @@ +em = $entityManager; + $this->security = $security; + } + + /** + * @param AccompanyingPeriod $accompanyingPeriod + * @param DateTimeImmutable|null $startDate + * @param DateTimeImmutable|null $endDate + * @param string|null $content + * @param string|null $origin + * @return FetchQueryInterface + * @throws MappingException + */ + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); +// $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $query = new FetchQuery( + self::KEY, + "jsonb_build_object('id', doc_obj.id)", + 'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'), + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj' + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + 'activity.accompanyingperiod_id = ?', + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + 'doc_obj.title ilike ?', + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + /** + * @param AccompanyingPeriod $accompanyingPeriod + * @return bool + */ + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); + } +} diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php new file mode 100644 index 000000000..eddcc6828 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -0,0 +1,45 @@ +repository = $storedObjectRepository; + } + + public function supports(GenericDocDTO $genericDocDTO, $options = []): bool + { + return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillCalendar/GenericDoc/activity_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + ]; + } +} + From bd074ebade229eaf747dd9be8f914e0f09f883a9 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 17:50:32 +0200 Subject: [PATCH 11/40] FEATURE [genericDoc][calendar] use metadatas --- ...anyingPeriodCalendarGenericDocProvider.php | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php index ff2cdbcf5..e653cfc25 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -21,6 +21,7 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInter use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Security\Core\Security; final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface @@ -39,6 +40,9 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo $this->em = $entityManager; } + /** + * @throws MappingException + */ public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); @@ -52,15 +56,23 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' ); $query->addJoinClause( - 'JOIN chill_doc.stored_object doc_store ON doc_store.id = cd.storedobject_id' - ); + sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + )); $query->addJoinClause( - 'JOIN chill_calendar.calendar calendar ON calendar.id = cd.calendar_id' + sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) ); $query->addWhereClause( - 'calendar.accompanyingperiod_id = ?', + sprintf('calendar.%s = ?', + $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), [$accompanyingPeriod->getId()], [Types::INTEGER] ); @@ -83,7 +95,7 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo if (null !== $content) { $query->addWhereClause( - 'doc_store.title ilike ?', + sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), ['%' . $content . '%'], [Types::STRING] ); From c245ffe559196e37eb38dd5aeb41f83fa13212a7 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 18:14:32 +0200 Subject: [PATCH 12/40] WIP [genericDoc][activity] add repository method to get activity linked to storedObject --- .../Repository/ActivityRepository.php | 12 ++++ .../GenericDoc/activity_document.html.twig | 72 +++++++++++++++++++ ...anyingPeriodActivityGenericDocProvider.php | 3 +- ...anyingPeriodActivityGenericDocRenderer.php | 13 ++-- 4 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index 5a6e16cd5..02658b03d 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\Query\Expr; use Doctrine\Persistence\ManagerRegistry; /** @@ -97,4 +98,15 @@ class ActivityRepository extends ServiceEntityRepository return $qb->getQuery()->getResult(); } + + public function findOneByDocument(int $documentId): Activity + { + $qb = $this->createQueryBuilder('a'); + $qb->select('a'); + + $qb->innerJoin('a.documents', 'd', Expr\Join::WITH, 'd.storedobject_id = :documentId'); + $qb->setParameter('documentId', $documentId); + + return $qb->getQuery()->getResult(); + } } diff --git a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig new file mode 100644 index 000000000..aeaea0872 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig @@ -0,0 +1,72 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set a = document.calendar %} + +
      +
      +
      + {% if document.storedObject.isPending %} +
      {{ 'docgen.Doc generation is pending'|trans }}
      + {% elseif document.storedObject.isFailure %} +
      {{ 'docgen.Doc generation failed'|trans }}
      + {% endif %} +
      + {{ document.storedObject.title }} +
      +
      + {{ 'chill_calendar.Document'|trans }} +
      + {% if document.storedObject.hasTemplate %} +
      +

      {{ document.storedObject.template.name|localize_translatable_string }}

      +
      + {% endif %} +
      + +
      +
      +
      + {{ document.storedObject.createdAt|format_date('short') }} +
      +
      +
      +
      + +
      +
      +

      + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} +

      +
      +
      + +
      +
      + {{ mmm.createdBy(document) }} +
      +
        +
      • + {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} +
      • + {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %} +
      • + {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} +
      • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %} +
      • + +
      • + {% endif %} +
      + +
      +
      diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php index fc7484edf..bf7a022f1 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -27,7 +27,7 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo { public const KEY = 'accompanying_period_activity_document'; - private EntityManagerInterface $em; + private EntityManagerInterface $em; private Security $security; @@ -49,7 +49,6 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); -// $activityMetadata = $this->em->getClassMetadata(Activity::class); $query = new FetchQuery( self::KEY, diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php index eddcc6828..f243fb941 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\GenericDoc\Renderers; +use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; @@ -18,11 +19,14 @@ use Chill\DocStoreBundle\Repository\StoredObjectRepository; final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface { - private StoredObjectRepository $repository; + private StoredObjectRepository $objectRepository; - public function __construct(StoredObjectRepository $storedObjectRepository) + private ActivityRepository $activityRepository; + + public function __construct(StoredObjectRepository $storedObjectRepository, ActivityRepository $activityRepository) { - $this->repository = $storedObjectRepository; + $this->objectRepository = $storedObjectRepository; + $this->activityRepository = $activityRepository; } public function supports(GenericDocDTO $genericDocDTO, $options = []): bool @@ -38,7 +42,8 @@ final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRe public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { return [ - 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + 'activity' => $this->activityRepository->findOneByDocument($genericDocDTO->identifiers['id']), + 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']) ]; } } From 40af1e64ac5dc612833b95752e86244cf616a84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 30 May 2023 20:48:35 +0200 Subject: [PATCH 13/40] [generic doc] listing generic doc for person --- .../ChillDocStoreBundle.php | 3 + .../Controller/GenericDocForPerson.php | 95 +++++++++++++ ...ForAccompanyingPeriodProviderInterface.php | 2 - .../GenericDocForPersonProviderInterface.php | 31 ++++ .../GenericDoc/Manager.php | 108 +++++++++++--- .../PersonDocumentGenericDocProvider.php | 51 +++++++ ...anyingCourseDocumentGenericDocRenderer.php | 20 ++- .../PersonDocumentACLAwareRepository.php | 134 ++++++++++++++++-- ...sonDocumentACLAwareRepositoryInterface.php | 14 ++ .../Repository/PersonDocumentRepository.php | 49 +++++++ .../views/GenericDoc/person_list.html.twig | 74 ++++++++++ .../Tests/GenericDoc/ManagerTest.php | 119 +++++++++++++++- .../PersonDocumentGenericDocProviderTest.php | 84 +++++++++++ .../PersonDocumentACLAwareRepositoryTest.php | 99 +++++++++++++ .../ChillDocStoreBundle/config/services.yaml | 1 + 15 files changed, 842 insertions(+), 42 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php create mode 100644 src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/PersonDocumentGenericDocProviderTest.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php diff --git a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php index bc659d146..8dcbe72c3 100644 --- a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php +++ b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle; use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -22,6 +23,8 @@ class ChillDocStoreBundle extends Bundle { $container->registerForAutoconfiguration(GenericDocForAccompanyingPeriodProviderInterface::class) ->addTag('chill_doc_store.generic_doc_accompanying_period_provider'); + $container->registerForAutoconfiguration(GenericDocForPersonProviderInterface::class) + ->addTag('chill_doc_store.generic_doc_person_provider'); $container->registerForAutoconfiguration(GenericDocRendererInterface::class) ->addTag('chill_doc_store.generic_doc_renderer'); } diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php new file mode 100644 index 000000000..3484e0904 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php @@ -0,0 +1,95 @@ +security->isGranted(PersonDocumentVoter::SEE, $person)) { + throw new AccessDeniedHttpException("not allowed to see the documents for person"); + } + + $filterBuilder = $this->filterOrderHelperFactory + ->create(self::class) + ->addSearchBox() + ->addDateRange('dateRange', 'generic_doc.filter.date-range'); + + if ([] !== $places = $this->manager->placesForPerson($person)) { + $filterBuilder->addCheckbox('places', $places, [], array_map( + static fn (string $k) => 'generic_doc.filter.keys.' . $k, + $places + )); + } + + $filter = $filterBuilder + ->build(); + + ['to' => $endDate, 'from' => $startDate ] = $filter->getDateRangeData('dateRange'); + $content = $filter->getQueryString(); + + $nb = $this->manager->countDocForPerson( + $person, + $startDate, + $endDate, + $content, + $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : [] + ); + $paginator = $this->paginator->create($nb); + + $documents = $this->manager->findDocForPerson( + $person, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage(), + $startDate, + $endDate, + $content, + $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : [] + ); + + return new Response($this->twig->render( + '@ChillDocStore/GenericDoc/person_list.html.twig', + [ + 'person' => $person, + 'pagination' => $paginator, + 'documents' => iterator_to_array($documents), + 'filter' => $filter, + ] + )); + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php index 4702e74c7..0d3cb1c32 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php @@ -25,8 +25,6 @@ interface GenericDocForAccompanyingPeriodProviderInterface /** * Return true if the user is allowed to see some documents for this provider. - * - * @return bool */ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool; diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php new file mode 100644 index 000000000..027afe834 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForPersonProviderInterface.php @@ -0,0 +1,31 @@ + */ - private readonly iterable $providersForAccompanyingPeriod, - private readonly Connection $connection, + private iterable $providersForAccompanyingPeriod, + + /** + * @var iterable + */ + private iterable $providersForPerson, + private Connection $connection, ) { $this->builder = new FetchQueryToSqlBuilder(); } @@ -44,6 +49,11 @@ class Manager ): int { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places); + return $this->countDoc($sql, $params, $types); + } + + private function countDoc(string $sql, array $params, array $types): int + { if ($sql === '') { return 0; } @@ -60,8 +70,21 @@ class Manager return $number; } + public function countDocForPerson( + Person $person, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + array $places = [] + ): int { + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places); + + return $this->countDoc($sql, $params, $types); + } + /** * @param list $places places to search. When empty, search in all places + * @return iterable * @throws Exception */ public function findDocForAccompanyingPeriod( @@ -75,6 +98,15 @@ class Manager ): iterable { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places); + return $this->findDocs($accompanyingPeriod, $sql, $params, $types, $offset, $limit); + } + + /** + * @throws \JsonException + * @throws Exception + */ + private function findDocs(AccompanyingPeriod|Person $linked, string $sql, array $params, array $types, int $offset, int $limit): iterable + { if ($sql === '') { return []; } @@ -88,20 +120,50 @@ class Manager $row['key'], json_decode($row['identifiers'], true, 512, JSON_THROW_ON_ERROR), new \DateTimeImmutable($row['doc_date']), - $accompanyingPeriod, + $linked, ); } } + /** + * @param list $places places to search. When empty, search in all places + * @return iterable + */ + public function findDocForPerson( + Person $person, + int $offset = 0, + int $limit = 20, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + array $places = [] + ): iterable { + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places); + + return $this->findDocs($person, $sql, $params, $types, $offset, $limit); + } + + public function placesForPerson(Person $person): array + { + ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person); + + return $this->places($sql, $params, $types); + } + public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array { ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod); + return $this->places($sql, $params, $types); + } + + private function places(string $sql, array $params, array $types): array + { if ($sql === '') { return []; } - $runSql = "SELECT DISTINCT key FROM ({$sql}) AS sq"; + $runSql = "SELECT DISTINCT key FROM ({$sql}) AS sq ORDER BY key"; $keys = []; @@ -122,28 +184,38 @@ class Manager ?string $content = null, array $places = [], ): array { - $sql = []; - $params = []; - $types = []; + $queries = []; if ($linked instanceof AccompanyingPeriod) { foreach ($this->providersForAccompanyingPeriod as $provider) { if (!$provider->isAllowedForAccompanyingPeriod($linked)) { continue; } - $query = $provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content); - - if ([] !== $places and !in_array($query->getSelectKeyString(), $places, true)) { + $queries[] = $provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content); + } + } else { + foreach ($this->providersForPerson as $provider) { + if (!$provider->isAllowedForPerson($linked)) { continue; } - - ['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder->toSql($query); - - $sql[] = $q; - $params = [...$params, ...$p]; - $types = [...$types, ...$t]; + $queries[] = $provider->buildFetchQueryForPerson($linked, $startDate, $endDate, $content); } } + $sql = []; + $params = []; + $types = []; + + foreach ($queries as $query) { + if ([] !== $places and !in_array($query->getSelectKeyString(), $places, true)) { + continue; + } + + ['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder->toSql($query); + + $sql[] = $q; + $params = [...$params, ...$p]; + $types = [...$types, ...$t]; + } return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types]; } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php new file mode 100644 index 000000000..08a0df960 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php @@ -0,0 +1,51 @@ +personDocumentACLAwareRepository->buildFetchQueryForPerson( + $person, + $startDate, + $endDate, + $content + ); + } + + public function isAllowedForPerson(Person $person): bool + { + return $this->security->isGranted(PersonDocumentVoter::SEE, $person); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php index 4e27f6335..a533b577a 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php @@ -12,20 +12,25 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\GenericDoc\Renderer; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; +use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingProviderCourseDocumentGenericDoc; use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository; +use Chill\DocStoreBundle\Repository\PersonDocumentRepository; +use Chill\PersonBundle\Entity\AccompanyingPeriod; final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface { public function __construct( private AccompanyingCourseDocumentRepository $accompanyingCourseDocumentRepository, + private PersonDocumentRepository $personDocumentRepository, ) { } public function supports(GenericDocDTO $genericDocDTO, $options = []): bool { - return $genericDocDTO->key === AccompanyingProviderCourseDocumentGenericDoc::KEY; + return $genericDocDTO->key === AccompanyingProviderCourseDocumentGenericDoc::KEY + || $genericDocDTO->key === PersonDocumentGenericDocProvider::KEY; } public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string @@ -35,9 +40,18 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { + if ($genericDocDTO->linked instanceof AccompanyingPeriod) { + return [ + 'document' => $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), + 'accompanyingCourse' => $genericDocDTO->linked, + 'options' => $options, + ]; + } + + // this is a person return [ - 'document' => $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), - 'accompanyingCourse' => $genericDocDTO->linked, + 'document' => $this->personDocumentRepository->find($genericDocDTO->identifiers['id']), + 'person' => $genericDocDTO->linked, 'options' => $options, ]; } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php index 23dcc4e0b..5d85541aa 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php @@ -12,30 +12,36 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Repository; use Chill\DocStoreBundle\Entity\PersonDocument; +use Chill\DocStoreBundle\GenericDoc\FetchQuery; +use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; +use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider; use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; +use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; +use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\PersonBundle\Entity\Person; +use DateTimeImmutable; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Security\Core\Security; class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface { - private AuthorizationHelperInterface $authorizationHelper; + private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser; - private CenterResolverDispatcher $centerResolverDispatcher; + private CenterResolverManagerInterface $centerResolverManager; private EntityManagerInterface $em; - private Security $security; - - public function __construct(EntityManagerInterface $em, AuthorizationHelperInterface $authorizationHelper, CenterResolverDispatcher $centerResolverDispatcher, Security $security) + public function __construct(EntityManagerInterface $em, CenterResolverManagerInterface $centerResolverManager, AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser) { $this->em = $em; - $this->authorizationHelper = $authorizationHelper; - $this->centerResolverDispatcher = $centerResolverDispatcher; - $this->security = $security; + $this->centerResolverManager = $centerResolverManager; + $this->authorizationHelperForCurrentUser = $authorizationHelperForCurrentUser; } public function buildQueryByPerson(Person $person): QueryBuilder @@ -49,6 +55,62 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito return $qb; } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface + { + $query = $this->buildBaseFetchQueryForPerson($person, $startDate, $endDate, $content); + + return $this->addFetchQueryByPersonACL($query, $person); + } + + public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); + + $query = new FetchQuery( + PersonDocumentGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', person_document.%s)', $personDocMetadata->getSingleIdentifierColumnName()), + sprintf('person_document.%s', $personDocMetadata->getColumnName('date')), + sprintf('%s AS person_document', $personDocMetadata->getSchemaName().'.'.$personDocMetadata->getTableName()) + ); + + $query->addWhereClause( + sprintf('person_document.%s = ?', $personDocMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('? <= %s', $personDocMetadata->getColumnName('date')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('? >= %s', $personDocMetadata->getColumnName('date')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content and '' !== $content) { + $query->addWhereClause( + sprintf( + '(%s ilike ? OR %s ilike ?)', + $personDocMetadata->getColumnName('title'), + $personDocMetadata->getColumnName('description') + ), + ['%' . $content . '%', '%' . $content . '%'], + [Types::STRING, Types::STRING] + ); + } + + return $query; + } + public function countByPerson(Person $person): int { $qb = $this->buildQueryByPerson($person)->select('COUNT(d)'); @@ -75,16 +137,58 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito private function addACL(QueryBuilder $qb, Person $person): void { - $center = $this->centerResolverDispatcher->resolveCenter($person); + $reachableScopes = []; - $reachableScopes = $this->authorizationHelper - ->getReachableScopes( - $this->security->getUser(), - PersonDocumentVoter::SEE, - $center - ); + foreach ($this->centerResolverManager->resolveCenters($person) as $center) { + $reachableScopes = [ + ...$reachableScopes, + ...$this->authorizationHelperForCurrentUser + ->getReachableScopes( + PersonDocumentVoter::SEE, + $center + ) + ]; + } + + if ([] === $reachableScopes) { + $qb->andWhere("'FALSE' = 'TRUE'"); + + return; + } $qb->andWhere($qb->expr()->in('d.scope', ':scopes')) ->setParameter('scopes', $reachableScopes); } + + private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery + { + $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); + + $reachableScopes = []; + + foreach ($this->centerResolverManager->resolveCenters($person) as $center) { + $reachableScopes = [ + ...$reachableScopes, + ...$this->authorizationHelperForCurrentUser->getReachableScopes(PersonDocumentVoter::SEE, $center) + ]; + } + + if ([] === $reachableScopes) { + $fetchQuery->addWhereClause('FALSE = TRUE'); + + return $fetchQuery; + } + + $fetchQuery->addWhereClause( + sprintf( + 'person_document.%s IN (%s)', + $personDocMetadata->getSingleAssociationJoinColumnName('scope'), + implode(', ', array_fill(0, count($reachableScopes), '?')) + ), + array_map(static fn (Scope $s) => $s->getId(), $reachableScopes), + array_fill(0, count($reachableScopes), Types::INTEGER) + ); + + return $fetchQuery; + } } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php index 6c4bd2e9a..0b5e26792 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php @@ -11,11 +11,25 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Repository; +use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\PersonBundle\Entity\Person; interface PersonDocumentACLAwareRepositoryInterface { + /** + * @deprecated use fetch query for listing and counting person documents + */ public function countByPerson(Person $person): int; + /** + * @deprecated use fetch query for listing and counting person documents + */ public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array; + + public function buildFetchQueryForPerson( + Person $person, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null + ): FetchQueryInterface; } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php new file mode 100644 index 000000000..62a3bbcec --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php @@ -0,0 +1,49 @@ + + */ +readonly class PersonDocumentRepository implements ObjectRepository +{ + private EntityRepository $repository; + + public function __construct( + private EntityManagerInterface $entityManager + ) + { + $this->repository = $this->entityManager->getRepository($this->getClassName()); + } + + public function find($id): ?PersonDocument + { + return $this->repository->find($id); + } + + public function findAll() + { + return $this->repository->findAll(); + } + + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + { + return $this->repository->findBy($criteria, $orderBy, $limit, $offset); + } + + public function findOneBy(array $criteria): ?PersonDocument + { + return $this->repository->findOneBy($criteria); + } + + public function getClassName(): string + { + return PersonDocument::class; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig new file mode 100644 index 000000000..a4aa4fbbc --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig @@ -0,0 +1,74 @@ +{# + * Copyright (C) 2018, Champs Libres Cooperative SCRLFS, + * + * 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 . +#} + +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = '' %} + +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} + +{% block title %} + {{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }} +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_docgen_picktemplate') }} + {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_docgen_picktemplate') }} + {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block content %} + +
      +

      {{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}

      + + {{ filter|chill_render_filter_order_helper }} + + {% if documents|length == 0 %} +

      {{ 'No documents'|trans }}

      + {% else %} +
      + {% for document in documents %} + {{ document|chill_generic_doc_render }} + {% endfor %} +
      + {% endif %} + + {{ chill_pagination(pagination) }} + +
      + + {% if is_granted('CHILL_PERSON_DOCUMENT_CREATE', person) %} + + {% endif %} + +
      +{% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php index 597aea84a..f59779374 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php @@ -14,9 +14,12 @@ namespace Chill\DocStoreBundle\Tests\GenericDoc; use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; +use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\DocStoreBundle\GenericDoc\Manager; use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; +use DateTimeImmutable; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; @@ -53,7 +56,8 @@ class ManagerTest extends KernelTestCase } $manager = new Manager( - [new SimpleGenericDocProvider()], + [new SimpleGenericDocAccompanyingPeriodProvider()], + [new SimpleGenericDocPersonProvider()], $this->connection, ); @@ -62,6 +66,27 @@ class ManagerTest extends KernelTestCase self::assertIsInt($nb); } + public function testCountByPerson(): void + { + $person = $this->em->createQuery('SELECT a FROM '.Person::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $person) { + throw new \UnexpectedValueException("person found"); + } + + $manager = new Manager( + [new SimpleGenericDocAccompanyingPeriodProvider()], + [new SimpleGenericDocPersonProvider()], + $this->connection, + ); + + $nb = $manager->countDocForPerson($person); + + self::assertIsInt($nb); + } + public function testFindDocByAccompanyingPeriod(): void { $period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') @@ -73,7 +98,8 @@ class ManagerTest extends KernelTestCase } $manager = new Manager( - [new SimpleGenericDocProvider()], + [new SimpleGenericDocAccompanyingPeriodProvider()], + [new SimpleGenericDocPersonProvider()], $this->connection, ); @@ -81,14 +107,77 @@ class ManagerTest extends KernelTestCase self::assertInstanceOf(GenericDocDTO::class, $doc); } } + + public function testFindDocByPerson(): void + { + $person = $this->em->createQuery('SELECT a FROM '.Person::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $person) { + throw new \UnexpectedValueException("person not found"); + } + + $manager = new Manager( + [new SimpleGenericDocAccompanyingPeriodProvider()], + [new SimpleGenericDocPersonProvider()], + $this->connection, + ); + + foreach ($manager->findDocForPerson($person) as $doc) { + self::assertInstanceOf(GenericDocDTO::class, $doc); + } + } + + public function testPlacesForPerson(): void + { + $person = $this->em->createQuery('SELECT a FROM '.Person::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $person) { + throw new \UnexpectedValueException("person not found"); + } + + $manager = new Manager( + [new SimpleGenericDocAccompanyingPeriodProvider()], + [new SimpleGenericDocPersonProvider()], + $this->connection, + ); + + $places = $manager->placesForPerson($person); + + self::assertEquals(['dummy_person_doc'], $places); + } + + public function testPlacesForAccompanyingPeriod(): void + { + $period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $period) { + throw new \UnexpectedValueException("period not found"); + } + + $manager = new Manager( + [new SimpleGenericDocAccompanyingPeriodProvider()], + [new SimpleGenericDocPersonProvider()], + $this->connection, + ); + + $places = $manager->placesForAccompanyingPeriod($period); + + self::assertEquals(['accompanying_course_document_dummy'], $places); + } } -final readonly class SimpleGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface +final readonly class SimpleGenericDocAccompanyingPeriodProvider implements GenericDocForAccompanyingPeriodProviderInterface { public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $query = new FetchQuery( - 'accompanying_course_document', + 'accompanying_course_document_dummy', sprintf('jsonb_build_object(\'id\', %s)', 'id'), 'd', '(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (id, d)', @@ -104,3 +193,25 @@ final readonly class SimpleGenericDocProvider implements GenericDocForAccompanyi return true; } } + +final readonly class SimpleGenericDocPersonProvider implements GenericDocForPersonProviderInterface +{ + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $query = new FetchQuery( + 'dummy_person_doc', + sprintf('jsonb_build_object(\'id\', %s)', 'id'), + 'd', + '(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (id, d)', + ); + + $query->addWhereClause('d > ?', [new \DateTimeImmutable('2023-01-01')], [Types::DATE_IMMUTABLE]); + + return $query; + } + + public function isAllowedForPerson(Person $person): bool + { + return true; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/PersonDocumentGenericDocProviderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/PersonDocumentGenericDocProviderTest.php new file mode 100644 index 000000000..577357820 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/PersonDocumentGenericDocProviderTest.php @@ -0,0 +1,84 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->personDocumentACLAwareRepository = self::$container->get(PersonDocumentACLAwareRepositoryInterface::class); + } + + /** + * @dataProvider provideDataBuildFetchQueryForPerson + * @throws \Doctrine\DBAL\Exception + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function testBuildFetchQueryForPerson(?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $person = $this->entityManager->createQuery('SELECT a FROM '.Person::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $person) { + throw new \UnexpectedValueException("person found"); + } + + $provider = new PersonDocumentGenericDocProvider( + $security->reveal(), + $this->personDocumentACLAwareRepository + ); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection() + ->fetchOne("SELECT COUNT(*) AS nb FROM (${sql}) AS sq", $params, $types); + + self::assertIsInt($nb, "Test that the query is syntactically correct"); + } + + public function provideDataBuildFetchQueryForPerson(): iterable + { + yield [null, null, null]; + yield [new DateTimeImmutable('1 year ago'), null, null]; + yield [null, new DateTimeImmutable('1 year ago'), null]; + yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null]; + yield [null, null, 'test']; + yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test']; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php new file mode 100644 index 000000000..98fca5622 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php @@ -0,0 +1,99 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->scopeRepository = self::$container->get(ScopeRepositoryInterface::class); + } + + /** + * @dataProvider provideDataBuildFetchQueryForPerson + * @throws \Doctrine\DBAL\Exception + * @throws \Doctrine\ORM\NoResultException + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function testBuildFetchQueryForPerson(?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): void + { + $centerManager = $this->prophesize(CenterResolverManagerInterface::class); + $centerManager->resolveCenters(Argument::type(Person::class)) + ->willReturn([new Center()]); + + $scopes = $this->scopeRepository->findAll(); + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes(PersonDocumentVoter::SEE, Argument::any())->willReturn($scopes); + + $repository = new PersonDocumentACLAwareRepository( + $this->entityManager, + $centerManager->reveal(), + $authorizationHelper->reveal() + ); + + $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p") + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $person) { + throw new \RuntimeException("person not exists in database"); + } + + $query = $repository->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection() + ->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb, "test that the query could be executed"); + } + + public function provideDataBuildFetchQueryForPerson(): iterable + { + yield [null, null, null]; + yield [new DateTimeImmutable('1 year ago'), null, null]; + yield [null, new DateTimeImmutable('1 year ago'), null]; + yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null]; + yield [null, null, 'test']; + yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test']; + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index f242acc1c..04fc3ace3 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -50,6 +50,7 @@ services: autoconfigure: true arguments: $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider + $providersForPerson: !tagged_iterator chill_doc_store.generic_doc_person_provider Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension: autoconfigure: true From e9fdabf93166f01f2a157f7d89f0ec4cd2792565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 30 May 2023 21:24:22 +0200 Subject: [PATCH 14/40] Remove old list for person document --- .../Controller/DocumentPersonController.php | 51 +------------ .../ChillDocStoreBundle/Menu/MenuBuilder.php | 4 +- .../views/PersonDocument/delete.html.twig | 4 +- .../views/PersonDocument/edit.html.twig | 2 +- .../views/PersonDocument/index.html.twig | 72 ------------------- .../views/PersonDocument/new.html.twig | 2 +- .../views/PersonDocument/show.html.twig | 2 +- 7 files changed, 9 insertions(+), 128 deletions(-) delete mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php index 6fcb6a8e5..20e8e9b03 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php @@ -45,10 +45,6 @@ class DocumentPersonController extends AbstractController protected TranslatorInterface $translator; - private PaginatorFactory $paginatorFactory; - - private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository; - /** * DocumentPersonController constructor. */ @@ -56,14 +52,10 @@ class DocumentPersonController extends AbstractController TranslatorInterface $translator, EventDispatcherInterface $eventDispatcher, AuthorizationHelper $authorizationHelper, - PaginatorFactory $paginatorFactory, - PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository ) { $this->translator = $translator; $this->eventDispatcher = $eventDispatcher; $this->authorizationHelper = $authorizationHelper; - $this->paginatorFactory = $paginatorFactory; - $this->personDocumentACLAwareRepository = $personDocumentACLAwareRepository; } /** @@ -88,7 +80,7 @@ class DocumentPersonController extends AbstractController return $this->redirect($request->query->get('returnPath')); } - return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]); + return $this->redirectToRoute('chill_docstore_generic-doc_by-person_index', ['id' => $person->getId()]); } return $this->render( @@ -160,45 +152,6 @@ class DocumentPersonController extends AbstractController ); } - /** - * @Route("/", name="person_document_index", methods="GET") - */ - public function index(Person $person): Response - { - $em = $this->getDoctrine()->getManager(); - - if (null === $person) { - throw $this->createNotFoundException('Person not found'); - } - - $this->denyAccessUnlessGranted(PersonVoter::SEE, $person); - - $total = $this->personDocumentACLAwareRepository->countByPerson($person); - $pagination = $this->paginatorFactory->create($total); - - $documents = $this->personDocumentACLAwareRepository->findByPerson( - $person, - ['date' => 'DESC', 'id' => 'DESC'], - $pagination->getItemsPerPage(), - $pagination->getCurrentPageFirstItemNumber() - ); - - $event = new PrivacyEvent($person, [ - 'element_class' => PersonDocument::class, - 'action' => 'index', - ]); - $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); - - return $this->render( - 'ChillDocStoreBundle:PersonDocument:index.html.twig', - [ - 'documents' => $documents, - 'person' => $person, - 'pagination' => $pagination, - ] - ); - } - /** * @Route("/new", name="person_document_new", methods="GET|POST") */ @@ -233,7 +186,7 @@ class DocumentPersonController extends AbstractController $this->addFlash('success', $this->translator->trans('The document is successfully registered')); - return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]); + return $this->redirectToRoute('chill_docstore_generic-doc_by-person_index', ['id' => $person->getId()]); } if ($form->isSubmitted() && !$form->isValid()) { diff --git a/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php b/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php index 95b23904a..4288dc821 100644 --- a/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php +++ b/src/Bundle/ChillDocStoreBundle/Menu/MenuBuilder.php @@ -80,9 +80,9 @@ final class MenuBuilder implements LocalMenuBuilderInterface if ($this->security->isGranted(PersonDocumentVoter::SEE, $person)) { $menu->addChild($this->translator->trans('Documents'), [ - 'route' => 'person_document_index', + 'route' => 'chill_docstore_generic-doc_by-person_index', 'routeParameters' => [ - 'person' => $person->getId(), + 'id' => $person->getId(), ], ]) ->setExtras([ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/delete.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/delete.html.twig index bfdd87dc3..41c229faa 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/delete.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/delete.html.twig @@ -36,8 +36,8 @@ 'title' : 'Delete document ?'|trans, 'display_content' : block('docdescription'), 'confirm_question' : 'Are you sure you want to remove this document ?'|trans, - 'cancel_route' : 'person_document_index', - 'cancel_parameters' : {'person' : person.id, 'id': document.id}, + 'cancel_route' : 'chill_docstore_generic-doc_by-person_index', + 'cancel_parameters' : {'id' : person.id}, 'form' : delete_form } ) }} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/edit.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/edit.html.twig index 416a35e35..17ce9d774 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/edit.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/edit.html.twig @@ -38,7 +38,7 @@
      • - + {{ 'Back to the list' | trans }}
      • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig deleted file mode 100644 index 8d201605e..000000000 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig +++ /dev/null @@ -1,72 +0,0 @@ -{# - * Copyright (C) 2018, Champs Libres Cooperative SCRLFS, - * - * 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 . -#} - -{% extends "@ChillPerson/Person/layout.html.twig" %} - -{% set activeRouteKey = '' %} - -{% import "@ChillDocStore/Macro/macro.html.twig" as m %} - -{% block title %} - {{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }} -{% endblock %} - -{% block js %} - {{ parent() }} - {{ encore_entry_script_tags('mod_docgen_picktemplate') }} - {{ encore_entry_script_tags('mod_entity_workflow_pick') }} - {{ encore_entry_script_tags('mod_document_action_buttons_group') }} -{% endblock %} - -{% block css %} - {{ parent() }} - {{ encore_entry_link_tags('mod_docgen_picktemplate') }} - {{ encore_entry_link_tags('mod_entity_workflow_pick') }} - {{ encore_entry_link_tags('mod_document_action_buttons_group') }} -{% endblock %} - -{% block content %} - -
        -

        {{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}

        - - {% if documents|length == 0 %} -

        {{ 'No documents'|trans }}

        - {% else %} -
        - {% for document in documents %} - {% include 'ChillDocStoreBundle:List:list_item.html.twig' %} - {% endfor %} -
        - {% endif %} - - {{ chill_pagination(pagination) }} - -
        - - {% if is_granted('CHILL_PERSON_DOCUMENT_CREATE', person) %} - - {% endif %} - -
        -{% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig index 3187714a6..faf8895e7 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/new.html.twig @@ -42,7 +42,7 @@
        • - + {{ 'Back to the list' | trans }}
        • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig index c276e067e..1c0aa5e64 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig @@ -63,7 +63,7 @@
          • - + {{ 'Back to the list' | trans }}
          • From 40ddd1f1ee3787656dc944785e493c56be5d6718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 30 May 2023 21:25:33 +0200 Subject: [PATCH 15/40] Add license --- .../Repository/PersonDocumentRepository.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php index 62a3bbcec..40afdc220 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php @@ -1,5 +1,14 @@ repository = $this->entityManager->getRepository($this->getClassName()); } From da36c59616379c77119d7fe9ff2e56275e003b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 30 May 2023 21:52:36 +0200 Subject: [PATCH 16/40] refactor: rename class providing AccompanyingCourseDocument Generic doc --- ...php => AccompanyingCourseDocumentGenericDocProvider.php} | 2 +- .../AccompanyingCourseDocumentGenericDocRenderer.php | 4 ++-- ...=> AccompanyingCourseDocumentGenericDocProviderTest.php} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/{AccompanyingProviderCourseDocumentGenericDoc.php => AccompanyingCourseDocumentGenericDocProvider.php} (95%) rename src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/{AccompanyingCourseDocumentProviderTest.php => AccompanyingCourseDocumentGenericDocProviderTest.php} (90%) diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php similarity index 95% rename from src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php rename to src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php index 970ce646d..439f6a511 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingProviderCourseDocumentGenericDoc.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php @@ -21,7 +21,7 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; -final readonly class AccompanyingProviderCourseDocumentGenericDoc implements GenericDocForAccompanyingPeriodProviderInterface +final readonly class AccompanyingCourseDocumentGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface { public const KEY = 'accompanying_course_document'; diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php index a533b577a..ffa158aca 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php @@ -14,7 +14,7 @@ namespace Chill\DocStoreBundle\GenericDoc\Renderer; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; -use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingProviderCourseDocumentGenericDoc; +use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider; use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository; use Chill\DocStoreBundle\Repository\PersonDocumentRepository; use Chill\PersonBundle\Entity\AccompanyingPeriod; @@ -29,7 +29,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen public function supports(GenericDocDTO $genericDocDTO, $options = []): bool { - return $genericDocDTO->key === AccompanyingProviderCourseDocumentGenericDoc::KEY + return $genericDocDTO->key === AccompanyingCourseDocumentGenericDocProvider::KEY || $genericDocDTO->key === PersonDocumentGenericDocProvider::KEY; } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProviderTest.php similarity index 90% rename from src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php rename to src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProviderTest.php index 8c6bd524d..79ced9258 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentProviderTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProviderTest.php @@ -12,7 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Tests\GenericDoc\Providers; use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder; -use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingProviderCourseDocumentGenericDoc; +use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Types\Types; @@ -25,7 +25,7 @@ use Symfony\Component\Security\Core\Security; * @internal * @coversNothing */ -class AccompanyingCourseDocumentProviderTest extends KernelTestCase +class AccompanyingCourseDocumentGenericDocProviderTest extends KernelTestCase { use ProphecyTrait; private EntityManagerInterface $entityManager; @@ -54,7 +54,7 @@ class AccompanyingCourseDocumentProviderTest extends KernelTestCase $security->isGranted(AccompanyingCourseDocumentVoter::SEE, $period) ->willReturn(true); - $provider = new AccompanyingProviderCourseDocumentGenericDoc( + $provider = new AccompanyingCourseDocumentGenericDocProvider( $security->reveal(), $this->entityManager ); From 2b57807565a335e6d81a761bd11d490cc9fa7de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 30 May 2023 22:14:13 +0200 Subject: [PATCH 17/40] show generic doc accompanying course document in person generic doc list --- ...anyingCourseDocumentGenericDocProvider.php | 75 +++++++++++++++++-- ...anyingCourseDocumentGenericDocRenderer.php | 10 +-- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php index 439f6a511..054e6a6d8 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php @@ -15,13 +15,17 @@ use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; +use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; -final readonly class AccompanyingCourseDocumentGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface +final readonly class AccompanyingCourseDocumentGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface { public const KEY = 'accompanying_course_document'; @@ -38,7 +42,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen $query = new FetchQuery( self::KEY, sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), - sprintf($classMetadata->getColumnName('date')), + $classMetadata->getColumnName('date'), $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() ); @@ -48,6 +52,67 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen [Types::INTEGER] ); + return $this->addWhereClause($query, $startDate, $endDate, $content); + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod); + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class); + + $query = new FetchQuery( + self::KEY, + sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), + $classMetadata->getColumnName('date'), + $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() . ' AS acc_course_document' + ); + + $atLeastOne = false; + $or = []; + $orParams = []; + $orTypes = []; + + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $atLeastOne = true; + + $or[] = sprintf( + "(acc_course_document.%s = ? AND acc_course_document.%s BETWEEN ? AND COALESCE(?, 'infinity'::date))", + $classMetadata->getSingleAssociationJoinColumnName('course'), + $classMetadata->getColumnName('date') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), $participation->getStartDate(), $participation->getEndDate()]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_MUTABLE, Types::DATE_MUTABLE]; + } + + if (!$atLeastOne) { + // there aren't any period allowed to be seen. Add an unreachable condition + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + + $query->addWhereClause(implode(' OR ', $or), $orParams, $orTypes); + + return $this->addWhereClause($query, $startDate, $endDate, $content); + } + + public function isAllowedForPerson(Person $person): bool + { + return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); + } + + private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class); + if (null !== $startDate) { $query->addWhereClause( sprintf('? <= %s', $classMetadata->getColumnName('date')), @@ -64,7 +129,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen ); } - if (null !== $content) { + if (null !== $content and '' !== $content) { $query->addWhereClause( sprintf( '(%s ilike ? OR %s ilike ?)', @@ -79,8 +144,4 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen return $query; } - public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool - { - return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod); - } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php index ffa158aca..052153be8 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php @@ -40,18 +40,18 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { - if ($genericDocDTO->linked instanceof AccompanyingPeriod) { + if (AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key) { return [ - 'document' => $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), - 'accompanyingCourse' => $genericDocDTO->linked, + 'document' => $doc = $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), + 'accompanyingCourse' => $doc->getCourse(), 'options' => $options, ]; } // this is a person return [ - 'document' => $this->personDocumentRepository->find($genericDocDTO->identifiers['id']), - 'person' => $genericDocDTO->linked, + 'document' => $doc = $this->personDocumentRepository->find($genericDocDTO->identifiers['id']), + 'person' => $doc->getPerson(), 'options' => $options, ]; } From cb718a80dece206e020c4cac7e86aa2e0b735172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 31 May 2023 12:23:34 +0200 Subject: [PATCH 18/40] Add accompanying period work evaluation documents to the list of documents for person --- ...anyingCourseDocumentGenericDocProvider.php | 2 +- ...PeriodWorkEvaluationGenericDocProvider.php | 136 +++++++++++++----- 2 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php index 054e6a6d8..fd36f7976 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php @@ -99,7 +99,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen return $query; } - $query->addWhereClause(implode(' OR ', $or), $orParams, $orTypes); + $query->addWhereClause('(' . implode(' OR ', $or) . ')', $orParams, $orTypes); return $this->addWhereClause($query, $startDate, $endDate, $content); } diff --git a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php index 883836547..f8b99a048 100644 --- a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php +++ b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Providers/AccompanyingPeriodWorkEvaluationGenericDocProvider.php @@ -15,13 +15,16 @@ use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; +use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; -final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface +final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface { public const KEY = 'accompanying_period_work_evaluation_document'; @@ -32,6 +35,100 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen } public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $accompanyingPeriodWorkMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWork::class); + $query = $this->buildBaseQuery(); + + $query->addWhereClause( + sprintf('action.%s = ?', $accompanyingPeriodWorkMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $accompanyingPeriod); + } + + private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $classMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument::class); + $storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf('apwed.%s ilike ?', $classMetadata->getColumnName('title')), + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class); + $accompanyingPeriodWorkMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWork::class); + $query = $this->buildBaseQuery(); + + // we loop over each accompanying period participation, to check of the user is allowed to see them + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(action.%s = ? AND apwed.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $accompanyingPeriodWorkMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; + } + + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + + $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); + + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); + } + + public function isAllowedForPerson(Person $person): bool + { + // this will be filtered during query + return true; + } + + private function buildBaseQuery(): FetchQuery { $classMetadata = $this->entityManager->getClassMetadata(AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument::class); $storedObjectMetadata = $this->entityManager->getClassMetadata(StoredObject::class); @@ -63,43 +160,6 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen $accompanyingPeriodWorkMetadata->getColumnName('id') )); - $query->addWhereClause( - sprintf('action.%s = ?', $accompanyingPeriodWorkMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), - [$accompanyingPeriod->getId()], - [Types::INTEGER] - ); - - if (null !== $startDate) { - $query->addWhereClause( - sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), - [$startDate], - [Types::DATE_IMMUTABLE] - ); - } - - if (null !== $endDate) { - $query->addWhereClause( - sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), - [$endDate], - [Types::DATE_IMMUTABLE] - ); - } - - if (null !== $content) { - $query->addWhereClause( - sprintf('apwed.%s ilike ?', $classMetadata->getColumnName('title')), - ['%' . $content . '%'], - [Types::STRING] - ); - } - return $query; } - - public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool - { - return $this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $accompanyingPeriod); - } - - } From 20489813f068c6147976ee1e6ed50b1b5dc2c66c Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 14:38:16 +0200 Subject: [PATCH 19/40] FIX [route] adjust to using new route name in redirect --- .../Controller/DocumentAccompanyingCourseController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php index 8762050aa..384eeb510 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php @@ -160,7 +160,7 @@ class DocumentAccompanyingCourseController extends AbstractController $this->addFlash('success', $this->translator->trans('The document is successfully registered')); - return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]); + return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]); } if ($form->isSubmitted() && !$form->isValid()) { From 47a3e30ec532e6cf27ba00e049a2e90c6ea7bd9b Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 16:56:20 +0200 Subject: [PATCH 20/40] FEATURE [genericDoc] generic doc interface implemented for rendez-vous --- .../GenericDoc/calendar_document.html.twig | 72 +++++++++++++ ...anyingPeriodCalendarGenericDocProvider.php | 101 ++++++++++++++++++ ...anyingPeriodCalendarGenericDocRenderer.php | 44 ++++++++ .../translations/messages.fr.yml | 2 + .../translations/messages.fr.yml | 1 + 5 files changed, 220 insertions(+) create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php create mode 100644 src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig new file mode 100644 index 000000000..712b362f5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig @@ -0,0 +1,72 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set c = document.calendar %} + +
            +
            +
            + {% if document.storedObject.isPending %} +
            {{ 'docgen.Doc generation is pending'|trans }}
            + {% elseif document.storedObject.isFailure %} +
            {{ 'docgen.Doc generation failed'|trans }}
            + {% endif %} +
            + {{ document.storedObject.title }} +
            +
            + {{ 'chill_calendar.Document'|trans }} +
            + {% if document.storedObject.hasTemplate %} +
            +

            {{ document.storedObject.template.name|localize_translatable_string }}

            +
            + {% endif %} +
            + +
            +
            +
            + {{ document.storedObject.createdAt|format_date('short') }} +
            +
            +
            +
            + +
            +
            +

            + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} +

            +
            +
            + +
            +
            + {{ mmm.createdBy(document) }} +
            +
              +
            • + {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} +
            • + {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %} +
            • + {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} +
            • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %} +
            • + +
            • + {% endif %} +
            + +
            +
            diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php new file mode 100644 index 000000000..ff2cdbcf5 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -0,0 +1,101 @@ +security = $security; + $this->em = $entityManager; + } + + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + 'JOIN chill_doc.stored_object doc_store ON doc_store.id = cd.storedobject_id' + ); + + $query->addJoinClause( + 'JOIN chill_calendar.calendar calendar ON calendar.id = cd.calendar_id' + ); + + $query->addWhereClause( + 'calendar.accompanyingperiod_id = ?', + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + 'doc_store.title ilike ?', + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); + } + + +} diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php new file mode 100644 index 000000000..0f680797c --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php @@ -0,0 +1,44 @@ +repository = $calendarDocRepository; + } + + public function supports(GenericDocDTO $genericDocDTO, $options = []): bool + { + return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillCalendar/GenericDoc/calendar_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + ]; + } +} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index eb02be280..5e1fc971a 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -43,6 +43,7 @@ crud: title_edit: Modifier le motif d'annulation chill_calendar: + Document: Document d'un rendez-vous form: The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement. Create for referrer: Créer pour le référent @@ -65,6 +66,7 @@ chill_calendar: Document outdated: La date et l'heure du rendez-vous ont été modifiés après la création du document + remote_ms_graph: freebusy_statuses: busy: Occupé diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 1dde57eee..4fa41f180 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -26,6 +26,7 @@ generic_doc: filter: keys: accompanying_course_document: Document du parcours + accompanying_period_calendar_document: Document des rendez-vous date-range: Date du document # delete From 9eb9a9a214d9801fa5fa87ae10449b1068a16576 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 16:59:16 +0200 Subject: [PATCH 21/40] WIP [genericDoc][activity] implementing generic doc for activities --- ...anyingPeriodActivityGenericDocProvider.php | 110 ++++++++++++++++++ ...anyingPeriodActivityGenericDocRenderer.php | 45 +++++++ 2 files changed, 155 insertions(+) create mode 100644 src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php create mode 100644 src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php new file mode 100644 index 000000000..fc7484edf --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -0,0 +1,110 @@ +em = $entityManager; + $this->security = $security; + } + + /** + * @param AccompanyingPeriod $accompanyingPeriod + * @param DateTimeImmutable|null $startDate + * @param DateTimeImmutable|null $endDate + * @param string|null $content + * @param string|null $origin + * @return FetchQueryInterface + * @throws MappingException + */ + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); +// $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $query = new FetchQuery( + self::KEY, + "jsonb_build_object('id', doc_obj.id)", + 'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'), + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj' + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + 'activity.accompanyingperiod_id = ?', + [$accompanyingPeriod->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + 'doc_obj.title ilike ?', + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + /** + * @param AccompanyingPeriod $accompanyingPeriod + * @return bool + */ + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); + } +} diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php new file mode 100644 index 000000000..eddcc6828 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -0,0 +1,45 @@ +repository = $storedObjectRepository; + } + + public function supports(GenericDocDTO $genericDocDTO, $options = []): bool + { + return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY; + } + + public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string + { + return '@ChillCalendar/GenericDoc/activity_document.html.twig'; + } + + public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array + { + return [ + 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + ]; + } +} + From 4155af6686cd173c8689b5d9bff52b3996963644 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 17:50:32 +0200 Subject: [PATCH 22/40] FEATURE [genericDoc][calendar] use metadatas --- ...anyingPeriodCalendarGenericDocProvider.php | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php index ff2cdbcf5..e653cfc25 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -21,6 +21,7 @@ use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInter use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\MappingException; use Symfony\Component\Security\Core\Security; final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface @@ -39,6 +40,9 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo $this->em = $entityManager; } + /** + * @throws MappingException + */ public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); @@ -52,15 +56,23 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' ); $query->addJoinClause( - 'JOIN chill_doc.stored_object doc_store ON doc_store.id = cd.storedobject_id' - ); + sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + )); $query->addJoinClause( - 'JOIN chill_calendar.calendar calendar ON calendar.id = cd.calendar_id' + sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) ); $query->addWhereClause( - 'calendar.accompanyingperiod_id = ?', + sprintf('calendar.%s = ?', + $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), [$accompanyingPeriod->getId()], [Types::INTEGER] ); @@ -83,7 +95,7 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo if (null !== $content) { $query->addWhereClause( - 'doc_store.title ilike ?', + sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), ['%' . $content . '%'], [Types::STRING] ); From c07e26785e5b73e810b50bb41be02d2ad424e01c Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 30 May 2023 18:14:32 +0200 Subject: [PATCH 23/40] WIP [genericDoc][activity] add repository method to get activity linked to storedObject --- .../Repository/ActivityRepository.php | 12 ++++ .../GenericDoc/activity_document.html.twig | 72 +++++++++++++++++++ ...anyingPeriodActivityGenericDocProvider.php | 3 +- ...anyingPeriodActivityGenericDocRenderer.php | 13 ++-- 4 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index 5a6e16cd5..02658b03d 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\Query\Expr; use Doctrine\Persistence\ManagerRegistry; /** @@ -97,4 +98,15 @@ class ActivityRepository extends ServiceEntityRepository return $qb->getQuery()->getResult(); } + + public function findOneByDocument(int $documentId): Activity + { + $qb = $this->createQueryBuilder('a'); + $qb->select('a'); + + $qb->innerJoin('a.documents', 'd', Expr\Join::WITH, 'd.storedobject_id = :documentId'); + $qb->setParameter('documentId', $documentId); + + return $qb->getQuery()->getResult(); + } } diff --git a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig new file mode 100644 index 000000000..aeaea0872 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig @@ -0,0 +1,72 @@ +{% import "@ChillDocStore/Macro/macro.html.twig" as m %} +{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} +{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} + +{% set a = document.calendar %} + +
            +
            +
            + {% if document.storedObject.isPending %} +
            {{ 'docgen.Doc generation is pending'|trans }}
            + {% elseif document.storedObject.isFailure %} +
            {{ 'docgen.Doc generation failed'|trans }}
            + {% endif %} +
            + {{ document.storedObject.title }} +
            +
            + {{ 'chill_calendar.Document'|trans }} +
            + {% if document.storedObject.hasTemplate %} +
            +

            {{ document.storedObject.template.name|localize_translatable_string }}

            +
            + {% endif %} +
            + +
            +
            +
            + {{ document.storedObject.createdAt|format_date('short') }} +
            +
            +
            +
            + +
            +
            +

            + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} +

            +
            +
            + +
            +
            + {{ mmm.createdBy(document) }} +
            +
              +
            • + {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} +
            • + {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %} +
            • + {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} +
            • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %} +
            • + +
            • + {% endif %} +
            + +
            +
            diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php index fc7484edf..bf7a022f1 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -27,7 +27,7 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo { public const KEY = 'accompanying_period_activity_document'; - private EntityManagerInterface $em; + private EntityManagerInterface $em; private Security $security; @@ -49,7 +49,6 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); -// $activityMetadata = $this->em->getClassMetadata(Activity::class); $query = new FetchQuery( self::KEY, diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php index eddcc6828..f243fb941 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\GenericDoc\Renderers; +use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; @@ -18,11 +19,14 @@ use Chill\DocStoreBundle\Repository\StoredObjectRepository; final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface { - private StoredObjectRepository $repository; + private StoredObjectRepository $objectRepository; - public function __construct(StoredObjectRepository $storedObjectRepository) + private ActivityRepository $activityRepository; + + public function __construct(StoredObjectRepository $storedObjectRepository, ActivityRepository $activityRepository) { - $this->repository = $storedObjectRepository; + $this->objectRepository = $storedObjectRepository; + $this->activityRepository = $activityRepository; } public function supports(GenericDocDTO $genericDocDTO, $options = []): bool @@ -38,7 +42,8 @@ final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRe public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { return [ - 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + 'activity' => $this->activityRepository->findOneByDocument($genericDocDTO->identifiers['id']), + 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']) ]; } } From ba55fa349baa108487af2b5eef9737196aa6c022 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 31 May 2023 16:53:38 +0200 Subject: [PATCH 24/40] FEATURE [genericDoc][activity] finalize implementation --- .../Repository/ActivityRepository.php | 11 ---- .../GenericDoc/activity_document.html.twig | 63 ++++++++++--------- ...anyingPeriodActivityGenericDocProvider.php | 23 ++----- ...anyingPeriodActivityGenericDocRenderer.php | 4 +- .../ChillActivityBundle/config/services.yaml | 3 + 5 files changed, 45 insertions(+), 59 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index 02658b03d..a3bfb5942 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -98,15 +98,4 @@ class ActivityRepository extends ServiceEntityRepository return $qb->getQuery()->getResult(); } - - public function findOneByDocument(int $documentId): Activity - { - $qb = $this->createQueryBuilder('a'); - $qb->select('a'); - - $qb->innerJoin('a.documents', 'd', Expr\Join::WITH, 'd.storedobject_id = :documentId'); - $qb->setParameter('documentId', $documentId); - - return $qb->getQuery()->getResult(); - } } diff --git a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig index aeaea0872..11aeeeca1 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig @@ -2,25 +2,30 @@ {% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} {% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} -{% set a = document.calendar %} +{% set person_id = null %} +{% if activity.person %} + {% set person_id = activity.person.id %} +{% endif %} -
            +{% set accompanying_course_id = null %} +{% if activity.accompanyingPeriod %} + {% set accompanying_course_id = activity.accompanyingPeriod.id %} +{% endif %} + +
            - {% if document.storedObject.isPending %} -
            {{ 'docgen.Doc generation is pending'|trans }}
            - {% elseif document.storedObject.isFailure %} + {% if document.isPending %} +
            {{ 'docgen.Doc generation is pending'|trans }}
            + {% elseif document.isFailure %}
            {{ 'docgen.Doc generation failed'|trans }}
            {% endif %}
            - {{ document.storedObject.title }} + {{ document.title }}
            -
            - {{ 'chill_calendar.Document'|trans }} -
            - {% if document.storedObject.hasTemplate %} + {% if document.hasTemplate %}
            -

            {{ document.storedObject.template.name|localize_translatable_string }}

            +

            {{ document.template.name|localize_translatable_string }}

            {% endif %}
            @@ -28,7 +33,7 @@
            - {{ document.storedObject.createdAt|format_date('short') }} + {{ document.createdAt|format_date('short') }}
            @@ -36,15 +41,15 @@
            -

            - {% if c.endDate.diff(c.startDate).days >= 1 %} - {{ c.startDate|format_datetime('short', 'short') }} - - {{ c.endDate|format_datetime('short', 'short') }} - {% else %} - {{ c.startDate|format_datetime('short', 'short') }} - - {{ c.endDate|format_datetime('none', 'short') }} - {% endif %} -

            +

            + + + {{ activity.type.name | localize_translatable_string }} + {% if activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +

            @@ -53,17 +58,19 @@ {{ mmm.createdBy(document) }}
              -
            • - {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} -
            • - {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %} + {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
            • - {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} + {{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
            • {% endif %} - {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %} + {% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
            • - + +
            • + {% endif %} + {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %} +
            • +
            • {% endif %}
            diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php index bf7a022f1..8c787fd3a 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -27,32 +27,19 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo { public const KEY = 'accompanying_period_activity_document'; - private EntityManagerInterface $em; - - private Security $security; - - public function __construct(Security $security, EntityManagerInterface $entityManager) - { - $this->em = $entityManager; - $this->security = $security; + public function __construct( + private EntityManagerInterface $em, + private Security $security + ){ } - /** - * @param AccompanyingPeriod $accompanyingPeriod - * @param DateTimeImmutable|null $startDate - * @param DateTimeImmutable|null $endDate - * @param string|null $content - * @param string|null $origin - * @return FetchQueryInterface - * @throws MappingException - */ public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); $query = new FetchQuery( self::KEY, - "jsonb_build_object('id', doc_obj.id)", + "jsonb_build_object('id', doc_obj.id, 'activity_id', activity.id)", 'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'), $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj' ); diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php index f243fb941..f3b70dac2 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -36,13 +36,13 @@ final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRe public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string { - return '@ChillCalendar/GenericDoc/activity_document.html.twig'; + return '@ChillActivity/GenericDoc/activity_document.html.twig'; } public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { return [ - 'activity' => $this->activityRepository->findOneByDocument($genericDocDTO->identifiers['id']), + 'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']), 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']) ]; } diff --git a/src/Bundle/ChillActivityBundle/config/services.yaml b/src/Bundle/ChillActivityBundle/config/services.yaml index d55f86d4f..18be76ec9 100644 --- a/src/Bundle/ChillActivityBundle/config/services.yaml +++ b/src/Bundle/ChillActivityBundle/config/services.yaml @@ -38,3 +38,6 @@ services: Chill\ActivityBundle\Service\EntityInfo\: resource: '../Service/EntityInfo/' + + Chill\ActivityBundle\Service\GenericDoc\: + resource: '../Service/GenericDoc/' From ef04a0405645fc5a38396e8f88486928a40be697 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 31 May 2023 16:54:21 +0200 Subject: [PATCH 25/40] FEATURE [genericDoc][calendar] minor changes to template and provider --- .../views/GenericDoc/calendar_document.html.twig | 3 --- .../AccompanyingPeriodCalendarGenericDocProvider.php | 10 ++-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig index 712b362f5..4cd369366 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig @@ -53,9 +53,6 @@ {{ mmm.createdBy(document) }}
              -
            • - {{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', document.id) }} -
            • {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
            • {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }} diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php index e653cfc25..7efc1589d 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php @@ -28,16 +28,10 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo { public const KEY = 'accompanying_period_calendar_document'; - private EntityManagerInterface $em; - - private Security $security; - public function __construct( - Security $security, - EntityManagerInterface $entityManager + private Security $security, + private EntityManagerInterface $em ) { - $this->security = $security; - $this->em = $entityManager; } /** From 9a3fcf081ecb5d3d7449878c7562d4528df167b6 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 31 May 2023 17:10:38 +0200 Subject: [PATCH 26/40] FEATURE [personCalendar][genericDoc] implement genericDoc for calendar objects linked to a person --- .../PersonCalendarGenericDocProvider.php | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php new file mode 100644 index 000000000..12235f10a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -0,0 +1,112 @@ +em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + )); + + $query->addJoinClause( + sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) + ); + + $query->addWhereClause( + sprintf('calendar.%s = ?', + $calendarMetadata->getAssociationMapping('person')['joinColumns'][0]['name']), + [$person->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), + ['%' . $content . '%'], + [Types::STRING] + ); + } + + dump($query); + + return $query; + } + + /** + * @param Person $person + * @return bool + */ + public function isAllowedForPerson(Person $person): bool + { + return $this->security->isGranted(CalendarVoter::SEE, $person); + } +} From 5196d26a3ea7ccac135fdda0f4f5fc93a2d70bf3 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 1 Jun 2023 10:44:14 +0200 Subject: [PATCH 27/40] FEATURE [translations] add translations --- src/Bundle/ChillCalendarBundle/translations/messages.fr.yml | 6 ++++++ src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml | 1 - src/Bundle/ChillPersonBundle/translations/messages.fr.yml | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index 5e1fc971a..99aae1082 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -147,3 +147,9 @@ CHILL_CALENDAR_CALENDAR_EDIT: Modifier les rendez-vous CHILL_CALENDAR_CALENDAR_DELETE: Supprimer les rendez-vous CHILL_CALENDAR_CALENDAR_SEE: Voir les rendez-vous + +generic_doc: + filter: + keys: + accompanying_period_calendar_document: Document des rendez-vous + person_calendar_document: Document des rendez-vous de la personne diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 4fa41f180..1dde57eee 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -26,7 +26,6 @@ generic_doc: filter: keys: accompanying_course_document: Document du parcours - accompanying_period_calendar_document: Document des rendez-vous date-range: Date du document # delete diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index a344ac50d..9b30e3a0b 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1236,3 +1236,4 @@ generic_doc: filter: keys: accompanying_period_work_evaluation_document: Document des actions d'accompagnement + person_document: Documents de la personne From 59e1e02b92b3bc66a0dfb607c9455284138541f3 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 1 Jun 2023 10:45:55 +0200 Subject: [PATCH 28/40] FEATURE [calendar][docs] try to implement showing calendar docs from parcours context in person context --- .../PersonCalendarGenericDocProvider.php | 76 ++++++++++++++----- ...anyingPeriodCalendarGenericDocRenderer.php | 3 +- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php index 12235f10a..bed04414c 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -19,6 +19,7 @@ use Chill\DocStoreBundle\GenericDoc\FetchQuery; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; @@ -35,6 +36,38 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi ) { } + + private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + /** * @throws MappingException */ @@ -72,33 +105,36 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi [Types::INTEGER] ); - if (null !== $startDate) { - $query->addWhereClause( - sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), - [$startDate], - [Types::DATE_IMMUTABLE] + // get the documents associated with accompanying periods in which person participates + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; } - if (null !== $endDate) { - $query->addWhereClause( - sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), - [$endDate], - [Types::DATE_IMMUTABLE] - ); + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; } - if (null !== $content) { - $query->addWhereClause( - sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')), - ['%' . $content . '%'], - [Types::STRING] - ); - } +// $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); - dump($query); + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); - return $query; } /** diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php index 0f680797c..d9636d99d 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php @@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\Service\GenericDoc\Renderers; use Chill\CalendarBundle\Repository\CalendarDocRepository; use Chill\CalendarBundle\Service\GenericDoc\Providers\AccompanyingPeriodCalendarGenericDocProvider; +use Chill\CalendarBundle\Service\GenericDoc\Providers\PersonCalendarGenericDocProvider; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; @@ -27,7 +28,7 @@ final class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRe public function supports(GenericDocDTO $genericDocDTO, $options = []): bool { - return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY; + return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY || $genericDocDTO->key === PersonCalendarGenericDocProvider::KEY; } public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string From 5dc1cbce487fc0ea1ef96365271c89e11c383a60 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 1 Jun 2023 11:01:29 +0200 Subject: [PATCH 29/40] FEATURE [activity][docs] generic doc for activity documents in person context --- ...rsonActivityDocumentACLAwareRepository.php | 197 ++++++++++++++++++ .../PersonActivityGenericDocProvider.php | 57 +++++ 2 files changed, 254 insertions(+) create mode 100644 src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php create mode 100644 src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php diff --git a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php new file mode 100644 index 000000000..862deb1ab --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php @@ -0,0 +1,197 @@ +em = $em; + $this->centerResolverManager = $centerResolverManager; + $this->authorizationHelperForCurrentUser = $authorizationHelperForCurrentUser; + } + + public function buildQueryByPerson(Person $person): QueryBuilder + { + $qb = $this->em->getRepository(PersonDocument::class)->createQueryBuilder('d'); + + $qb + ->where($qb->expr()->eq('d.person', ':person')) + ->setParameter('person', $person); + + return $qb; + } + + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface + { + $query = $this->buildBaseFetchQueryForPerson($person, $startDate, $endDate, $content); + + return $this->addFetchQueryByPersonACL($query, $person); + } + + public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + + $query = new FetchQuery( + PersonDocumentGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', stored_obj.%s)', $storedObjectMetadata->getSingleIdentifierColumnName()), + sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + 'activity.person_id = ?', + [$person->getId()], + [Types::INTEGER] + ); + + if (null !== $startDate) { + $query->addWhereClause( + sprintf('stored_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), + [$startDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $endDate) { + $query->addWhereClause( + sprintf('stored_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), + [$endDate], + [Types::DATE_IMMUTABLE] + ); + } + + if (null !== $content) { + $query->addWhereClause( + 'stored_obj.title ilike ?', + ['%' . $content . '%'], + [Types::STRING] + ); + } + + return $query; + } + + public function countByPerson(Person $person): int + { + $qb = $this->buildQueryByPerson($person)->select('COUNT(d)'); + + $this->addACL($qb, $person); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array + { + $qb = $this->buildQueryByPerson($person)->select('d'); + + $this->addACL($qb, $person); + + foreach ($orderBy as $field => $order) { + $qb->addOrderBy('d.' . $field, $order); + } + + $qb->setFirstResult($offset)->setMaxResults($limit); + + return $qb->getQuery()->getResult(); + } + + private function addACL(QueryBuilder $qb, Person $person): void + { + $reachableScopes = []; + + foreach ($this->centerResolverManager->resolveCenters($person) as $center) { + $reachableScopes = [ + ...$reachableScopes, + ...$this->authorizationHelperForCurrentUser + ->getReachableScopes( + PersonDocumentVoter::SEE, + $center + ) + ]; + } + + if ([] === $reachableScopes) { + $qb->andWhere("'FALSE' = 'TRUE'"); + + return; + } + + $qb->andWhere($qb->expr()->in('d.scope', ':scopes')) + ->setParameter('scopes', $reachableScopes); + } + + private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery + { + $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); + + $reachableScopes = []; + + foreach ($this->centerResolverManager->resolveCenters($person) as $center) { + $reachableScopes = [ + ...$reachableScopes, + ...$this->authorizationHelperForCurrentUser->getReachableScopes(PersonDocumentVoter::SEE, $center) + ]; + } + + if ([] === $reachableScopes) { + $fetchQuery->addWhereClause('FALSE = TRUE'); + + return $fetchQuery; + } + + $fetchQuery->addWhereClause( + sprintf( + 'person_document.%s IN (%s)', + $personDocMetadata->getSingleAssociationJoinColumnName('scope'), + implode(', ', array_fill(0, count($reachableScopes), '?')) + ), + array_map(static fn (Scope $s) => $s->getId(), $reachableScopes), + array_fill(0, count($reachableScopes), Types::INTEGER) + ); + + return $fetchQuery; + } +} diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php new file mode 100644 index 000000000..d00a23e79 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php @@ -0,0 +1,57 @@ +security = $security; + $this->personActivityDocumentACLAwareRepository = $personActivityDocumentACLAwareRepository; + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + return $this->personActivityDocumentACLAwareRepository->buildFetchQueryForPerson( + $person, + $startDate, + $endDate, + $content + ); + } + + /** + * @param Person $person + * @return bool + */ + public function isAllowedForPerson(Person $person): bool + { + return $this->security->isGranted(ActivityVoter::SEE, $person); + } +} From 7eb4fb4e563d618b562786e9d36c1c6aa820ea58 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 1 Jun 2023 13:09:51 +0200 Subject: [PATCH 30/40] FEATURE [calendar][docs] fix query to display rendez-vous documents from person and parcours contexts --- .../Providers/PersonCalendarGenericDocProvider.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php index bed04414c..640f0d197 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -98,13 +98,6 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi ) ); - $query->addWhereClause( - sprintf('calendar.%s = ?', - $calendarMetadata->getAssociationMapping('person')['joinColumns'][0]['name']), - [$person->getId()], - [Types::INTEGER] - ); - // get the documents associated with accompanying periods in which person participates $or = []; $orParams = []; From 2aeb72811a68f51dfc2c1ff300c34c7aa28a62ae Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 1 Jun 2023 13:31:35 +0200 Subject: [PATCH 31/40] [activity][docs] attempt to implement generic doc for activity documents in person context --- ...rsonActivityDocumentACLAwareRepository.php | 64 +++++++++++++------ ...anyingPeriodActivityGenericDocProvider.php | 4 +- ...anyingPeriodActivityGenericDocRenderer.php | 3 +- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php index 862deb1ab..84bb08fb4 100644 --- a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; +use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\GenericDoc\FetchQuery; @@ -22,25 +24,22 @@ use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\Security\Core\Security; class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface { - private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser; - - private CenterResolverManagerInterface $centerResolverManager; - - private EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $em, CenterResolverManagerInterface $centerResolverManager, AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser) + public function __construct( + private EntityManagerInterface $em, + private CenterResolverManagerInterface $centerResolverManager, + private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, + private Security $security) { - $this->em = $em; - $this->centerResolverManager = $centerResolverManager; - $this->authorizationHelperForCurrentUser = $authorizationHelperForCurrentUser; } public function buildQueryByPerson(Person $person): QueryBuilder @@ -65,10 +64,11 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); $query = new FetchQuery( PersonDocumentGenericDocProvider::KEY, - sprintf('jsonb_build_object(\'id\', stored_obj.%s)', $storedObjectMetadata->getSingleIdentifierColumnName()), + sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) ); @@ -81,11 +81,39 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' ); - $query->addWhereClause( + // add documents of activities from parcours context + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(activity.%s = ? AND stored_obj.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $activityMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; + } + + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + + $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); + +/* $query->addWhereClause( 'activity.person_id = ?', [$person->getId()], [Types::INTEGER] - ); + );*/ if (null !== $startDate) { $query->addWhereClause( @@ -147,7 +175,7 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware ...$reachableScopes, ...$this->authorizationHelperForCurrentUser ->getReachableScopes( - PersonDocumentVoter::SEE, + ActivityVoter::SEE, $center ) ]; @@ -165,14 +193,14 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery { - $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); $reachableScopes = []; foreach ($this->centerResolverManager->resolveCenters($person) as $center) { $reachableScopes = [ ...$reachableScopes, - ...$this->authorizationHelperForCurrentUser->getReachableScopes(PersonDocumentVoter::SEE, $center) + ...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center) ]; } @@ -184,8 +212,8 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware $fetchQuery->addWhereClause( sprintf( - 'person_document.%s IN (%s)', - $personDocMetadata->getSingleAssociationJoinColumnName('scope'), + 'activity.%s IN (%s)', + $activityMetadata->getSingleAssociationJoinColumnName('scope'), implode(', ', array_fill(0, count($reachableScopes), '?')) ), array_map(static fn (Scope $s) => $s->getId(), $reachableScopes), diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php index 8c787fd3a..284182cfb 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\GenericDoc\Providers; +use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\GenericDoc\FetchQuery; @@ -36,10 +37,11 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); $query = new FetchQuery( self::KEY, - "jsonb_build_object('id', doc_obj.id, 'activity_id', activity.id)", + sprintf("jsonb_build_object('id', doc_obj.%s, 'activity_id', activity.%s)", $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), 'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'), $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj' ); diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php index f3b70dac2..9f9a4bec8 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Service\GenericDoc\Renderers; use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; +use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; use Chill\DocStoreBundle\Repository\StoredObjectRepository; @@ -31,7 +32,7 @@ final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRe public function supports(GenericDocDTO $genericDocDTO, $options = []): bool { - return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY; + return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY || $genericDocDTO->key === PersonActivityGenericDocProvider::KEY; } public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string From 727e9d0f74fa53818ca0cadd2f9da1470e1e91b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 12 Jun 2023 17:58:27 +0200 Subject: [PATCH 32/40] WIP: fix loading of activity document --- ...=> ActivityDocumentACLAwareRepository.php} | 113 +++++++----------- ...ityDocumentACLAwareRepositoryInterface.php | 28 +++++ .../Repository/ActivityRepository.php | 1 - ...anyingPeriodActivityGenericDocProvider.php | 20 +++- .../PersonActivityGenericDocProvider.php | 19 ++- ...ActivityDocumentACLAwareRepositoryTest.php | 113 ++++++++++++++++++ 6 files changed, 209 insertions(+), 85 deletions(-) rename src/Bundle/ChillActivityBundle/Repository/{PersonActivityDocumentACLAwareRepository.php => ActivityDocumentACLAwareRepository.php} (69%) create mode 100644 src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php create mode 100644 src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php diff --git a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php similarity index 69% rename from src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php rename to src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index 84bb08fb4..f3f61ad3d 100644 --- a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -23,16 +23,18 @@ use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\QueryBuilder; use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\Security\Core\Security; -class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface +final readonly class ActivityDocumentACLAwareRepository implements ActivityDocumentACLAwareRepositoryInterface { public function __construct( private EntityManagerInterface $em, @@ -42,26 +44,43 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware { } - public function buildQueryByPerson(Person $person): QueryBuilder + public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface { - $qb = $this->em->getRepository(PersonDocument::class)->createQueryBuilder('d'); - - $qb - ->where($qb->expr()->eq('d.person', ':person')) - ->setParameter('person', $person); - - return $qb; - } - - - public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface - { - $query = $this->buildBaseFetchQueryForPerson($person, $startDate, $endDate, $content); + $query = $this->buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); return $this->addFetchQueryByPersonACL($query, $person); } - public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $query = new FetchQuery( + PersonDocumentGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), + sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + sprintf('activity.%s = ?', $activityMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); + + return $this->addWhereClauses($query, $startDate, $endDate, $content); + } + + public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); $activityMetadata = $this->em->getClassMetadata(Activity::class); @@ -109,11 +128,12 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); -/* $query->addWhereClause( - 'activity.person_id = ?', - [$person->getId()], - [Types::INTEGER] - );*/ + return $this->addWhereClauses($query, $startDate, $endDate, $content); + } + + private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); if (null !== $startDate) { $query->addWhereClause( @@ -131,7 +151,7 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware ); } - if (null !== $content) { + if (null !== $content or '' !== $content) { $query->addWhereClause( 'stored_obj.title ilike ?', ['%' . $content . '%'], @@ -142,55 +162,6 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware return $query; } - public function countByPerson(Person $person): int - { - $qb = $this->buildQueryByPerson($person)->select('COUNT(d)'); - - $this->addACL($qb, $person); - - return $qb->getQuery()->getSingleScalarResult(); - } - - public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array - { - $qb = $this->buildQueryByPerson($person)->select('d'); - - $this->addACL($qb, $person); - - foreach ($orderBy as $field => $order) { - $qb->addOrderBy('d.' . $field, $order); - } - - $qb->setFirstResult($offset)->setMaxResults($limit); - - return $qb->getQuery()->getResult(); - } - - private function addACL(QueryBuilder $qb, Person $person): void - { - $reachableScopes = []; - - foreach ($this->centerResolverManager->resolveCenters($person) as $center) { - $reachableScopes = [ - ...$reachableScopes, - ...$this->authorizationHelperForCurrentUser - ->getReachableScopes( - ActivityVoter::SEE, - $center - ) - ]; - } - - if ([] === $reachableScopes) { - $qb->andWhere("'FALSE' = 'TRUE'"); - - return; - } - - $qb->andWhere($qb->expr()->in('d.scope', ':scopes')) - ->setParameter('scopes', $reachableScopes); - } - private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery { $activityMetadata = $this->em->getClassMetadata(Activity::class); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php new file mode 100644 index 000000000..eb9e82cbe --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php @@ -0,0 +1,28 @@ +security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); } + + public function isAllowedForPerson(Person $person): bool + { + return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + return $this->activityDocumentACLAwareRepository + ->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); + } } diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php index d00a23e79..b1bd1d712 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php @@ -11,7 +11,8 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\GenericDoc\Providers; -use Chill\ActivityBundle\Repository\PersonActivityDocumentACLAwareRepository; +use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository; +use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; @@ -21,24 +22,20 @@ use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; -final class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface +final readonly class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface { public const KEY = 'person_activity_document'; - private Security $security; - - private PersonActivityDocumentACLAwareRepository $personActivityDocumentACLAwareRepository; - - public function __construct(Security $security, PersonActivityDocumentACLAwareRepository $personActivityDocumentACLAwareRepository -) + public function __construct( + private Security $security, + private ActivityDocumentACLAwareRepositoryInterface $personActivityDocumentACLAwareRepository, + ) { - $this->security = $security; - $this->personActivityDocumentACLAwareRepository = $personActivityDocumentACLAwareRepository; } public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { - return $this->personActivityDocumentACLAwareRepository->buildFetchQueryForPerson( + return $this->personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext( $person, $startDate, $endDate, diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php new file mode 100644 index 000000000..7390963fc --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php @@ -0,0 +1,113 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class); + $this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class); + $this->security = self::$container->get(Security::class); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, array $reachableScopes, bool $_unused, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes(ActivityVoter::SEE, Argument::any()) + ->willReturn($reachableScopes); + + $repository = new ActivityDocumentACLAwareRepository( + $this->entityManager, + $this->centerResolverManager, + $authorizationHelper->reveal(), + $this->security + ); + + $query = $repository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); + + self::assertIsInt($nb); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, array $_unused, bool $canSeePeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(ActivityVoter::SEE, Argument::type(AccompanyingPeriod::class)) + ->willReturn($canSeePeriod); + + $repository = new ActivityDocumentACLAwareRepository( + $this->entityManager, + $this->centerResolverManager, + $this->authorizationHelperForCurrentUser, + $security->reveal() + ); + + $query = $repository->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); + + self::assertIsInt($nb); + } + + public function provideDataForPerson(): iterable + { + $this->setUp(); + + if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ") + ->setMaxResults(1) + ->getSingleResult()) { + throw new \RuntimeException("no person in dtabase"); + } + + if ([] === $scopes = $this->entityManager->createQuery("SELECT s FROM " . Scope::class . " s ")->setMaxResults(5)->getResult()) { + throw new \RuntimeException("no scopes in database"); + } + + yield [$person, [], true, null, null, null]; + yield [$person, $scopes, true, null, null, null]; + yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), null, null]; + yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), null]; + yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; + yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"]; + yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; + } + +} From 4456fb3749a78544451f6561605dd6db5a3b26c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 11:01:40 +0200 Subject: [PATCH 33/40] Fixing generic doc providers from calendar + fix cs --- .../ActivityDocumentACLAwareRepository.php | 4 +- ...ityDocumentACLAwareRepositoryInterface.php | 9 ++ ...anyingPeriodActivityGenericDocProvider.php | 2 +- .../PersonActivityGenericDocProvider.php | 3 +- ...anyingPeriodActivityGenericDocRenderer.php | 1 - ...ActivityDocumentACLAwareRepositoryTest.php | 13 ++ ...anyingPeriodCalendarGenericDocProvider.php | 113 +++++++++++++-- .../PersonCalendarGenericDocProvider.php | 51 +++---- .../AccompanyingCourseDocumentProvider.php | 54 +++++++ ...ngPeriodCalendarGenericDocProviderTest.php | 134 ++++++++++++++++++ .../PersonCalendarGenericDocProviderTest.php | 70 +++++++++ 11 files changed, 401 insertions(+), 53 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index f3f61ad3d..180d7fc4a 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -40,8 +40,8 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum private EntityManagerInterface $em, private CenterResolverManagerInterface $centerResolverManager, private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, - private Security $security) - { + private Security $security + ) { } public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php index eb9e82cbe..9f4a9c0f8 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php @@ -1,5 +1,14 @@ getSchemaName().'.'.$classMetadata->getTableName().' AS cd' ); $query->addJoinClause( - sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', - $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), - $storedObjectMetadata->getColumnName('id'), - $classMetadata->getSingleAssociationJoinColumnName('storedObject') - )); + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); $query->addJoinClause( - sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), $calendarMetadata->getColumnName('id'), $classMetadata->getSingleAssociationJoinColumnName('calendar') @@ -65,12 +76,91 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo ); $query->addWhereClause( - sprintf('calendar.%s = ?', - $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), + sprintf( + 'calendar.%s = ?', + $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name'] + ), [$accompanyingPeriod->getId()], [Types::INTEGER] ); + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); + + $query->addJoinClause( + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) + ); + + // get the documents associated with accompanying periods in which person participates + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; + } + + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); + } + + public function isAllowedForPerson(Person $person): bool + { + // check that the person is allowed to see an accompanying period. If yes, the + // ACL on each accompanying period will be checked when the query is build + return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); + } + + private function addWhereClausesToQuery(FetchQuery $query, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + if (null !== $startDate) { $query->addWhereClause( sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), @@ -98,10 +188,5 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo return $query; } - public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool - { - return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); - } - } diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php index 640f0d197..f5d4b3cbb 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -24,9 +24,15 @@ use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\MappingException; +use Service\GenericDoc\Providers\PersonCalendarGenericDocProviderTest; use Symfony\Component\Security\Core\Security; -final class PersonCalendarGenericDocProvider implements GenericDocForPersonProviderInterface +/** + * Provide calendar documents for calendar associated to persons + * + * @see PersonCalendarGenericDocProviderTest + */ +final readonly class PersonCalendarGenericDocProvider implements GenericDocForPersonProviderInterface { public const KEY = 'person_calendar_document'; @@ -36,7 +42,6 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi ) { } - private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); @@ -84,50 +89,30 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' ); $query->addJoinClause( - sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), $storedObjectMetadata->getColumnName('id'), $classMetadata->getSingleAssociationJoinColumnName('storedObject') - )); + ) + ); $query->addJoinClause( - sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), $calendarMetadata->getColumnName('id'), $classMetadata->getSingleAssociationJoinColumnName('calendar') ) ); - // get the documents associated with accompanying periods in which person participates - $or = []; - $orParams = []; - $orTypes = []; - foreach ($person->getAccompanyingPeriodParticipations() as $participation) { - if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) { - continue; - } - - $or[] = sprintf( - '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', - $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), - $storedObjectMetadata->getColumnName('createdAt') - ); - $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), - DateTimeImmutable::createFromInterface($participation->getStartDate()), - null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; - $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; - } - - if ([] === $or) { - $query->addWhereClause('TRUE = FALSE'); - - return $query; - } - -// $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); + $query->addWhereClause( + sprintf('calendar.%s = ?', $calendarMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); - } /** diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php new file mode 100644 index 000000000..163d17458 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php @@ -0,0 +1,54 @@ +entityManager->getClassMetadata(AccompanyingCourseDocument::class); + + $query = new FetchQuery( + 'accompanying_course_document', + sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), + sprintf($classMetadata->getColumnName('date')), + $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() + ); + + $query->addWhereClause( + sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')), + [$accompanyingPeriod->getId()] + ); + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php new file mode 100644 index 000000000..8aff23673 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php @@ -0,0 +1,134 @@ +security = self::$container->get(Security::class); + $this->entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @dataProvider provideDataForAccompanyingPeriod + */ + public function testBuildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $provider = new AccompanyingPeriodCalendarGenericDocProvider($this->security, $this->entityManager); + + $query = $provider->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(true); + + $provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + self::assertStringNotContainsStringIgnoringCase('FALSE = TRUE', $sql); + self::assertStringNotContainsStringIgnoringCase('TRUE = FALSE', $sql); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryForPersonWithoutAnyRight(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(false); + + $provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + self::assertStringContainsStringIgnoringCase('TRUE = FALSE', $sql); + } + + public function provideDataForPerson(): iterable + { + $this->setUp(); + + if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0") + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException("There is no person"); + } + + yield [$person, null, null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), "text"]; + yield [$person, null, null, "text"]; + yield [$person, null, new \DateTimeImmutable("6 month ago"), null]; + } + + public function provideDataForAccompanyingPeriod(): iterable + { + $this->setUp(); + + if (null === $period = $this->entityManager->createQuery("SELECT p FROM " . AccompanyingPeriod::class . " p ") + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException("There is no accompanying period"); + } + + yield [$period, null, null, null]; + yield [$period, new \DateTimeImmutable("1 year ago"), null, null]; + yield [$period, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), null]; + yield [$period, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), "text"]; + yield [$period, null, null, "text"]; + yield [$period, null, new \DateTimeImmutable("6 month ago"), null]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php new file mode 100644 index 000000000..dbad052ca --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php @@ -0,0 +1,70 @@ +security = self::$container->get(Security::class); + $this->entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $provider = new PersonCalendarGenericDocProvider($this->security, $this->entityManager); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + } + + public function provideDataForPerson(): iterable + { + $this->setUp(); + + if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p ") + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException("There is no person"); + } + + yield [$person, null, null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), "text"]; + yield [$person, null, null, "text"]; + yield [$person, null, new \DateTimeImmutable("6 month ago"), null]; + } +} From 1f1ebb6adb0f4c3c823ed803dd745848bc9aad75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 11:30:00 +0200 Subject: [PATCH 34/40] fix mismatch for key in different providers --- .../Repository/ActivityDocumentACLAwareRepository.php | 6 ++++-- .../AccompanyingCourseDocumentGenericDocRenderer.php | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index 180d7fc4a..3ca9281af 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -13,6 +13,8 @@ namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; +use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; +use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider; use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\GenericDoc\FetchQuery; @@ -57,7 +59,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum $activityMetadata = $this->em->getClassMetadata(Activity::class); $query = new FetchQuery( - PersonDocumentGenericDocProvider::KEY, + PersonActivityGenericDocProvider::KEY, sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) @@ -86,7 +88,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum $activityMetadata = $this->em->getClassMetadata(Activity::class); $query = new FetchQuery( - PersonDocumentGenericDocProvider::KEY, + AccompanyingPeriodActivityGenericDocProvider::KEY, sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php index 052153be8..d9c33373e 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php @@ -40,6 +40,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { + dump($genericDocDTO); if (AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key) { return [ 'document' => $doc = $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), @@ -47,10 +48,9 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen 'options' => $options, ]; } - // this is a person return [ - 'document' => $doc = $this->personDocumentRepository->find($genericDocDTO->identifiers['id']), + 'document' => $doc = dump($this->personDocumentRepository->find($genericDocDTO->identifiers['id'])), 'person' => $doc->getPerson(), 'options' => $options, ]; From 909d2dfb60de67059d1391409ba5a42c2bdd754d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 15:00:16 +0200 Subject: [PATCH 35/40] layout for rendering list --- .../Resources/public/chill/chillactivity.scss | 24 +++++++++++ .../GenericDoc/activity_document.html.twig | 32 +++++++------- ...anyingPeriodActivityGenericDocRenderer.php | 4 +- .../translations/messages.fr.yml | 5 +++ .../Resources/public/chill/chill.js | 1 + .../Resources/public/chill/scss/badge.scss | 25 +++++++++++ .../Resources/public/chill/scss/calendar.scss | 2 +- .../GenericDoc/calendar_document.html.twig | 42 +++++++++++-------- ...anyingPeriodCalendarGenericDocRenderer.php | 3 +- .../chill.webpack.config.js | 2 + .../translations/messages.fr.yml | 4 +- .../GenericDoc/GenericDocDTO.php | 5 +++ ...anyingCourseDocumentGenericDocRenderer.php | 5 ++- .../Resources/views/List/list_item.html.twig | 10 ++++- .../translations/messages.fr.yml | 1 + .../chill/scss/accompanying_period_work.scss | 22 ++++++++++ .../GenericDoc/evaluation_document.html.twig | 23 +++++----- ...PeriodWorkEvaluationGenericDocRenderer.php | 4 +- 18 files changed, 162 insertions(+), 52 deletions(-) create mode 100644 src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js create mode 100644 src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss diff --git a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss index d70bd28c3..13de8dfc0 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss +++ b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss @@ -1,5 +1,7 @@ // Access to Bootstrap variables and mixins @import '~ChillMainAssets/module/bootstrap/shared'; +@import '~ChillPersonAssets/chill/scss/mixins.scss'; +@import 'bootstrap/scss/_badge.scss'; //// ACTIVITY CREATION // first step: select type page @@ -96,3 +98,25 @@ li.document-list-item { justify-content: space-between; margin-bottom: 0.3rem; } + +.badge-activity-type { + display: inline-block; + background-color: #f3f3f3; + + .title_label { + @include chill_badge(#9acd32); + } + + .title_action { + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + margin-right: 1rem; + + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + } +} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig index 11aeeeca1..5b9547675 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document.html.twig @@ -20,8 +20,25 @@ {% elseif document.isFailure %}
              {{ 'docgen.Doc generation failed'|trans }}
              {% endif %} + +
              + {% if activity.accompanyingPeriod is not null and context == 'person' %} + + {{ activity.accompanyingPeriod.id }} +   + {% endif %} +
              + + + {{ activity.type.name | localize_translatable_string }} + {% if activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +
              +
              - {{ document.title }} + {{ document.title|chill_print_or_message("No title") }}
              {% if document.hasTemplate %}
              @@ -39,19 +56,6 @@
            -
            -
            -

            - - - {{ activity.type.name | localize_translatable_string }} - {% if activity.emergency %} - {{ 'Emergency'|trans|upper }} - {% endif %} - -

            -
            -
            diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php index a07137206..c465dbca1 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php @@ -17,6 +17,7 @@ use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocPr use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use Chill\PersonBundle\Entity\AccompanyingPeriod; final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface { @@ -44,7 +45,8 @@ final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRe { return [ 'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']), - 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']) + 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']), + 'context' => $genericDocDTO->getContext(), ]; } } diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 042fccc69..abef160d3 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -372,3 +372,8 @@ export: is sent: envoyé is received: reçu Group activity by sentreceived: Grouper les échanges par envoyé / reçu + +generic_doc: + filter: + keys: + accompanying_period_activity_document: Document des échanges des parcours diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js b/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js new file mode 100644 index 000000000..56a8ce563 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js @@ -0,0 +1 @@ +import './scss/badge.scss'; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss new file mode 100644 index 000000000..ffcda8f0f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss @@ -0,0 +1,25 @@ +@import '~ChillPersonAssets/chill/scss/mixins.scss'; +@import '~ChillMainAssets/module/bootstrap/shared'; + +.badge-calendar { + display: inline-block; + background-color: #f3f3f3; + + .title_label { + @include chill_badge($chill-l-gray); + } + + .title_action { + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + margin-right: 1rem; + + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + } +} + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss index a2c0c4b89..ce54b0fa8 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss @@ -17,4 +17,4 @@ span.calendarRangeItems { text-decoration: none; padding: 3px; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig index 4cd369366..facf5be50 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig @@ -12,11 +12,31 @@ {% elseif document.storedObject.isFailure %}
            {{ 'docgen.Doc generation failed'|trans }}
            {% endif %} -
            - {{ document.storedObject.title }} -
            +
            - {{ 'chill_calendar.Document'|trans }} + {% if c.accompanyingPeriod is not null and context == 'person' %} + + {{ c.accompanyingPeriod.id }} +   + {% endif %} + + + + + {{ 'Calendar'|trans }} + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} + + +
            + +
            + {{ document.storedObject.title|chill_print_or_message("No title") }}
            {% if document.storedObject.hasTemplate %}
            @@ -34,20 +54,6 @@
            -
            -
            -

            - {% if c.endDate.diff(c.startDate).days >= 1 %} - {{ c.startDate|format_datetime('short', 'short') }} - - {{ c.endDate|format_datetime('short', 'short') }} - {% else %} - {{ c.startDate|format_datetime('short', 'short') }} - - {{ c.endDate|format_datetime('none', 'short') }} - {% endif %} -

            -
            -
            -
            {{ mmm.createdBy(document) }} diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php index d9636d99d..b15c091c6 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php @@ -39,7 +39,8 @@ final class AccompanyingPeriodCalendarGenericDocRenderer implements GenericDocRe public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { return [ - 'document' => $this->repository->find($genericDocDTO->identifiers['id']) + 'document' => $this->repository->find($genericDocDTO->identifiers['id']), + 'context' => $genericDocDTO->getContext(), ]; } } diff --git a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js index e82210087..9d45a3142 100644 --- a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js +++ b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js @@ -1,6 +1,8 @@ // this file loads all assets from the Chill calendar bundle module.exports = function(encore, entries) { + entries.push(__dirname + '/Resources/public/chill/chill.js'); + encore.addAliases({ ChillCalendarAssets: __dirname + '/Resources/public' }); diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index 99aae1082..c56d7835f 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -151,5 +151,5 @@ CHILL_CALENDAR_CALENDAR_SEE: Voir les rendez-vous generic_doc: filter: keys: - accompanying_period_calendar_document: Document des rendez-vous - person_calendar_document: Document des rendez-vous de la personne + accompanying_period_calendar_document: Document des rendez-vous des parcours + person_calendar_document: Document des rendez-vous de l'usager diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php index 25a96750c..fe9bf7e4f 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php @@ -23,4 +23,9 @@ final readonly class GenericDocDTO public AccompanyingPeriod|Person $linked, ) { } + + public function getContext(): string + { + return $this->linked instanceof AccompanyingPeriod ? 'accompanying-period' : 'person'; + } } diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php index d9c33373e..c32620030 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php @@ -40,19 +40,20 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { - dump($genericDocDTO); if (AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key) { return [ 'document' => $doc = $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']), 'accompanyingCourse' => $doc->getCourse(), 'options' => $options, + 'context' => $genericDocDTO->getContext(), ]; } // this is a person return [ - 'document' => $doc = dump($this->personDocumentRepository->find($genericDocDTO->identifiers['id'])), + 'document' => $doc = $this->personDocumentRepository->find($genericDocDTO->identifiers['id']), 'person' => $doc->getPerson(), 'options' => $options, + 'context' => $genericDocDTO->getContext(), ]; } } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig index 8dea4bbe7..9be38074d 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig @@ -10,8 +10,16 @@ {% elseif document.object.isFailure %}
            {{ 'docgen.Doc generation failed'|trans }}
            {% endif %} + + {% if context == 'person' and accompanyingCourse is defined %} +
            + + {{ accompanyingCourse.id }} +   +
            + {% endif %}
            - {{ document.title }} + {{ document.title|chill_print_or_message("No title") }}
            {% if document.object.type is not empty %}
            diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 78de523c4..f8f231a7c 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -42,6 +42,7 @@ by_user: "par " lifecycleUpdate: Evenements de création et mise à jour address_fields: Données liées à l'adresse Datas: Données +No title: Aucun titre inactive: inactif diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss index a12f1554c..e82029a1f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss @@ -1,3 +1,25 @@ +.badge-accompanying-work-type { + display: inline-block; + background-color: #f3f3f3; + + .title_label { + @include chill_badge(#e2793d); + } + + .title_action { + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + margin-right: 1rem; + + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + } +} + /// AccompanyingCourse Work Pages div.accompanying-course-work { diff --git a/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig index 255139bb4..ae54d077b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document.html.twig @@ -12,8 +12,19 @@ {% elseif document.storedObject.isFailure %}
            {{ 'docgen.Doc generation failed'|trans }}
            {% endif %} +
            + {% if context == 'person' %} + + {{ w.accompanyingPeriod.id }} +   + {% endif %} +
            + + {{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }} +
            +
            - {{ document.title }} + {{ document.title|chill_print_or_message("No title") }}
            {% if document.storedObject.type is not empty %}
            @@ -36,16 +47,6 @@
            -
            -
            -

            - - {{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }} - -

            -
            -
            -
            {{ mmm.createdBy(document) }} diff --git a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php index 5da8297fb..9810fe7a1 100644 --- a/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php +++ b/src/Bundle/ChillPersonBundle/Service/GenericDoc/Renderer/AccompanyingPeriodWorkEvaluationGenericDocRenderer.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Service\GenericDoc\Renderer; use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository; use Chill\PersonBundle\Service\GenericDoc\Providers\AccompanyingPeriodWorkEvaluationGenericDocProvider; @@ -36,7 +37,8 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocRenderer implemen public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array { return [ - 'document' => $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id']) + 'document' => $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($genericDocDTO->identifiers['id']), + 'context' => $genericDocDTO->getContext(), ]; } } From 29140d9374f534f7d21f29000948f0d36d5610c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 15:16:03 +0200 Subject: [PATCH 36/40] add changelog entry --- .changes/unreleased/Feature-20230613-151546.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/unreleased/Feature-20230613-151546.yaml diff --git a/.changes/unreleased/Feature-20230613-151546.yaml b/.changes/unreleased/Feature-20230613-151546.yaml new file mode 100644 index 000000000..e66076aa5 --- /dev/null +++ b/.changes/unreleased/Feature-20230613-151546.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: Get an unified list of document in person and accompanying period context +time: 2023-06-13T15:15:46.146899906+02:00 +custom: + Issue: "103" From 24049b9dfcceb83764d0cbc044de18268caf9dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 15:33:03 +0200 Subject: [PATCH 37/40] remove unused file --- .../AccompanyingCourseDocumentProvider.php | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php deleted file mode 100644 index 163d17458..000000000 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php +++ /dev/null @@ -1,54 +0,0 @@ -entityManager->getClassMetadata(AccompanyingCourseDocument::class); - - $query = new FetchQuery( - 'accompanying_course_document', - sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), - sprintf($classMetadata->getColumnName('date')), - $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() - ); - - $query->addWhereClause( - sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')), - [$accompanyingPeriod->getId()] - ); - - return $query; - } - - public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool - { - return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod); - } -} From 5495b1cb447b2763948776dd3fc4440d9a73e485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 15:33:10 +0200 Subject: [PATCH 38/40] fix condition --- .../Repository/ActivityDocumentACLAwareRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index 3ca9281af..ce70409ba 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -153,7 +153,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum ); } - if (null !== $content or '' !== $content) { + if (null !== $content and '' !== $content) { $query->addWhereClause( 'stored_obj.title ilike ?', ['%' . $content . '%'], From 7a1feaa8cb2e09560953eb087e05e285be5c2405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 27 Jun 2023 18:20:41 +0200 Subject: [PATCH 39/40] show documents from person in list of document from course --- .../PersonDocumentGenericDocProvider.php | 16 +++- .../PersonDocumentACLAwareRepository.php | 89 ++++++++++++++++--- ...sonDocumentACLAwareRepositoryInterface.php | 8 ++ .../Resources/views/List/list_item.html.twig | 9 +- .../PersonDocumentACLAwareRepositoryTest.php | 61 ++++++++++++- .../translations/messages.fr.yml | 2 + .../Entity/AccompanyingPeriod.php | 2 + .../translations/messages.fr.yml | 1 - 8 files changed, 171 insertions(+), 17 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php index 08a0df960..613f8d758 100644 --- a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php @@ -12,14 +12,16 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\GenericDoc\Providers; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; +use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface; use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use DateTimeImmutable; use Symfony\Component\Security\Core\Security; -final readonly class PersonDocumentGenericDocProvider implements GenericDocForPersonProviderInterface +final readonly class PersonDocumentGenericDocProvider implements GenericDocForPersonProviderInterface, GenericDocForAccompanyingPeriodProviderInterface { public const KEY = 'person_document'; @@ -48,4 +50,16 @@ final readonly class PersonDocumentGenericDocProvider implements GenericDocForPe { return $this->security->isGranted(PersonDocumentVoter::SEE, $person); } + + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + return $this->personDocumentACLAwareRepository->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content); + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + // we assume that the user is allowed to see at least one person of the course + // this will be double checked when running the query + return true; + } } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php index 5d85541aa..26a42b894 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php @@ -22,6 +22,8 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Person; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; @@ -29,19 +31,14 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Security\Core\Security; -class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface +final readonly class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface { - private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser; - - private CenterResolverManagerInterface $centerResolverManager; - - private EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $em, CenterResolverManagerInterface $centerResolverManager, AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser) - { - $this->em = $em; - $this->centerResolverManager = $centerResolverManager; - $this->authorizationHelperForCurrentUser = $authorizationHelperForCurrentUser; + public function __construct( + private EntityManagerInterface $em, + private CenterResolverManagerInterface $centerResolverManager, + private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, + private Security $security, + ) { } public function buildQueryByPerson(Person $person): QueryBuilder @@ -63,6 +60,66 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito return $this->addFetchQueryByPersonACL($query, $person); } + public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface + { + $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); + $participationMetadata = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class); + + $query = new FetchQuery( + PersonDocumentGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', person_document.%s)', $personDocMetadata->getSingleIdentifierColumnName()), + sprintf('person_document.%s', $personDocMetadata->getColumnName('date')), + sprintf('%s AS person_document', $personDocMetadata->getSchemaName().'.'.$personDocMetadata->getTableName()) + ); + + $query->addJoinClause( + sprintf( + 'JOIN %s AS participation ON participation.%s = person_document.%s '. + 'AND person_document.%s BETWEEN participation.%s AND COALESCE(participation.%s, \'infinity\'::date)', + $participationMetadata->getTableName(), + $participationMetadata->getSingleAssociationJoinColumnName('person'), + $personDocMetadata->getSingleAssociationJoinColumnName('person'), + $personDocMetadata->getColumnName('date'), + $participationMetadata->getColumnName('startDate'), + $participationMetadata->getColumnName('endDate') + ) + ); + + $query->addWhereClause( + sprintf('participation.%s = ?', $participationMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod')), + [$period->getId()], + [Types::INTEGER] + ); + + // can we see the document for this person ? + $orPersonId = []; + foreach ($period->getParticipations() as $participation) { + if (!$this->security->isGranted(PersonDocumentVoter::SEE, $participation->getPerson())) { + continue; + } + $orPersonId[] = $participation->getPerson()->getId(); + + } + + if ([] === $orPersonId) { + $query->addWhereClause('FALSE = TRUE'); + + return $query; + } + + $query->addWhereClause( + sprintf( + 'participation.%s IN (%s)', + $participationMetadata->getSingleAssociationJoinColumnName('person'), + implode(', ', array_fill(0, count($orPersonId), '?')) + ), + $orPersonId, + array_fill(0, count($orPersonId), Types::INTEGER) + ); + + return $this->addFilterClauses($query, $startDate, $endDate, $content); + } + public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); @@ -80,6 +137,13 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito [Types::INTEGER] ); + return $this->addFilterClauses($query, $startDate, $endDate, $content); + } + + private function addFilterClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class); + if (null !== $startDate) { $query->addWhereClause( sprintf('? <= %s', $personDocMetadata->getColumnName('date')), @@ -107,7 +171,6 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito [Types::STRING, Types::STRING] ); } - return $query; } diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php index 0b5e26792..f1bc70812 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Repository; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; interface PersonDocumentACLAwareRepositoryInterface @@ -32,4 +33,11 @@ interface PersonDocumentACLAwareRepositoryInterface ?\DateTimeImmutable $endDate = null, ?string $content = null ): FetchQueryInterface; + + public function buildFetchQueryForAccompanyingPeriod( + AccompanyingPeriod $period, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null + ): FetchQueryInterface; } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig index 9be38074d..58504b095 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig @@ -17,7 +17,14 @@ {{ accompanyingCourse.id }}  
            - {% endif %} + {% elseif context == 'accompanying-period' and person is defined %} +
            + + {{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }} +   +
            + + {% endif %}
            {{ document.title|chill_print_or_message("No title") }}
            diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php index 98fca5622..fd611042c 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php @@ -21,12 +21,14 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Security\Core\Security; /** * @internal @@ -66,7 +68,8 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase $repository = new PersonDocumentACLAwareRepository( $this->entityManager, $centerManager->reveal(), - $authorizationHelper->reveal() + $authorizationHelper->reveal(), + $this->prophesize(Security::class)->reveal() ); $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p") @@ -86,6 +89,62 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase self::assertIsInt($nb, "test that the query could be executed"); } + /** + * @dataProvider provideDateForFetchQueryForAccompanyingPeriod + */ + public function testBuildFetchQueryForAccompanyingPeriod( + AccompanyingPeriod $period, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null + ): void { + $centerManager = $this->prophesize(CenterResolverManagerInterface::class); + $centerManager->resolveCenters(Argument::type(Person::class)) + ->willReturn([new Center()]); + + $scopes = $this->scopeRepository->findAll(); + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes(PersonDocumentVoter::SEE, Argument::any())->willReturn($scopes); + + $security = $this->prophesize(Security::class); + $security->isGranted(PersonDocumentVoter::SEE, Argument::type(Person::class))->willReturn(true); + + $repository = new PersonDocumentACLAwareRepository( + $this->entityManager, + $centerManager->reveal(), + $authorizationHelper->reveal(), + $security->reveal() + ); + + $query = $repository->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection() + ->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb, "test that the query could be executed"); + } + + public function provideDateForFetchQueryForAccompanyingPeriod(): iterable + { + $this->setUp(); + + if (null === $period = $this->entityManager->createQuery( + "SELECT p FROM " . AccompanyingPeriod::class . " p WHERE SIZE(p.participations) > 0" + ) + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException("no course found"); + } + + yield [$period, null, null, null]; + yield [$period, new DateTimeImmutable('1 year ago'), null, null]; + yield [$period, null, new DateTimeImmutable('1 year ago'), null]; + yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null]; + yield [$period, null, null, 'test']; + yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test']; + + } + public function provideDataBuildFetchQueryForPerson(): iterable { yield [null, null, null]; diff --git a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml index 1dde57eee..d4531fa2b 100644 --- a/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocStoreBundle/translations/messages.fr.yml @@ -18,6 +18,7 @@ No document found: Aucun document trouvé The document is successfully registered: Le document est enregistré The document is successfully updated: Le document est mis à jour Any description: Aucune description +Document from person %name%: Document de l'usager %name% document: Any title: Aucun titre @@ -26,6 +27,7 @@ generic_doc: filter: keys: accompanying_course_document: Document du parcours + person_document: Documents de l'usager date-range: Date du document # delete diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index a8e191df3..18ad2da7d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -269,6 +269,7 @@ class AccompanyingPeriod implements * cascade={"persist", "refresh", "remove", "merge", "detach"}) * @Groups({"read", "docgen:read"}) * @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED}) + * @var Collection */ private Collection $participations; @@ -870,6 +871,7 @@ class AccompanyingPeriod implements /** * Get Participations Collection. + * @return Collection */ public function getParticipations(): Collection { diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 6205fabae..aeaa2bb3f 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1236,4 +1236,3 @@ generic_doc: filter: keys: accompanying_period_work_evaluation_document: Document des actions d'accompagnement - person_document: Documents de la personne From 4632c18d930dcdd794ad849e9f98ccc243e44509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 27 Jun 2023 18:26:41 +0200 Subject: [PATCH 40/40] restore feature: generate a document from period --- .../views/GenericDoc/accompanying_period_list.html.twig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig index f76e4b984..b22c7d00f 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/accompanying_period_list.html.twig @@ -8,12 +8,14 @@ {% block js %} {{ parent() }} + {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} + {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} @@ -36,6 +38,8 @@ {{ chill_pagination(pagination) }} +
            + {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}