Compute allowed centers and regroupment at the time of generating the export

This commit is contained in:
Julien Fastré 2025-04-03 17:47:46 +02:00
parent 128d365a72
commit e48bec490c
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
17 changed files with 449 additions and 111 deletions

View File

@ -25,6 +25,7 @@ use Chill\MainBundle\Form\Type\Export\FormatterType;
use Chill\MainBundle\Form\Type\Export\PickCenterType; use Chill\MainBundle\Form\Type\Export\PickCenterType;
use Chill\MainBundle\Repository\SavedExportOrExportGenerationRepository; use Chill\MainBundle\Repository\SavedExportOrExportGenerationRepository;
use Chill\MainBundle\Security\Authorization\SavedExportVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -117,7 +118,7 @@ class ExportController extends AbstractController
$options = match ($step) { $options = match ($step) {
'export', 'generate_export' => [ 'export', 'generate_export' => [
'export_alias' => $alias, 'export_alias' => $alias,
'picked_centers' => $exportManager->getPickedCenters($data['centers'] ?? []), 'picked_centers' => $this->exportFormHelper->getPickedCenters($data),
], ],
'formatter', 'generate_formatter' => [ 'formatter', 'generate_formatter' => [
'export_alias' => $alias, 'export_alias' => $alias,
@ -337,9 +338,15 @@ class ExportController extends AbstractController
if ($this->filterStatsByCenters) { if ($this->filterStatsByCenters) {
$formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null); $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null);
$formCenters->submit($dataCenters); $formCenters->submit($dataCenters);
$dataCenters = $formCenters->getData(); $dataAsCollection = $formCenters->getData()['centers'];
$centers = $dataAsCollection['centers'];
$regroupments = $dataAsCollection['regroupments'];
$dataCenters = [
'centers' => $centers instanceof Collection ? $centers->toArray() : $centers,
'regroupments' => $regroupments instanceof Collection ? $regroupments->toArray() : $regroupments,
];
} else { } else {
$dataCenters = ['centers' => []]; $dataCenters = ['centers' => [], 'regroupments' => []];
} }
$formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, null); $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, null);
@ -358,7 +365,7 @@ class ExportController extends AbstractController
} }
return [ return [
'centers' => $dataCenters['centers'], 'centers' => ['centers' => $dataCenters['centers'], 'regroupments' => $dataCenters['regroupments'] ?? []],
'export' => $dataExport['export']['export'] ?? [], 'export' => $dataExport['export']['export'] ?? [],
'filters' => $dataExport['export']['filters'] ?? [], 'filters' => $dataExport['export']['filters'] ?? [],
'aggregators' => $dataExport['export']['aggregators'] ?? [], 'aggregators' => $dataExport['export']['aggregators'] ?? [],
@ -401,7 +408,7 @@ class ExportController extends AbstractController
false === $exportManager->isGrantedForElement( false === $exportManager->isGrantedForElement(
$export, $export,
null, null,
$exportManager->getPickedCenters($data['centers']) $this->exportFormHelper->getPickedCenters($data['centers']),
) )
) { ) {
throw $this->createAccessDeniedException('you do not have access to this export for those centers'); throw $this->createAccessDeniedException('you do not have access to this export for those centers');
@ -411,7 +418,7 @@ class ExportController extends AbstractController
'centers_step_raw', 'centers_step_raw',
$request->request->all() $request->request->all()
); );
$this->session->set('centers_step', $data); $this->session->set('centers_step', $data['centers']);
return $this->redirectToRoute('chill_main_export_new', [ return $this->redirectToRoute('chill_main_export_new', [
'step' => $this->getNextStep('centers', $export), 'step' => $this->getNextStep('centers', $export),

View File

@ -102,4 +102,22 @@ class Regroupment
return $this; return $this;
} }
/**
* Return true if the given center is contained into this regroupment.
*/
public function containsCenter(Center $center): bool
{
return $this->centers->contains($center);
}
/**
* Return true if at least one of the given centers is contained into this regroupment.
*
* @param list<Center> $centers
*/
public function containsAtLeastOneCenter(array $centers): bool
{
return array_reduce($centers, fn (bool $carry, Center $center) => $carry || $this->containsCenter($center), false);
}
} }

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Export\Exception;
class ExportGenerationException extends \RuntimeException {}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Export\Exception;
class UnauthorizedGenerationException extends ExportGenerationException
{
public function __construct(string $message, ?\Throwable $previous = null)
{
parent::__construct($message, previous: $previous);
}
}

