add filter for generic doc + fix issues in filter

This commit is contained in:
Julien Fastré 2023-05-30 12:46:05 +02:00
parent a3d3588b75
commit eb107f5a15
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
10 changed files with 109 additions and 25 deletions

View File

@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\GenericDoc\Manager; use Chill\DocStoreBundle\GenericDoc\Manager;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter; use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Symfony\Bundle\TwigBundle\TwigEngine; use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -25,9 +26,10 @@ use Symfony\Component\Templating\EngineInterface;
final readonly class GenericDocForAccompanyingPeriodController final readonly class GenericDocForAccompanyingPeriodController
{ {
public function __construct( public function __construct(
private Security $security, private FilterOrderHelperFactory $filterOrderHelperFactory,
private Manager $manager, private Manager $manager,
private PaginatorFactory $paginator, private PaginatorFactory $paginator,
private Security $security,
private EngineInterface $twig, private EngineInterface $twig,
) { ) {
} }
@ -45,12 +47,41 @@ final readonly class GenericDocForAccompanyingPeriodController
throw new AccessDeniedHttpException("not allowed to see the documents for accompanying period"); 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); $paginator = $this->paginator->create($nb);
$documents = $this->manager->findDocForAccompanyingPeriod( $documents = $this->manager->findDocForAccompanyingPeriod(
$accompanyingPeriod, $accompanyingPeriod,
$paginator->getCurrentPageFirstItemNumber(), $paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage() $paginator->getItemsPerPage(),
$startDate,
$endDate,
$content,
$filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
); );
return new Response($this->twig->render( return new Response($this->twig->render(
@ -59,6 +90,7 @@ final readonly class GenericDocForAccompanyingPeriodController
'accompanyingCourse' => $accompanyingPeriod, 'accompanyingCourse' => $accompanyingPeriod,
'pagination' => $paginator, 'pagination' => $paginator,
'documents' => iterator_to_array($documents), 'documents' => iterator_to_array($documents),
'filter' => $filter,
] ]
)); ));
} }

View File

@ -14,12 +14,12 @@ namespace Chill\DocStoreBundle\GenericDoc;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
class GenericDocDTO final readonly class GenericDocDTO
{ {
public function __construct( public function __construct(
public readonly string $key, public string $key,
public readonly array $identifiers, public array $identifiers,
public readonly \DateTimeImmutable $docDate, public \DateTimeImmutable $docDate,
public AccompanyingPeriod|Person $linked, public AccompanyingPeriod|Person $linked,
) { ) {
} }

View File

@ -32,6 +32,7 @@ class Manager
} }
/** /**
* @param list<string> $places
* @throws Exception * @throws Exception
*/ */
public function countDocForAccompanyingPeriod( public function countDocForAccompanyingPeriod(
@ -39,16 +40,16 @@ class Manager
?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $startDate = null,
?\DateTimeImmutable $endDate = null, ?\DateTimeImmutable $endDate = null,
?string $content = null, ?string $content = null,
?string $origin = null array $places = []
): int { ): 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 === '') { if ($sql === '') {
return 0; return 0;
} }
$countSql = "SELECT count(*) AS c FROM ({$sql}) AS sq"; $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(); $number = $result->fetchOne();
@ -60,6 +61,7 @@ class Manager
} }
/** /**
* @param list<string> $places places to search. When empty, search in all places
* @throws Exception * @throws Exception
*/ */
public function findDocForAccompanyingPeriod( public function findDocForAccompanyingPeriod(
@ -69,15 +71,19 @@ class Manager
?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $startDate = null,
?\DateTimeImmutable $endDate = null, ?\DateTimeImmutable $endDate = null,
?string $content = null, ?string $content = null,
?string $origin = null array $places = []
): iterable { ): 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 ?"; $runSql = "{$sql} ORDER BY doc_date DESC LIMIT ? OFFSET ?";
$runParams = [...$params, ...[$limit, $offset]]; $runParams = [...$params, ...[$limit, $offset]];
$runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]]; $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( yield new GenericDocDTO(
$row['key'], $row['key'],
json_decode($row['identifiers'], true, 512, JSON_THROW_ON_ERROR), 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<string> $places places to search. When empty, search in all places
*/ */
private function buildUnionQuery( private function buildUnionQuery(
AccompanyingPeriod|Person $linked, AccompanyingPeriod|Person $linked,
?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $startDate = null,
?\DateTimeImmutable $endDate = null, ?\DateTimeImmutable $endDate = null,
?string $content = null, ?string $content = null,
?string $origin = null array $places = [],
): array { ): array {
$sql = []; $sql = [];
$params = []; $params = [];
@ -105,17 +131,20 @@ class Manager
if (!$provider->isAllowedForAccompanyingPeriod($linked)) { if (!$provider->isAllowedForAccompanyingPeriod($linked)) {
continue; continue;
} }
$query = $provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content);
['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder if ([] !== $places and !in_array($query->getSelectKeyString(), $places, true)) {
->toSql($provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content, $origin)); continue;
$params = [...$params, ...$p]; }
$types = [...$types, ...$t];
['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder->toSql($query);
$sql[] = $q; $sql[] = $q;
$params = [...$params, ...$p];
$types = [...$types, ...$t];
} }
} }
return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types]; return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types];
} }
} }

