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