View File

@ -12,19 +12,22 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export; namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Form\Type\Export\AggregatorType; use Chill\MainBundle\Form\Type\Export\AggregatorType;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\FilterType; use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Repository\RegroupmentRepository;
/** /**
* @phpstan-type NormalizedData array{centers: list<int>, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, version: int}} * @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, version: int}}
*/ */
class ExportConfigNormalizer class ExportConfigNormalizer
{ {
public function __construct( public function __construct(
private readonly ExportManager $exportManager, private readonly ExportManager $exportManager,
private readonly CenterRepositoryInterface $centerRepository, private readonly CenterRepositoryInterface $centerRepository,
private readonly RegroupmentRepository $regroupmentRepository,
) {} ) {}
/** /**
@ -42,10 +45,10 @@ class ExportConfigNormalizer
], ],
]; ];
$serialized['centers'] = array_values( $serialized['centers'] = [
array_map(static fn (Center $center) => $center->getId(), $formData['centers']) 'centers' => array_values(array_map(static fn (Center $center) => $center->getId(), $formData['centers']['centers'] ?? [])),
); 'regroupments' => array_values(array_map(static fn (Regroupment $group) => $group->getId(), $formData['centers']['regroupments'] ?? [])),
];
$filtersSerialized = []; $filtersSerialized = [];
foreach ($formData[ExportType::FILTER_KEY] as $alias => $filterData) { foreach ($formData[ExportType::FILTER_KEY] as $alias => $filterData) {
@ -116,7 +119,10 @@ class ExportConfigNormalizer
'aggregators' => $aggregatorsConfig, 'aggregators' => $aggregatorsConfig,
'pick_formatter' => $serializedData['pick_formatter'], 'pick_formatter' => $serializedData['pick_formatter'],
'formatter' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']), 'formatter' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
'centers' => array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']), fn ($item) => null !== $item), 'centers' => [
'centers' => array_values(array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']['centers']), fn ($item) => null !== $item)),
'regroupments' => array_values(array_filter(array_map(fn (int $id) => $this->regroupmentRepository->find($id), $serializedData['centers']['regroupments']), fn ($item) => null !== $item)),
],
]; ];
} }
} }

View File

@ -11,11 +11,14 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export; namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\SavedExport;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\FilterType; use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use Doctrine\Common\Collections\Collection;
final readonly class ExportFormHelper final readonly class ExportFormHelper
{ {
@ -23,12 +26,13 @@ final readonly class ExportFormHelper
private AuthorizationHelperForCurrentUserInterface $authorizationHelper, private AuthorizationHelperForCurrentUserInterface $authorizationHelper,
private ExportManager $exportManager, private ExportManager $exportManager,
private ExportConfigNormalizer $configNormalizer, private ExportConfigNormalizer $configNormalizer,
private CenterRegroupementResolver $centerRegroupementResolver,
) {} ) {}
public function getDefaultData(string $step, DirectExportInterface|ExportInterface $export, array $options = []): array public function getDefaultData(string $step, DirectExportInterface|ExportInterface $export, array $options = []): array
{ {
return match ($step) { return match ($step) {
'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole())], 'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole()), 'regroupments' => []],
'export', 'generate_export' => ['export' => $this->getDefaultDataStepExport($export, $options)], 'export', 'generate_export' => ['export' => $this->getDefaultDataStepExport($export, $options)],
'formatter', 'generate_formatter' => ['formatter' => $this->getDefaultDataStepFormatter($options)], 'formatter', 'generate_formatter' => ['formatter' => $this->getDefaultDataStepFormatter($options)],
default => throw new \LogicException('step not allowed : '.$step), default => throw new \LogicException('step not allowed : '.$step),
@ -132,4 +136,24 @@ final readonly class ExportFormHelper
'formatter' => $data['formatter'], 'formatter' => $data['formatter'],
]; ];
} }
/**
* Get the Center picked by the user for this export. The data are
* extracted from the PickCenterType data.
*
* @param array $data the data as given by the @see{Chill\MainBundle\Form\Type\Export\PickCenterType}
*
* @return list<Center>
*/
public function getPickedCenters(array $data): array
{
if (!array_key_exists('centers', $data) || !array_key_exists('regroupments', $data)) {
throw new \RuntimeException('array has not the expected shape');
}
$centers = $data['centers'] instanceof Collection ? $data['centers']->toArray() : $data['centers'];
$regroupments = $data['regroupments'] instanceof Collection ? $data['regroupments']->toArray() : $data['regroupments'];
return $this->centerRegroupementResolver->resolveCenters(dump($regroupments), dump($centers));
}
} }