View File

@ -50,7 +50,7 @@ final readonly class AccompanyingProviderCourseDocumentGenericDoc implements Gen
if (null !== $startDate) { if (null !== $startDate) {
$query->addWhereClause( $query->addWhereClause(
sprintf('? >= %s', $classMetadata->getColumnName('date')), sprintf('? <= %s', $classMetadata->getColumnName('date')),
[$startDate], [$startDate],
[Types::DATE_IMMUTABLE] [Types::DATE_IMMUTABLE]
); );
@ -58,7 +58,7 @@ final readonly class AccompanyingProviderCourseDocumentGenericDoc implements Gen
if (null !== $endDate) { if (null !== $endDate) {
$query->addWhereClause( $query->addWhereClause(
sprintf('? < %s', $classMetadata->getColumnName('date')), sprintf('? >= %s', $classMetadata->getColumnName('date')),
[$endDate], [$endDate],
[Types::DATE_IMMUTABLE] [Types::DATE_IMMUTABLE]
); );
@ -71,7 +71,7 @@ final readonly class AccompanyingProviderCourseDocumentGenericDoc implements Gen
$classMetadata->getColumnName('title'), $classMetadata->getColumnName('title'),
$classMetadata->getColumnName('description') $classMetadata->getColumnName('description')
), ),
[$content, $content], ['%' . $content . '%', '%' . $content . '%'],
[Types::STRING, Types::STRING] [Types::STRING, Types::STRING]
); );
} }

View File

@ -22,6 +22,8 @@
<div class="document-list"> <div class="document-list">
<h1>{{ 'Documents' }}</h1> <h1>{{ 'Documents' }}</h1>
{{ filter|chill_render_filter_order_helper }}
{% if documents|length == 0 %} {% if documents|length == 0 %}
<p class="chill-no-data-statement">{{ 'No documents'|trans }}</p> <p class="chill-no-data-statement">{{ 'No documents'|trans }}</p>
{% else %} {% else %}

View File

@ -18,6 +18,7 @@ use Chill\DocStoreBundle\GenericDoc\Manager;
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; 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 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', 'accompanying_course_document',
sprintf('jsonb_build_object(\'id\', %s)', 'id'), sprintf('jsonb_build_object(\'id\', %s)', 'id'),
'd', 'd',
'(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (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 public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool

View File

@ -22,6 +22,12 @@ Any description: Aucune description
document: document:
Any title: Aucun titre Any title: Aucun titre
generic_doc:
filter:
keys:
accompanying_course_document: Document du parcours
date-range: Date du document
# delete # delete
Delete document ?: Supprimer le document ? Delete document ?: Supprimer le document ?
Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ? Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ?

View File

@ -91,6 +91,11 @@ class FilterOrderHelper
return $this->checkboxes; return $this->checkboxes;
} }
public function hasCheckBox(string $name): bool
{
return array_key_exists($name, $this->checkboxes);
}
/** /**
* @return array<'to': DateTimeImmutable, 'from': DateTimeImmutable> * @return array<'to': DateTimeImmutable, 'from': DateTimeImmutable>
*/ */

View File

@ -71,7 +71,7 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen
if (null !== $startDate) { if (null !== $startDate) {
$query->addWhereClause( $query->addWhereClause(
sprintf('doc_store.%s <= ?', $storedObjectMetadata->getColumnName('createdAt')), sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
[$startDate], [$startDate],
[Types::DATE_IMMUTABLE] [Types::DATE_IMMUTABLE]
); );
@ -79,7 +79,7 @@ final readonly class AccompanyingPeriodWorkEvaluationGenericDocProvider implemen
if (null !== $endDate) { if (null !== $endDate) {
$query->addWhereClause( $query->addWhereClause(
sprintf('doc_store.%s > ?', $storedObjectMetadata->getColumnName('createdAt')), sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
[$endDate], [$endDate],
[Types::DATE_IMMUTABLE] [Types::DATE_IMMUTABLE]
); );

View File

@ -1231,3 +1231,8 @@ social_action:
social_issue: social_issue:
and children: et dérivés and children: et dérivés
generic_doc:
filter:
keys:
accompanying_period_work_evaluation_document: Document des actions d'accompagnement