View File

@ -13,7 +13,11 @@ namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\Exception\UnauthorizedGenerationException;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -27,14 +31,17 @@ final readonly class ExportGenerator
private ExportManager $exportManager, private ExportManager $exportManager,
private ExportConfigNormalizer $configNormalizer, private ExportConfigNormalizer $configNormalizer,
private LoggerInterface $logger, private LoggerInterface $logger,
private AuthorizationHelperInterface $authorizationHelper,
private CenterRegroupementResolver $centerRegroupementResolver,
) {} ) {}
public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration
{ {
$data = $this->configNormalizer->denormalizeConfig($exportAlias, $configuration); $data = $this->configNormalizer->denormalizeConfig($exportAlias, $configuration);
$centers = $data['centers'];
$export = $this->exportManager->getExport($exportAlias); $export = $this->exportManager->getExport($exportAlias);
$centers = $this->filterCenters($byUser, $data['centers']['centers'], $data['centers']['regroupments'], $export);
$context = new ExportGenerationContext($byUser); $context = new ExportGenerationContext($byUser);
if ($export instanceof DirectExportInterface) { if ($export instanceof DirectExportInterface) {
@ -94,6 +101,7 @@ final readonly class ExportGenerator
} }
} }
/** @phpstan-ignore-next-line the method "generate" is not yet implemented on all formatters */
if (method_exists($formatter, 'generate')) { if (method_exists($formatter, 'generate')) {
return $formatter->generate( return $formatter->generate(
$result, $result,
@ -119,6 +127,29 @@ final readonly class ExportGenerator
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type')); return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type'));
} }
private function filterCenters(User $byUser, array $centers, array $regroupements, ExportInterface|DirectExportInterface $export): array
{
$authorizedCenters = new ArrayCollection($this->authorizationHelper->getReachableCenters($byUser, $export->requiredRole()));
if ($authorizedCenters->isEmpty()) {
throw new UnauthorizedGenerationException('No authorized centers');
}
$wantedCenters = $this->centerRegroupementResolver->resolveCenters($regroupements, $centers);
$resolvedCenters = [];
foreach ($wantedCenters as $wantedCenter) {
if ($authorizedCenters->contains($wantedCenter)) {
$resolvedCenters[] = $wantedCenter;
}
}
if ([] == $resolvedCenters) {
throw new UnauthorizedGenerationException('No common centers between wanted centers and authorized centers');
}
return $resolvedCenters;
}
/** /**
* parse the data to retrieve the used filters and aggregators. * parse the data to retrieve the used filters and aggregators.
* *

View File

@ -348,19 +348,6 @@ class ExportManager
} }
} }
/**
* Get the Center picked by the user for this export. The data are
* extracted from the PickCenterType data.
*
* @param array $data the data from a PickCenterType
*
* @return \Chill\MainBundle\Entity\Center[] the picked center
*/
public function getPickedCenters(array $data): array
{
return $data;
}
/** /**
* get the aggregators types used in the form export data. * get the aggregators types used in the form export data.
* *

View File

@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Form\DataMapper;
use Chill\MainBundle\Entity\Regroupment;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\FormInterface;
final readonly class ExportPickCenterDataMapper implements DataMapperInterface
{
public function mapDataToForms($viewData, \Traversable $forms): void
{
if (null === $viewData) {
return;
}
/** @var array<string, FormInterface> $form */
$form = iterator_to_array($forms);
$form['center']->setData($viewData);
// NOTE: we do not map back the regroupments
}
public function mapFormsToData(\Traversable $forms, &$viewData): void
{
/** @var array<string, FormInterface> $forms */
$forms = iterator_to_array($forms);
$centers = [];
foreach ($forms['center']->getData() as $center) {
$centers[spl_object_hash($center)] = $center;
}
if (\array_key_exists('regroupment', $forms)) {
/** @var Regroupment $regroupment */
foreach ($forms['regroupment']->getData() as $regroupment) {
foreach ($regroupment->getCenters() as $center) {
$centers[spl_object_hash($center)] = $center;
}
}
}
$viewData = array_values($centers);
}
}

View File

@ -14,9 +14,9 @@ namespace Chill\MainBundle\Form\Type\Export;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment; use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Form\DataMapper\ExportPickCenterDataMapper;
use Chill\MainBundle\Repository\RegroupmentRepository; use Chill\MainBundle\Repository\RegroupmentRepository;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Service\Regroupement\RegroupementFiltering;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -27,27 +27,26 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/ */
final class PickCenterType extends AbstractType final class PickCenterType extends AbstractType
{ {
public const CENTERS_IDENTIFIERS = 'c';
public function __construct( public function __construct(
private readonly ExportManager $exportManager, private readonly ExportManager $exportManager,
private readonly RegroupmentRepository $regroupmentRepository, private readonly RegroupmentRepository $regroupmentRepository,
private readonly AuthorizationHelperForCurrentUserInterface $authorizationHelper, private readonly AuthorizationHelperForCurrentUserInterface $authorizationHelper,
private readonly RegroupementFiltering $regroupementFiltering,
) {} ) {}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$export = $this->exportManager->getExport($options['export_alias']); $export = $this->exportManager->getExport($options['export_alias']);
$centers = $this->authorizationHelper->getReachableCenters( $centers = $this->authorizationHelper->getReachableCenters(
$export->requiredRole() $export->requiredRole(),
); );
$centersActive = array_filter($centers, fn (Center $c) => $c->getIsActive()); $centersActive = array_filter($centers, fn (Center $c) => $c->getIsActive());
// order alphabetically // order alphabetically
usort($centersActive, fn (Center $a, Center $b) => $a->getCenter() <=> $b->getName()); usort($centersActive, fn (Center $a, Center $b) => $a->getName() <=> $b->getName());
$builder->add('center', EntityType::class, [ $builder->add('centers', EntityType::class, [
'class' => Center::class, 'class' => Center::class,
'choices' => $centersActive, 'choices' => $centersActive,
'label' => 'center', 'label' => 'center',
@ -56,18 +55,22 @@ final class PickCenterType extends AbstractType
'choice_label' => static fn (Center $c) => $c->getName(), 'choice_label' => static fn (Center $c) => $c->getName(),
]); ]);
if (\count($this->regroupmentRepository->findAllActive()) > 0) { $groups = $this->regroupementFiltering
$builder->add('regroupment', EntityType::class, [ ->filterContainsAtLeastOneCenter($this->regroupmentRepository->findAllActive(), $centersActive);
// order alphabetically
usort($groups, fn (Regroupment $a, Regroupment $b) => $a->getName() <=> $b->getName());
if (\count($groups) > 0) {
$builder->add('regroupments', EntityType::class, [
'class' => Regroupment::class, 'class' => Regroupment::class,
'label' => 'regroupment', 'label' => 'regroupment',
'multiple' => true, 'multiple' => true,
'expanded' => true, 'expanded' => true,
'choices' => $this->regroupmentRepository->findAllActive(), 'choices' => $groups,
'choice_label' => static fn (Regroupment $r) => $r->getName(), 'choice_label' => static fn (Regroupment $r) => $r->getName(),
]); ]);
} }
$builder->setDataMapper(new ExportPickCenterDataMapper());
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View File

@ -40,15 +40,15 @@
<h3 class="m-3">{{ 'Center'|trans }}</h3> <h3 class="m-3">{{ 'Center'|trans }}</h3>
{{ form_widget(form.centers.center) }} {{ form_widget(form.centers.centers) }}
<div class="mb-3 mt-3"> <div class="mb-3 mt-3">
<input id="toggle-check-all" class="btn btn-misc" type= "button" onclick='uncheckAll(this)' value="{{ 'uncheck all centers'|trans|e('html_attr') }}"/> <input id="toggle-check-all" class="btn btn-misc" type= "button" onclick='uncheckAll(this)' value="{{ 'uncheck all centers'|trans|e('html_attr') }}"/>
</div> </div>
{% if form.centers.regroupment is defined %} {% if form.centers.regroupments is defined %}
<h3 class="m-3">{{ 'Pick aggregated centers'|trans }}</h3> <h3 class="m-3">{{ 'Pick aggregated centers'|trans }}</h3>
{{ form_widget(form.centers.regroupment) }} {{ form_widget(form.centers.regroupments) }}
{% endif %} {% endif %}
</section> </section>

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Service\Regroupement;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment;
class CenterRegroupementResolver
{
/**
* Resolves and returns a unique list of centers by merging those from the provided
* groups and the additional centers while eliminating duplicates.
*
* @param list<Regroupment> $groups
* @param list<Center> $centers
*
* @return list<Center>
*/
public function resolveCenters(array $groups, array $centers = []): array
{
$centersByHash = [];
foreach ($groups as $group) {
foreach ($group->getCenters() as $center) {
$centersByHash[spl_object_hash($center)] = $center;
}
}
foreach ($centers as $center) {
$centersByHash[spl_object_hash($center)] = $center;
}
return array_values($centersByHash);
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Service\Regroupement;
use Chill\MainBundle\Entity\Regroupment;
/**
* Class RegroupementFiltering.
*
* Provides methods to filter and manage groups based on specific criteria.
*/
class RegroupementFiltering
{
/**
* Filters the provided groups and returns only those that contain at least one of the specified centers.
*
* @param array $groups an array of groups to filter
* @param array $centers an array of centers to check against the groups
*
* @return array an array of filtered groups containing at least one of the specified centers
*/
public function filterContainsAtLeastOneCenter(array $groups, array $centers): array
{
return array_values(
array_filter($groups, static fn (Regroupment $group) => $group->containsAtLeastOneCenter($centers)),
);
}
}

View File

@ -66,7 +66,7 @@ class ExportConfigNormalizerTest extends TestCase
$center->getId()->willReturn(10); $center->getId()->willReturn(10);
$formData = [ $formData = [
'centers' => [$center->reveal()], 'centers' => ['centers' => [$center->reveal()]],
'export' => ['test' => '0'], 'export' => ['test' => '0'],
'filters' => [ 'filters' => [
'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']],
@ -82,6 +82,7 @@ class ExportConfigNormalizerTest extends TestCase
$expected = [ $expected = [
'export' => ['form' => ['test' => '0'], 'version' => 1], 'export' => ['form' => ['test' => '0'], 'version' => 1],
'centers' => ['centers' => [10], 'regroupments' => []],
'filters' => [ 'filters' => [
'filtersEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], 'filtersEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1],
'filterDisabled' => ['enabled' => false], 'filterDisabled' => ['enabled' => false],
@ -95,7 +96,6 @@ class ExportConfigNormalizerTest extends TestCase
'form' => ['test' => '0'], 'form' => ['test' => '0'],
'version' => 1, 'version' => 1,
], ],
'centers' => [10],
]; ];
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal()); $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal());
@ -137,7 +137,7 @@ class ExportConfigNormalizerTest extends TestCase
$centerRepository->find(10)->willReturn($center = new Center()); $centerRepository->find(10)->willReturn($center = new Center());
$serialized = [ $serialized = [
'centers' => [10], 'centers' => ['regroupments' => [], 'centers' => [10]],
'export' => ['form' => ['test' => '0'], 'version' => 1], 'export' => ['form' => ['test' => '0'], 'version' => 1],
'filters' => [ 'filters' => [
'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1],
@ -166,7 +166,7 @@ class ExportConfigNormalizerTest extends TestCase
], ],
'pick_formatter' => 'xlsx', 'pick_formatter' => 'xlsx',
'formatter' => ['test' => '0'], 'formatter' => ['test' => '0'],
'centers' => [$center], 'centers' => ['centers' => [$center], 'regroupments' => []],
]; ];
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal()); $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal());
@ -209,7 +209,7 @@ class ExportConfigNormalizerTest extends TestCase
$center->getId()->willReturn(10); $center->getId()->willReturn(10);
$formData = [ $formData = [
'centers' => [$center->reveal()], 'centers' => ['centers' => [$center->reveal()]],
'export' => [], 'export' => [],
'filters' => [ 'filters' => [
'filterEnabled' => ['enabled' => true, 'form' => []], 'filterEnabled' => ['enabled' => true, 'form' => []],
@ -225,6 +225,7 @@ class ExportConfigNormalizerTest extends TestCase
$expected = [ $expected = [
'export' => ['form' => [], 'version' => 1], 'export' => ['form' => [], 'version' => 1],
'centers' => ['centers' => [10], 'regroupments' => []],
'filters' => [ 'filters' => [
'filtersEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], 'filtersEnabled' => ['enabled' => true, 'form' => [], 'version' => 1],
'filterDisabled' => ['enabled' => false], 'filterDisabled' => ['enabled' => false],
@ -238,7 +239,6 @@ class ExportConfigNormalizerTest extends TestCase
'form' => [], 'form' => [],
'version' => 1, 'version' => 1,
], ],
'centers' => [10],
]; ];
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal()); $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal());
@ -280,7 +280,7 @@ class ExportConfigNormalizerTest extends TestCase
$centerRepository->find(10)->willReturn($center = new Center()); $centerRepository->find(10)->willReturn($center = new Center());
$serialized = [ $serialized = [
'centers' => [10], 'centers' => ['centers' => [10], 'regroupments' => []],
'export' => ['form' => [], 'version' => 1], 'export' => ['form' => [], 'version' => 1],
'filters' => [ 'filters' => [
'filterEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], 'filterEnabled' => ['enabled' => true, 'form' => [], 'version' => 1],
@ -309,7 +309,7 @@ class ExportConfigNormalizerTest extends TestCase
], ],
'pick_formatter' => 'xlsx', 'pick_formatter' => 'xlsx',
'formatter' => [], 'formatter' => [],
'centers' => [$center], 'centers' => ['centers' => [$center], 'regroupments' => []],
]; ];
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal()); $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal());

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Tests\Export; namespace Chill\MainBundle\Tests\Export;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\DirectExportInterface; use Chill\MainBundle\Export\DirectExportInterface;
@ -23,6 +24,8 @@ use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormattedExportGeneration;
use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use Doctrine\ORM\NativeQuery; use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -54,7 +57,7 @@ class ExportGeneratorTest extends TestCase
], ],
'pick_formatter' => 'xlsx', 'pick_formatter' => 'xlsx',
'formatter' => $formatterData = ['key' => 'form4'], 'formatter' => $formatterData = ['key' => 'form4'],
'centers' => [$centerA = new Center(), $centerB = new Center()], 'centers' => ['centers' => [$centerA = new Center()], 'regroupments' => [(new Regroupment())->addCenter($centerB = new Center())]],
]; ];
$user = new User(); $user = new User();
@ -70,11 +73,30 @@ class ExportGeneratorTest extends TestCase
// required methods // required methods
$export->initiateQuery( $export->initiateQuery(
['tagada', 'tsointsoin'], ['tagada', 'tsointsoin'],
[['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]], Argument::that(function ($arg) use ($centerB, $centerA) {
if (!is_array($arg)) {
return false;
}
if (2 !== count($arg)) {
return false;
}
foreach ($arg as $item) {
if ([] !== $item['circles']) {
return false;
}
if (!in_array($item['center'], [$centerA, $centerB], true)) {
return false;
}
}
return true;
}),
['key' => 'form1'], ['key' => 'form1'],
)->shouldBeCalled()->willReturn($query->reveal()); )->shouldBeCalled()->willReturn($query->reveal());
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled()->willReturn([['result0' => '0']]); ->shouldBeCalled()->willReturn([['result0' => '0']]);
$export->requiredRole()->willReturn('dummy_role');
$filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled(); ->shouldBeCalled();
@ -105,7 +127,10 @@ class ExportGeneratorTest extends TestCase
$exportManager->hasAggregator('disabled_aggregator')->willReturn(true); $exportManager->hasAggregator('disabled_aggregator')->willReturn(true);
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger()); $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger(), $authorizationHelper->reveal(), new CenterRegroupementResolver());
$actual = $generator->generate('dummy', $initialData, $user); $actual = $generator->generate('dummy', $initialData, $user);
@ -123,7 +148,7 @@ class ExportGeneratorTest extends TestCase
'aggregators' => [], 'aggregators' => [],
'pick_formatter' => 'xlsx', 'pick_formatter' => 'xlsx',
'formatter' => $formatterData = ['key' => 'form4'], 'formatter' => $formatterData = ['key' => 'form4'],
'centers' => [$centerA = new Center(), $centerB = new Center()], 'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []],
]; ];
$user = new User(); $user = new User();
@ -141,6 +166,7 @@ class ExportGeneratorTest extends TestCase
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled()->willReturn([['result0' => '0']]); ->shouldBeCalled()->willReturn([['result0' => '0']]);
$export->supportsModifiers()->willReturn([]); $export->supportsModifiers()->willReturn([]);
$export->requiredRole()->willReturn('dummy_role');
$formatter->generate( $formatter->generate(
[['result0' => '0']], [['result0' => '0']],
@ -160,7 +186,10 @@ class ExportGeneratorTest extends TestCase
$exportManager->getExport('dummy')->willReturn($export->reveal()); $exportManager->getExport('dummy')->willReturn($export->reveal());
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger()); $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger(), $authorizationHelper->reveal(), new CenterRegroupementResolver());
$actual = $generator->generate('dummy', $initialData, $user); $actual = $generator->generate('dummy', $initialData, $user);

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Services\Regroupement;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use PHPUnit\Framework\TestCase;
/**
* @internal
*
* @coversNothing
*/
class CenterRegroupementResolverTest extends TestCase
{
private static CenterRegroupementResolver $resolver;
public static function setUpBeforeClass(): void
{
self::$resolver = new CenterRegroupementResolver();
}
/**
* @dataProvider provideData
*/
public function testResolveCenter(array $groups, array $centers, array $expected): void
{
$actual = self::$resolver->resolveCenters($groups, $centers);
self::assertEquals(count($expected), count($actual));
foreach ($expected as $center) {
self::assertContains($center, $actual);
}
}
public static function provideData(): iterable
{
$centerA = new Center();
$centerB = new Center();
$centerC = new Center();
$centerD = new Center();
$groupA = new Regroupment();
$groupA->addCenter($centerA)->addCenter($centerB);
$groupB = new Regroupment();
$groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC);
yield [
[$groupA],
[],
[$centerA, $centerB],
];
yield [
[$groupA, $groupB],
[],
[$centerA, $centerB, $centerC],
];
yield [
[$groupA, $groupB],
[$centerB, $centerD],
[$centerA, $centerB, $centerC, $centerD],
];
}
}

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Services\Regroupement;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Service\Regroupement\RegroupementFiltering;
use PHPUnit\Framework\TestCase;
/**
* @internal
*
* @coversNothing
*/
class RegroupementFilteringTest extends TestCase
{
private static RegroupementFiltering $regroupementFiltering;
public static function setUpBeforeClass(): void
{
self::$regroupementFiltering = new RegroupementFiltering();
}
/**
* @dataProvider provideDataForFilterContainsAtLeastOnCenter
*/
public function testFilterContainsAtLeastOnCenter(array $groups, array $centers, array $expected): void
{
$actual = self::$regroupementFiltering->filterContainsAtLeastOneCenter($groups, $centers);
self::assertEquals(count($expected), count($actual));
self::assertTrue(array_is_list($actual));
foreach ($expected as $center) {
self::assertContains($center, $actual);
}
}
public static function provideDataForFilterContainsAtLeastOnCenter(): iterable
{
$centerA = new Center();
$centerB = new Center();
$centerC = new Center();
$centerD = new Center();
$groupA = new Regroupment();
$groupA->addCenter($centerA)->addCenter($centerB);
$groupB = new Regroupment();
$groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC);
$groupC = new Regroupment();
$groupC->addCenter($centerA)->addCenter($centerD);
yield [
[$groupA, $groupB],
[],
[],
];
yield [
[$groupA, $groupB],
[$centerA, $centerB, $centerC],
[$groupA, $groupB],
];
yield [
[$groupA, $groupC],
[$centerD],
[$groupC],
];
yield [
[$groupA],
[$centerB, $centerD],
[$groupA],
];
yield [
[$groupA],
[new Center()],
[],
];
}
}