Merge branch '172-missing-exports' into 'master'

[Export]: add missing grouping and filters and fix some issues

Closes #177 and #172

See merge request Chill-Projet/chill-bundles!598
This commit is contained in:
Julien Fastré 2023-10-24 13:17:03 +00:00
commit f8ee2903b2
41 changed files with 1615 additions and 166 deletions

View File

@ -0,0 +1,6 @@
kind: Feature
body: '[export] Add a filter "grouping accompanying period by opening date" and "trouping
accompanying period by closing date"'
time: 2023-10-18T11:38:25.80362952+02:00
custom:
Issue: "172"

View File

@ -0,0 +1,6 @@
kind: Feature
body: '[export] add a filter and aggregator on accompanying period work: group/filter
by handling third party'
time: 2023-10-18T13:32:03.565201495+02:00
custom:
Issue: "172"

View File

@ -0,0 +1,6 @@
kind: Feature
body: '[export] add a filter and aggregator on activites: group/filter activities
by people participating to the exchange'
time: 2023-10-18T16:02:35.99265091+02:00
custom:
Issue: "172"

View File

@ -0,0 +1,6 @@
kind: Feature
body: '[export] add a grouping on accompanying period export: group by activity type
associated to at least one activity within the accompanying period'
time: 2023-10-18T16:49:27.567166953+02:00
custom:
Issue: "172"

View File

@ -0,0 +1,5 @@
kind: Feature
body: '[export] sort filters and aggregators by title'
time: 2023-10-19T14:03:57.435338127+02:00
custom:
Issue: ""

View File

@ -0,0 +1,6 @@
kind: Fixed
body: '[export] fix date range selection on filter and grouping "by status of the
course at date", on accompanying periods'
time: 2023-10-19T11:43:50.455829748+02:00
custom:
Issue: "177"

View File

@ -0,0 +1,122 @@
<?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\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ByActivityTypeAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_by_activity_type_agg';
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
private ActivityTypeRepositoryInterface $activityTypeRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
) {}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('after_date', PickRollingDateType::class, [
'required' => false,
'label' => 'export.aggregator.acp.by_activity_type.after_date',
])
->add('before_date', PickRollingDateType::class, [
'required' => false,
'label' => 'export.aggregator.acp.by_activity_type.before_date',
]);
}
public function getFormDefaultData(): array
{
return [
'before_date' => null,
'after_date' => null,
];
}
public function getLabels($key, array $values, mixed $data)
{
return function (null|int|string $value): string {
if ('_header' === $value) {
return 'export.aggregator.acp.by_activity_type.activity_type';
}
if ('' === $value || null === $value || null === $activityType = $this->activityTypeRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($activityType->getName());
};
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_actype_id'];
}
public function getTitle()
{
return 'export.aggregator.acp.by_activity_type.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
// we make a left join, with acp having at least one activity of the given type
$exists = 'EXISTS (SELECT 1 FROM '.Activity::class." {$p}_activity WHERE {$p}_activity.accompanyingPeriod = acp AND {$p}_activity.activityType = {$p}_activity_type";
if (null !== $data['after_date']) {
$exists .= " AND {$p}_activity.date > :{$p}_after_date";
$qb->setParameter("{$p}_after_date", $this->rollingDateConverter->convert($data['after_date']));
}
if (null !== $data['before_date']) {
$exists .= " AND {$p}_activity.date < :{$p}_before_date";
$qb->setParameter("{$p}_before_date", $this->rollingDateConverter->convert($data['before_date']));
}
$exists .= ')';
$qb->leftJoin(
ActivityType::class,
"{$p}_activity_type",
Join::WITH,
$exists
);
$qb
->addSelect("{$p}_activity_type.id AS {$p}_actype_id")
->addGroupBy("{$p}_actype_id");
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@ -73,7 +73,6 @@ class DateAggregator implements AggregatorInterface
'choices' => self::CHOICES, 'choices' => self::CHOICES,
'multiple' => false, 'multiple' => false,
'expanded' => true, 'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
]); ]);
} }

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\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Tests\Export\Aggregator\PersonsAggregatorTest;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see PersonsAggregatorTest
*/
final readonly class PersonsAggregator implements AggregatorInterface
{
private const PREFIX = 'act_persons_agg';
public function __construct(private LabelPersonHelper $labelPersonHelper) {}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
if ($key !== self::PREFIX.'_pid') {
throw new \UnexpectedValueException('this key should not be handled: '.$key);
}
return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons');
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_pid'];
}
public function getTitle()
{
return 'export.aggregator.activity.by_persons.Group activity by persons';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.persons', "{$p}_p")
->addSelect("{$p}_p.id AS {$p}_pid")
->addGroupBy("{$p}_pid");
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

@ -0,0 +1,87 @@
<?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\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Tests\Export\Filter\PersonsFilterTest;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see PersonsFilterTest
*/
final readonly class PersonsFilter implements FilterInterface
{
private const PREFIX = 'act_persons_filter';
public function __construct(private PersonRenderInterface $personRender) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$orX = $qb->expr()->orX();
foreach (array_values($data['accepted_persons']) as $key => $person) {
$orX->add($qb->expr()->isMemberOf(":{$p}_p_{$key}", 'activity.persons'));
$qb->setParameter(":{$p}_p_{$key}", $person);
}
$qb->andWhere($orX);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_persons', PickPersonDynamicType::class, [
'multiple' => true,
'label' => 'export.filter.activity.by_persons.persons taking part on the activity',
]);
}
public function getFormDefaultData(): array
{
return [
'accepted_persons' => [],
];
}
public function describeAction($data, $format = 'string')
{
$users = [];
foreach ($data['accepted_persons'] as $u) {
$users[] = $this->personRender->renderString($u, []);
}
return ['export.filter.activity.by_persons.Filtered activity by persons: only %persons%', [
'%persons%' => implode(', ', $users),
]];
}
public function getTitle(): string
{
return 'export.filter.activity.by_persons.Filter activity by persons';
}
}

View File

@ -0,0 +1,87 @@
<?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\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByActivityTypeAggregator;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ByActivityTypeAggregatorTest extends AbstractAggregatorTest
{
private RollingDateConverterInterface $rollingDateConverter;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->rollingDateConverter = self::$container->get(RollingDateConverterInterface::class);
$this->activityTypeRepository = self::$container->get(ActivityTypeRepositoryInterface::class);
$this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class);
}
public function getAggregator()
{
return new ByActivityTypeAggregator(
$this->rollingDateConverter,
$this->activityTypeRepository,
$this->translatableStringHelper,
);
}
public function getFormData()
{
return [
[
'after_date' => null,
'before_date' => null,
],
[
'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'before_date' => null,
],
[
'after_date' => null,
'before_date' => new RollingDate(RollingDate::T_TODAY),
],
[
'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'before_date' => new RollingDate(RollingDate::T_TODAY),
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(distinct acp.id)')
->from(AccompanyingPeriod::class, 'acp'),
];
}
}

View File

@ -0,0 +1,60 @@
<?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\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\PersonsAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class PersonsAggregatorTest extends AbstractAggregatorTest
{
private LabelPersonHelper $labelPersonHelper;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->labelPersonHelper = self::$container->get(LabelPersonHelper::class);
}
public function getAggregator()
{
return new PersonsAggregator($this->labelPersonHelper);
}
public function getFormData()
{
return [
[],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity'),
];
}
}

View File

@ -0,0 +1,72 @@
<?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\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\PersonsFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class PersonsFilterTest extends AbstractFilterTest
{
private PersonRenderInterface $personRender;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->personRender = self::$container->get(PersonRenderInterface::class);
}
public function getFilter()
{
return new PersonsFilter($this->personRender);
}
public function getFormData()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$persons = $em->createQuery('SELECT p FROM '.Person::class.' p ')
->setMaxResults(2)
->getResult();
self::ensureKernelShutdown();
return [
[
'accepted_persons' => $persons,
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
yield $em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity');
self::ensureKernelShutdown();
}
}

View File

@ -200,3 +200,15 @@ services:
Chill\ActivityBundle\Export\Aggregator\SentReceivedAggregator: Chill\ActivityBundle\Export\Aggregator\SentReceivedAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: activity_sentreceived_aggregator } - { name: chill.export_aggregator, alias: activity_sentreceived_aggregator }
Chill\ActivityBundle\Export\Filter\PersonsFilter:
tags:
- { name: chill.export_filter, alias: activity_by_persons_filter }
Chill\ActivityBundle\Export\Aggregator\PersonsAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_by_persons_aggregator }
Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByActivityTypeAggregator:
tags:
- { name: chill.export_aggregator, alias: acp_by_activity_type_aggregator }

View File

@ -362,7 +362,7 @@ export:
Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant
'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%' 'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%'
course_having_activity_between_date: course_having_activity_between_date:
Title: Filtre les parcours ayant reçu un échange entre deux dates Title: Filtrer les parcours ayant reçu un échange entre deux dates
Receiving an activity after: Ayant reçu un échange après le Receiving an activity after: Ayant reçu un échange après le
Receiving an activity before: Ayant reçu un échange avant le Receiving an activity before: Ayant reçu un échange avant le
acp_by_activity_type: acp_by_activity_type:
@ -372,13 +372,23 @@ export:
Implied in an activity before this date: Impliqué dans un échange avant cette date Implied in an activity before this date: Impliqué dans un échange avant cette date
Activity reasons for those activities: Sujets de ces échanges Activity reasons for those activities: Sujets de ces échanges
if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte
title: Filtrer les personnes ayant été associés à un échange au cours de la période title: Filtrer les usagers ayant été associés à un échange au cours de la période
date mismatch: La date de fin de la période doit être supérieure à la date du début date mismatch: La date de fin de la période doit être supérieure à la date du début
by_creator_scope: by_creator_scope:
Filter activity by user scope: Filtrer les échanges par service du créateur de l'échange Filter activity by user scope: Filtrer les échanges par service du créateur de l'échange
'Filtered activity by user scope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" 'Filtered activity by user scope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%"
by_persons:
Filter activity by persons: Filtrer les échanges par usager participant
'Filtered activity by persons: only %persons%': 'Échanges filtrés par usagers participants: seulement %persons%'
persons taking part on the activity: Usagers participants à l'échange
aggregator: aggregator:
acp:
by_activity_type:
title: Grouper les parcours par type d'échange
after_date: Uniquement échanges après cette date
before_date: Uniquement échanges avant cette date
activity_type: Types d'échange
activity: activity:
by_sent_received: by_sent_received:
Sent or received: Envoyé ou reçu Sent or received: Envoyé ou reçu
@ -400,6 +410,9 @@ export:
by_creator_job: by_creator_job:
Group activity by creator job: Grouper les échanges par service du créateur de l'échange Group activity by creator job: Grouper les échanges par service du créateur de l'échange
Calc date: Date de calcul du service du créateur de l'échange Calc date: Date de calcul du service du créateur de l'échange
by_persons:
Group activity by persons: Grouper les échanges par usager participant
Persons: Usagers participants
generic_doc: generic_doc:
filter: filter:

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export; namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -54,20 +55,17 @@ class ExportManager
*/ */
private array $formatters = []; private array $formatters = [];
private readonly string|\Stringable|\Symfony\Component\Security\Core\User\UserInterface $user;
public function __construct( public function __construct(
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
private readonly AuthorizationCheckerInterface $authorizationChecker, private readonly AuthorizationCheckerInterface $authorizationChecker,
private readonly AuthorizationHelperInterface $authorizationHelper, private readonly AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage, private readonly TokenStorageInterface $tokenStorage,
iterable $exports, iterable $exports,
iterable $aggregators, iterable $aggregators,
iterable $filters iterable $filters
// iterable $formatters, // iterable $formatters,
// iterable $exportElementProvider // iterable $exportElementProvider
) { ) {
$this->user = $tokenStorage->getToken()->getUser();
$this->exports = iterator_to_array($exports); $this->exports = iterator_to_array($exports);
$this->aggregators = iterator_to_array($aggregators); $this->aggregators = iterator_to_array($aggregators);
$this->filters = iterator_to_array($filters); $this->filters = iterator_to_array($filters);
@ -91,20 +89,24 @@ class ExportManager
* *
* @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias
*/ */
public function &getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): iterable public function getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array
{ {
if ($export instanceof DirectExportInterface) { if ($export instanceof DirectExportInterface) {
return; return [];
} }
$filters = [];
foreach ($this->filters as $alias => $filter) { foreach ($this->filters as $alias => $filter) {
if ( if (
\in_array($filter->applyOn(), $export->supportsModifiers(), true) \in_array($filter->applyOn(), $export->supportsModifiers(), true)
&& $this->isGrantedForElement($filter, $export, $centers) && $this->isGrantedForElement($filter, $export, $centers)
) { ) {
yield $alias => $filter; $filters[$alias] = $filter;
} }
} }
return $filters;
} }
/** /**
@ -112,22 +114,26 @@ class ExportManager
* *
* @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array
* *
* @return iterable<string, AggregatorInterface>|null a \Generator that contains aggretagors. The key is the filter's alias * @return array<string, AggregatorInterface> an array that contains aggregators. The key is the filter's alias
*/ */
public function &getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): ?iterable public function getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array
{ {
if ($export instanceof ListInterface || $export instanceof DirectExportInterface) { if ($export instanceof ListInterface || $export instanceof DirectExportInterface) {
return; return [];
} }
$aggregators = [];
foreach ($this->aggregators as $alias => $aggregator) { foreach ($this->aggregators as $alias => $aggregator) {
if ( if (
\in_array($aggregator->applyOn(), $export->supportsModifiers(), true) \in_array($aggregator->applyOn(), $export->supportsModifiers(), true)
&& $this->isGrantedForElement($aggregator, $export, $centers) && $this->isGrantedForElement($aggregator, $export, $centers)
) { ) {
yield $alias => $aggregator; $aggregators[$alias] = $aggregator;
} }
} }
return $aggregators;
} }
public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void
@ -347,6 +353,17 @@ class ExportManager
return $this->filters[$alias]; return $this->filters[$alias];
} }
public function getAllFilters(): array
{
$filters = [];
foreach ($this->filters as $alias => $filter) {
$filters[$alias] = $filter;
}
return $filters;
}
/** /**
* get all filters. * get all filters.
* *
@ -452,7 +469,7 @@ class ExportManager
if (null === $centers || [] === $centers) { if (null === $centers || [] === $centers) {
// we want to try if at least one center is reachable // we want to try if at least one center is reachable
return [] !== $this->authorizationHelper->getReachableCenters( return [] !== $this->authorizationHelper->getReachableCenters(
$this->user, $this->tokenStorage->getToken()->getUser(),
$role $role
); );
} }
@ -484,11 +501,17 @@ class ExportManager
{ {
$r = []; $r = [];
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof User) {
return [];
}
foreach ($centers as $center) { foreach ($centers as $center) {
$r[] = [ $r[] = [
'center' => $center, 'center' => $center,
'circles' => $this->authorizationHelper->getReachableScopes( 'circles' => $this->authorizationHelper->getReachableScopes(
$this->user, $user,
$element->requiredRole(), $element->requiredRole(),
$center $center
), ),

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\Export;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class SortExportElement
{
public function __construct(
private TranslatorInterface $translator,
) {}
/**
* @param array<int|string, FilterInterface> $elements
*/
public function sortFilters(array &$elements): void
{
uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle()));
}
/**
* @param array<int|string, AggregatorInterface> $elements
*/
public function sortAggregators(array &$elements): void
{
uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle()));
}
}

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Export; namespace Chill\MainBundle\Form\Type\Export;
use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\SortExportElement;
use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint; use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
@ -28,15 +29,7 @@ class ExportType extends AbstractType
final public const PICK_FORMATTER_KEY = 'pick_formatter'; final public const PICK_FORMATTER_KEY = 'pick_formatter';
/** public function __construct(private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement) {}
* @var ExportManager
*/
protected $exportManager;
public function __construct(ExportManager $exportManager)
{
$this->exportManager = $exportManager;
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@ -58,12 +51,12 @@ class ExportType extends AbstractType
if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { if ($export instanceof \Chill\MainBundle\Export\ExportInterface) {
// add filters // add filters
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']);
$this->sortExportElement->sortFilters($filters);
$filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]);
foreach ($filters as $alias => $filter) { foreach ($filters as $alias => $filter) {
$filterBuilder->add($alias, FilterType::class, [ $filterBuilder->add($alias, FilterType::class, [
'filter_alias' => $alias, 'filter' => $filter,
'export_manager' => $this->exportManager,
'label' => $filter->getTitle(), 'label' => $filter->getTitle(),
'constraints' => [ 'constraints' => [
new ExportElementConstraint(['element' => $filter]), new ExportElementConstraint(['element' => $filter]),
@ -76,6 +69,7 @@ class ExportType extends AbstractType
// add aggregators // add aggregators
$aggregators = $this->exportManager $aggregators = $this->exportManager
->getAggregatorsApplyingOn($export, $options['picked_centers']); ->getAggregatorsApplyingOn($export, $options['picked_centers']);
$this->sortExportElement->sortAggregators($aggregators);
$aggregatorBuilder = $builder->create( $aggregatorBuilder = $builder->create(
self::AGGREGATOR_KEY, self::AGGREGATOR_KEY,
FormType::class, FormType::class,

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Export; namespace Chill\MainBundle\Form\Type\Export;
use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
@ -25,8 +26,7 @@ class FilterType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$exportManager = $options['export_manager']; $filter = $options['filter'];
$filter = $exportManager->getFilter($options['filter_alias']);
$builder $builder
->add(self::ENABLED_FIELD, CheckboxType::class, [ ->add(self::ENABLED_FIELD, CheckboxType::class, [
@ -46,8 +46,9 @@ class FilterType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver->setRequired('filter_alias') $resolver
->setRequired('export_manager') ->setRequired('filter')
->setAllowedTypes('filter', [FilterInterface::class])
->setDefault('compound', true) ->setDefault('compound', true)
->setDefault('error_bubbling', false); ->setDefault('error_bubbling', false);
} }

View File

@ -34,6 +34,7 @@ class PickRollingDateType extends AbstractType
), ),
'multiple' => false, 'multiple' => false,
'expanded' => false, 'expanded' => false,
'required' => $options['required'],
'label' => 'rolling_date.roll_movement', 'label' => 'rolling_date.roll_movement',
]) ])
->add('fixedDate', ChillDateType::class, [ ->add('fixedDate', ChillDateType::class, [
@ -57,7 +58,10 @@ class PickRollingDateType extends AbstractType
'constraints' => [ 'constraints' => [
new Callback($this->validate(...)), new Callback($this->validate(...)),
], ],
'required' => true,
]); ]);
$resolver->setAllowedTypes('required', 'bool');
} }
public function validate($data, ExecutionContextInterface $context, $payload): void public function validate($data, ExecutionContextInterface $context, $payload): void

View File

@ -1,5 +1,5 @@
{# {#
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop> <info@champs-libres.coop> / <http://www.champs-libres.coop>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -36,84 +36,84 @@
{% block content %} {% block content %}
<div class="col-md-10"> <div class="col-md-10">
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }} {{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
<h1>{{ export.title|trans }}</h1> <h1>{{ export.title|trans }}</h1>
<p>{{ export.description|trans }}</p> <p>{{ export.description|trans }}</p>
{{ form_start(form) }} {{ form_start(form) }}
{% if form.children.export.children.filters is defined %} {% if form.children.export.children.filters is defined %}
{% if form.children.export.children.filters is not empty%} {% if form.children.export.children.filters is not empty%}
<section class="filter mb-4"> <section class="filter mb-4">
<h2>{{ 'Filters'| trans }}</h2> <h2>{{ 'Filters'| trans }}</h2>
{{ form_errors(form.children.export.children.filters) }} {{ form_errors(form.children.export.children.filters) }}
<div class="flex-table"> <div class="flex-table">
{% for filter_form in form.children.export.children.filters %} {% for filter_form in form.children.export.children.filters %}
<div class="item-bloc no-altern"> <div class="item-bloc no-altern">
{{ form_widget(filter_form.enabled, { {{ form_widget(filter_form.enabled, {
'label': filter_form.vars.label, 'label': filter_form.vars.label,
'label_attr': { 'class': 'h6' }, 'label_attr': { 'class': 'h6' },
'attr': { 'data-display-target': filter_form.vars.id } 'attr': { 'data-display-target': filter_form.vars.id }
}) }} }) }}
<div data-display-show-hide="{{ filter_form.vars.id }}"> <div data-display-show-hide="{{ filter_form.vars.id }}">
{{ form_widget(filter_form.form) }} {{ form_widget(filter_form.form) }}
{{ form_errors(filter_form) }} {{ form_errors(filter_form) }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
{% else %} {% else %}
{# render the children, to mark the widget as 'rendered' #} {# render the children, to mark the widget as 'rendered' #}
{{ form_widget(form.children.export.children.filters) }} {{ form_widget(form.children.export.children.filters) }}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if form.children.export.children.aggregators is defined %} {% if form.children.export.children.aggregators is defined %}
{% if form.children.export.children.aggregators is not empty %} {% if form.children.export.children.aggregators is not empty %}
<section class="aggregator mb-4"> <section class="aggregator mb-4">
<h2>{{ 'Aggregators'| trans }}</h2> <h2>{{ 'Aggregators'| trans }}</h2>
<div class="flex-table"> <div class="flex-table">
{% for aggregator_form in form.children.export.children.aggregators %} {% for aggregator_form in form.children.export.children.aggregators %}
<div class="item-bloc no-altern"> <div class="item-bloc no-altern">
{{ form_widget(aggregator_form.enabled, { {{ form_widget(aggregator_form.enabled, {
'label': aggregator_form.vars.label, 'label': aggregator_form.vars.label,
'label_attr': { 'class': 'h6' }, 'label_attr': { 'class': 'h6' },
'attr': { 'data-display-target': aggregator_form.vars.id } 'attr': { 'data-display-target': aggregator_form.vars.id }
}) }} }) }}
<div data-display-show-hide="{{ aggregator_form.vars.id }}"> <div data-display-show-hide="{{ aggregator_form.vars.id }}">
{{ form_widget(aggregator_form.form) }} {{ form_widget(aggregator_form.form) }}
{{ form_errors(aggregator_form) }} {{ form_errors(aggregator_form) }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
{% else %} {% else %}
{# render the children, to mark the widget as 'rendered' #} {# render the children, to mark the widget as 'rendered' #}
{{ form_widget(form.children.export.children.aggregators) }} {{ form_widget(form.children.export.children.aggregators) }}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if form.children.export.children.export.children|length > 0 %} {% if form.children.export.children.export.children|length > 0 %}
<style> <style>
div#export_export_fields { column-count: 2; } div#export_export_fields { column-count: 2; }
</style> </style>
@ -123,12 +123,12 @@
</h2> </h2>
{{ form_widget(form.children.export.children.export) }} {{ form_widget(form.children.export.children.export) }}
</section> </section>
{% else %} {% else %}
{# render the children, to mark the widget as 'rendered' #} {# render the children, to mark the widget as 'rendered' #}
{{ form_widget(form.children.export.children.export) }} {{ form_widget(form.children.export.children.export) }}
{% endif %} {% endif %}
{% if form.children.export.children.pick_formatter is defined %} {% if form.children.export.children.pick_formatter is defined %}
<section class="formatter mb-4"> <section class="formatter mb-4">
<h2> <h2>
@ -140,7 +140,7 @@
{{ form_row(form.children.export.children.pick_formatter.children.alias, { 'label' : 'Formatter' }) }} {{ form_row(form.children.export.children.pick_formatter.children.alias, { 'label' : 'Formatter' }) }}
</section> </section>
{% endif %} {% endif %}
<p>{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' }, 'label' : 'Go to formatter options' } ) }}</p> <p>{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' }, 'label' : 'Go to formatter options' } ) }}</p>
{{ form_end(form) }} {{ form_end(form) }}

View File

@ -0,0 +1,193 @@
<?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\Export;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\SortExportElement;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class SortExportElementTest extends KernelTestCase
{
private SortExportElement $sortExportElement;
public function setUp(): void
{
parent::setUpBeforeClass();
$this->sortExportElement = new SortExportElement($this->makeTranslator());
}
public function testSortFilterRealData(): void
{
self::bootKernel();
$sorter = self::$container->get(SortExportElement::class);
$translator = self::$container->get(TranslatorInterface::class);
$exportManager = self::$container->get(ExportManager::class);
$filters = $exportManager->getAllFilters();
$sorter->sortFilters($filters);
$previousName = null;
foreach ($filters as $filter) {
if (null === $previousName) {
$previousName = $translator->trans($filter->getTitle());
continue;
}
$current = $translator->trans($filter->getTitle());
if ($current === $previousName) {
continue;
}
self::assertEquals(-1, $previousName <=> $current, sprintf("comparing '%s' and '%s'", $previousName, $current));
$previousName = $current;
}
}
public function testSortAggregator(): void
{
$aggregators = [
'foo' => $a = $this->makeAggregator('a'),
'zop' => $q = $this->makeAggregator('q'),
'bar' => $c = $this->makeAggregator('c'),
'baz' => $b = $this->makeAggregator('b'),
];
$this->sortExportElement->sortAggregators($aggregators);
self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($aggregators));
self::assertSame($a, $aggregators['foo']);
self::assertSame($b, $aggregators['baz']);
self::assertSame($c, $aggregators['bar']);
self::assertSame($q, $aggregators['zop']);
}
public function testSortFilter(): void
{
$filters = [
'foo' => $a = $this->makeFilter('a'),
'zop' => $q = $this->makeFilter('q'),
'bar' => $c = $this->makeFilter('c'),
'baz' => $b = $this->makeFilter('b'),
];
$this->sortExportElement->sortFilters($filters);
self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($filters));
self::assertSame($a, $filters['foo']);
self::assertSame($b, $filters['baz']);
self::assertSame($c, $filters['bar']);
self::assertSame($q, $filters['zop']);
}
private function makeTranslator(): TranslatorInterface
{
return new class () implements TranslatorInterface {
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
{
return $id;
}
public function getLocale(): string
{
return 'en';
}
};
}
private function makeAggregator(string $title): AggregatorInterface
{
return new class ($title) implements AggregatorInterface {
public function __construct(private readonly string $title) {}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return fn ($v) => $v;
}
public function getQueryKeys($data)
{
return [];
}
public function getTitle()
{
return $this->title;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data) {}
public function applyOn()
{
return [];
}
};
}
private function makeFilter(string $title): FilterInterface
{
return new class ($title) implements FilterInterface {
public function __construct(private readonly string $title) {}
public function getTitle()
{
return $this->title;
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string')
{
return ['a', []];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data) {}
public function applyOn()
{
return [];
}
};
}
}

View File

@ -54,3 +54,5 @@ services:
- { name: chill.export_formatter, alias: 'csv_pivoted_list' } - { name: chill.export_formatter, alias: 'csv_pivoted_list' }
Chill\MainBundle\Export\AccompanyingCourseExportHelper: ~ Chill\MainBundle\Export\AccompanyingCourseExportHelper: ~
Chill\MainBundle\Export\SortExportElement: ~

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Enum\DateGroupingChoiceEnum;
use Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregators\ClosingDateAggregatorTest;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see ClosingDateAggregatorTest
*/
final readonly class ClosingDateAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_closing_date_agg';
public function buildForm(FormBuilderInterface $builder): void
{
$builder->add('frequency', ChoiceType::class, [
'choices' => array_combine(
array_map(fn (DateGroupingChoiceEnum $c) => 'export.enum.frequency.'.$c->value, DateGroupingChoiceEnum::cases()),
array_map(fn (DateGroupingChoiceEnum $c) => $c->value, DateGroupingChoiceEnum::cases()),
),
'label' => 'export.aggregator.course.by_closing_date.frequency',
'multiple' => false,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [
'frequency' => 'year',
];
}
public function getLabels($key, array $values, mixed $data)
{
return function (null|string $value): string {
if ('_header' === $value) {
return 'export.aggregator.course.by_closing_date.header';
}
return (string) $value;
};
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_closing_date'];
}
public function getTitle()
{
return 'export.aggregator.course.by_closing_date.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb->addSelect(sprintf("TO_CHAR(acp.closingDate, '%s') AS {$p}_closing_date", $data['frequency']));
$qb->addGroupBy("{$p}_closing_date");
$qb->addOrderBy("{$p}_closing_date", 'DESC');
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Enum\DateGroupingChoiceEnum;
use Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregators\OpeningDateAggregatorTest;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see OpeningDateAggregatorTest
*/
final readonly class OpeningDateAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_opening_date_agg';
public function buildForm(FormBuilderInterface $builder): void
{
$builder->add('frequency', ChoiceType::class, [
'choices' => array_combine(
array_map(fn (DateGroupingChoiceEnum $c) => 'export.enum.frequency.'.$c->value, DateGroupingChoiceEnum::cases()),
array_map(fn (DateGroupingChoiceEnum $c) => $c->value, DateGroupingChoiceEnum::cases()),
),
'label' => 'export.aggregator.course.by_opening_date.frequency',
'multiple' => false,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [
'frequency' => 'year',
];
}
public function getLabels($key, array $values, mixed $data)
{
return function (null|string $value): string {
if ('_header' === $value) {
return 'export.aggregator.course.by_opening_date.header';
}
return (string) $value;
};
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_opening_date'];
}
public function getTitle()
{
return 'export.aggregator.course.by_opening_date.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb->addSelect(sprintf("TO_CHAR(acp.openingDate, '%s') AS {$p}_opening_date", $data['frequency']));
$qb->addGroupBy("{$p}_opening_date");
$qb->addOrderBy("{$p}_opening_date", 'DESC');
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@ -49,7 +49,7 @@ final readonly class StepAggregator implements AggregatorInterface
$qb->expr()->lte(self::A.'.startDate', ':'.self::P), $qb->expr()->lte(self::A.'.startDate', ':'.self::P),
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->isNull(self::A.'.endDate'), $qb->expr()->isNull(self::A.'.endDate'),
$qb->expr()->lt(self::A.'.endDate', ':'.self::P) $qb->expr()->gt(self::A.'.endDate', ':'.self::P)
) )
) )
) )

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Tests\Export\Aggregator\SocialWorkAggregators\HandlingThirdPartyAggregatorTest;
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see HandlingThirdPartyAggregatorTest
*/
final readonly class HandlingThirdPartyAggregator implements AggregatorInterface
{
private const PREFIX = 'acpw_handling3party_agg';
public function __construct(private LabelThirdPartyHelper $labelThirdPartyHelper) {}
public function buildForm(FormBuilderInterface $builder)
{
// no form needed here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return $this->labelThirdPartyHelper->getLabel($key, $values, 'export.aggregator.course_work.by_handling_third_party.header');
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_h3party'];
}
public function getTitle()
{
return 'export.aggregator.course_work.by_handling_third_party.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->addSelect("IDENTITY(acpw.handlingThierParty) AS {$p}_h3party")
->addGroupBy("{$p}_h3party");
}
public function applyOn()
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Enum;
enum DateGroupingChoiceEnum: string
{
case MONTH = 'YYYY-MM';
case WEEK = 'YYYY-IW';
case YEAR = 'YYYY';
}

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\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class HandlingThirdPartyFilter implements FilterInterface
{
private const PREFIX = 'acpw_handling_3party_filter';
public function __construct(
private ThirdPartyRender $thirdPartyRender,
) {}
public function getTitle()
{
return 'export.filter.work.by_handling3party.title';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('handling_3parties', PickThirdpartyDynamicType::class, [
'label' => 'export.filter.work.by_handling3party.pick_3parties',
'multiple' => true,
]);
}
public function getFormDefaultData(): array
{
return ['handling_3parties' => []];
}
public function describeAction($data, $format = 'string')
{
return [
'export.filter.work.by_handling3party.Only 3 parties %3parties%',
[
'%3parties%' => implode(
', ',
array_map(fn (ThirdParty $thirdParty) => $this->thirdPartyRender->renderString($thirdParty, []), $data['handling_3parties'])
),
],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb->andWhere("acpw.handlingThierParty IN (:{$p}_3ps)");
$qb->setParameter("{$p}_3ps", $data['handling_3parties']);
}
public function applyOn()
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
}

View File

@ -61,7 +61,7 @@ class StepFilterOnDate implements FilterInterface
$qb->expr()->lte(self::A.'.startDate', ':'.self::P), $qb->expr()->lte(self::A.'.startDate', ':'.self::P),
$qb->expr()->orX( $qb->expr()->orX(
$qb->expr()->isNull(self::A.'.endDate'), $qb->expr()->isNull(self::A.'.endDate'),
$qb->expr()->lt(self::A.'.endDate', ':'.self::P) $qb->expr()->gt(self::A.'.endDate', ':'.self::P)
) )
) )
) )

View File

@ -66,7 +66,7 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface
{ {
$builder $builder
->add('date_calc', PickRollingDateType::class, [ ->add('date_calc', PickRollingDateType::class, [
'label' => 'Compute address at date', 'label' => 'export.filter.person.by_address_ref_status.Address at date',
'required' => true, 'required' => true,
]) ])
->add('ref_statuses', ChoiceType::class, [ ->add('ref_statuses', ChoiceType::class, [

View File

@ -18,6 +18,21 @@ class LabelPersonHelper
{ {
public function __construct(private readonly PersonRepository $personRepository, private readonly PersonRenderInterface $personRender) {} public function __construct(private readonly PersonRepository $personRepository, private readonly PersonRenderInterface $personRender) {}
public function getLabel(string $key, array $values, string $header): callable
{
return function (null|int|string $value) use ($header): string {
if ('_header' === $value) {
return $header;
}
if ('' === $value || null === $value || null === $person = $this->personRepository->find($value)) {
return '';
}
return $this->personRender->renderString($person, []);
};
}
public function getLabelMulti(string $key, array $values, string $header): callable public function getLabelMulti(string $key, array $values, string $header): callable
{ {
return function ($value) use ($header) { return function ($value) use ($header) {

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ClosingDateAggregator;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ClosingDateAggregatorTest extends AbstractAggregatorTest
{
private static ClosingDateAggregator $closingDateAggregator;
private static EntityManagerInterface $entityManager;
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
self::bootKernel();
self::$closingDateAggregator = self::$container->get(ClosingDateAggregator::class);
self::$entityManager = self::$container->get(EntityManagerInterface::class);
}
public function getAggregator()
{
return self::$closingDateAggregator;
}
public function getFormData()
{
yield ['frequency' => 'YYYY'];
yield ['frequency' => 'YYYY-MM'];
yield ['frequency' => 'YYYY-IV'];
}
public function getQueryBuilders()
{
self::bootKernel();
self::$entityManager = self::$container->get(EntityManagerInterface::class);
$data = [
self::$entityManager->createQueryBuilder()
->select('count(acp.id)')
->from(AccompanyingPeriod::class, 'acp'),
];
self::ensureKernelShutdown();
return $data;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\OpeningDateAggregator;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class OpeningDateAggregatorTest extends AbstractAggregatorTest
{
private static OpeningDateAggregator $openingDateAggregator;
private static EntityManagerInterface $entityManager;
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
self::bootKernel();
self::$openingDateAggregator = self::$container->get(OpeningDateAggregator::class);
self::$entityManager = self::$container->get(EntityManagerInterface::class);
}
public function getAggregator()
{
return self::$openingDateAggregator;
}
public function getFormData()
{
yield ['frequency' => 'YYYY'];
yield ['frequency' => 'YYYY-MM'];
yield ['frequency' => 'YYYY-IV'];
}
public function getQueryBuilders()
{
self::bootKernel();
self::$entityManager = self::$container->get(EntityManagerInterface::class);
$data = [
self::$entityManager->createQueryBuilder()
->select('count(acp.id)')
->from(AccompanyingPeriod::class, 'acp'),
];
self::ensureKernelShutdown();
return $data;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\HandlingThirdPartyAggregator;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class HandlingThirdPartyAggregatorTest extends AbstractAggregatorTest
{
private static HandlingThirdPartyAggregator $handlingThirdPartyAggregator;
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
self::bootKernel();
self::$handlingThirdPartyAggregator = self::$container->get(HandlingThirdPartyAggregator::class);
}
public function getAggregator()
{
return self::$handlingThirdPartyAggregator;
}
public function getFormData()
{
return [
[],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container
->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(acpw.id)')
->from(AccompanyingPeriodWork::class, 'acpw'),
];
}
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Filter\SocialWorkFilters;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\HandlingThirdPartyFilter;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class HandlingThirdPartyFilterTest extends AbstractFilterTest
{
private ThirdPartyRender $thirdPartyRender;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->thirdPartyRender = self::$container->get(ThirdPartyRender::class);
}
public function getFilter()
{
return new HandlingThirdPartyFilter($this->thirdPartyRender);
}
public function getFormData()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$thirdParties = $em->createQuery('SELECT tp FROM '.ThirdParty::class.' tp')
->setMaxResults(2)
->getResult();
return [
[
'handling_3parties' => $thirdParties,
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('acpw.id')
->from(AccompanyingPeriodWork::class, 'acpw'),
];
}
}

View File

@ -251,3 +251,11 @@ services:
Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ScopeWorkingOnCourseAggregator: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ScopeWorkingOnCourseAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_scope_working_on_course_aggregator } - { name: chill.export_aggregator, alias: accompanyingcourse_scope_working_on_course_aggregator }
Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\OpeningDateAggregator:
tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_opening_date_aggregator }
Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ClosingDateAggregator:
tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_closing_date_aggregator }

View File

@ -1,116 +1,93 @@
services: services:
_defaults:
autowire: true
autoconfigure: true
## Indicators ## Indicators
Chill\PersonBundle\Export\Export\CountAccompanyingPeriodWork: Chill\PersonBundle\Export\Export\CountAccompanyingPeriodWork:
autowire: true tags:
autoconfigure: true - { name: chill.export, alias: count_social_work_actions }
tags:
- { name: chill.export, alias: count_social_work_actions }
Chill\PersonBundle\Export\Export\ListAccompanyingPeriodWork: Chill\PersonBundle\Export\Export\ListAccompanyingPeriodWork:
autowire: true tags:
autoconfigure: true - { name: chill.export, alias: list_social_work_actions }
tags:
- { name: chill.export, alias: list_social_work_actions }
## FILTERS ## FILTERS
chill.person.export.filter_social_work_type: chill.person.export.filter_social_work_type:
class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\SocialWorkTypeFilter class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\SocialWorkTypeFilter
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_type_filter }
tags:
- { name: chill.export_filter, alias: social_work_type_filter }
chill.person.export.filter_scope: chill.person.export.filter_scope:
class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ScopeFilter class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ScopeFilter
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_actions_scope_filter }
tags:
- { name: chill.export_filter, alias: social_work_actions_scope_filter }
chill.person.export.filter_job: chill.person.export.filter_job:
class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\JobFilter class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\JobFilter
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_actions_job_filter }
tags:
- { name: chill.export_filter, alias: social_work_actions_job_filter }
chill.person.export.filter_treatingagent: chill.person.export.filter_treatingagent:
class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ReferrerFilter class: Chill\PersonBundle\Export\Filter\SocialWorkFilters\ReferrerFilter
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_actions_treatingagent_filter }
tags:
- { name: chill.export_filter, alias: social_work_actions_treatingagent_filter }
Chill\PersonBundle\Export\Filter\SocialWorkFilters\CurrentActionFilter: Chill\PersonBundle\Export\Filter\SocialWorkFilters\CurrentActionFilter:
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_actions_current_filter }
tags:
- { name: chill.export_filter, alias: social_work_actions_current_filter }
Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkStartDateBetweenDateFilter: Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkStartDateBetweenDateFilter:
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_actions_start_btw_dates_filter }
tags:
- { name: chill.export_filter, alias: social_work_actions_start_btw_dates_filter }
Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkEndDateBetweenDateFilter: Chill\PersonBundle\Export\Filter\SocialWorkFilters\AccompanyingPeriodWorkEndDateBetweenDateFilter:
autowire: true tags:
autoconfigure: true - { name: chill.export_filter, alias: social_work_actions_end_btw_dates_filter }
tags:
- { name: chill.export_filter, alias: social_work_actions_end_btw_dates_filter }
## AGGREGATORS ## AGGREGATORS
chill.person.export.aggregator_action_type: chill.person.export.aggregator_action_type:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ActionTypeAggregator class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ActionTypeAggregator
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_action_type_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_action_type_aggregator }
chill.person.export.aggregator_treatingagent_scope: chill.person.export.aggregator_treatingagent_scope:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ScopeAggregator class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ScopeAggregator
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_scope_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_treatingagent_scope_aggregator }
chill.person.export.aggregator_treatingagent_job: chill.person.export.aggregator_treatingagent_job:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\JobAggregator class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\JobAggregator
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_job_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_treatingagent_job_aggregator }
Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ReferrerAggregator: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ReferrerAggregator:
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_treatingagent_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_treatingagent_aggregator }
chill.person.export.aggregator_goal: chill.person.export.aggregator_goal:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalAggregator class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalAggregator
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_goal_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_goal_aggregator }
chill.person.export.aggregator_result: chill.person.export.aggregator_result:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ResultAggregator class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\ResultAggregator
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_result_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_result_aggregator }
chill.person.export.aggregator_goalresult: chill.person.export.aggregator_goalresult:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalResultAggregator class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalResultAggregator
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_goal_result_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_goal_result_aggregator }
Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\CurrentActionAggregator: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\CurrentActionAggregator:
autowire: true tags:
autoconfigure: true - { name: chill.export_aggregator, alias: social_work_actions_current_aggregator }
tags:
- { name: chill.export_aggregator, alias: social_work_actions_current_aggregator } Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\HandlingThirdPartyAggregator:
tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_handling3party_aggregator }
Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\HandlingThirdPartyFilter:
tags:
- { name: chill.export_filter, alias: 'acpw_handling3party_filter'}

View File

@ -480,7 +480,7 @@ acp_geog_agg_unitrefid: Clé de la zone géographique
Geographical layer: Couche géographique Geographical layer: Couche géographique
Select a geographical layer: Choisir une couche géographique Select a geographical layer: Choisir une couche géographique
Group people by geographical unit based on his address: Grouper les usagers par zone géographique (sur base de l'adresse) Group people by geographical unit based on his address: Grouper les usagers par zone géographique (sur base de l'adresse)
Filter by person's geographical unit (based on address): Filter les usagers par zone géographique (sur base de l'adresse) Filter by person's geographical unit (based on address): Filtrer les usagers par zone géographique (sur base de l'adresse)
Filter by socialaction: Filtrer les parcours par action d'accompagnement Filter by socialaction: Filtrer les parcours par action d'accompagnement
Accepted socialactions: Actions d'accompagnement Accepted socialactions: Actions d'accompagnement
@ -981,6 +981,11 @@ notification:
personId: Identifiant de l'usager personId: Identifiant de l'usager
export: export:
enum:
frequency:
YYYY-IW: par semaine
YYYY-MM: par mois
YYYY: par année
export: export:
acp_stats: acp_stats:
avg_duration: Moyenne de la durée de participation de chaque usager concerné avg_duration: Moyenne de la durée de participation de chaque usager concerné
@ -1037,6 +1042,14 @@ export:
Calc date: Date de calcul du service de l'intervenant Calc date: Date de calcul du service de l'intervenant
by_scope: by_scope:
Group course by scope: Grouper les parcours par service Group course by scope: Grouper les parcours par service
by_opening_date:
title: Grouper les parcours par date d'ouverture
frequency: Intervalle de regroupement
header: Date d'ouverture des parcours (période)
by_closing_date:
title: Grouper les parcours par date de cloture
frequency: Intervalle de regroupement
header: Date de cloture des parcours (période)
course_work: course_work:
by_treating_agent: by_treating_agent:
@ -1053,6 +1066,9 @@ export:
by_agent_job: by_agent_job:
Group by treating agent job: Grouper les actions par métier de l'agent traitant Group by treating agent job: Grouper les actions par métier de l'agent traitant
Calc date: Date de calcul du métier de l'agent traitant Calc date: Date de calcul du métier de l'agent traitant
by_handling_third_party:
title: Grouper les actions par tiers traitant
header: Tiers traitant
eval: eval:
by_end_date: by_end_date:
@ -1084,21 +1100,22 @@ export:
Persons filtered by no composition at %date%: Uniquement les usagers sans composition de ménage à la date du %date% Persons filtered by no composition at %date%: Uniquement les usagers sans composition de ménage à la date du %date%
Date calc: Date de calcul Date calc: Date de calcul
by_address_ref_status: by_address_ref_status:
Filter by person's address ref status: Filtrer par comparaison avec l'adresse de référence Filter by person's address ref status: Filtrer les usagers par comparaison avec l'adresse de référence
to_review: Diffère de l'adresse de référence to_review: Diffère de l'adresse de référence
reviewed: Diffère de l'adresse de référence mais conservé par l'utilisateur reviewed: Diffère de l'adresse de référence mais conservé par l'utilisateur
match: Identique à l'adresse de référence match: Identique à l'adresse de référence
Filtered by person\'s address status computed at %datecalc%, only %statuses%: Filtré par comparaison à l'adresse de référence, calculé à %datecalc%, seulement %statuses% Filtered by person\'s address status computed at %datecalc%, only %statuses%: Filtré par comparaison à l'adresse de référence, calculé à %datecalc%, seulement %statuses%
Status: Statut Status: Statut
Address at date: Adresse à la date
course: course:
having_info_within_interval: having_info_within_interval:
title: Filter les parcours ayant reçu une intervention entre deux dates title: Filtrer les parcours ayant reçu une intervention entre deux dates
start_date: Début de la période start_date: Début de la période
end_date: Fin de la période end_date: Fin de la période
Only course with events between %startDate% and %endDate%: Seulement les parcours ayant reçu une intervention entre le %startDate% et le %endDate% Only course with events between %startDate% and %endDate%: Seulement les parcours ayant reçu une intervention entre le %startDate% et le %endDate%
by_user_working: by_user_working:
title: Filter les parcours par intervenant, entre deux dates title: Filtrer les parcours par intervenant, entre deux dates
'Filtered by user working on course: only %users%, between %start_date% and %end_date%': 'Filtré par intervenants sur le parcours: seulement %users%, entre le %start_date% et le %end_date%' 'Filtered by user working on course: only %users%, between %start_date% and %end_date%': 'Filtré par intervenants sur le parcours: seulement %users%, entre le %start_date% et le %end_date%'
User working after: Intervention après le User working after: Intervention après le
User working before: Intervention avant le User working before: Intervention avant le
@ -1168,6 +1185,10 @@ export:
Calc date: Date à laquelle l'agent est en situation de désignation sur l'action Calc date: Date à laquelle l'agent est en situation de désignation sur l'action
calc_date_help: Il s'agit de la date à laquelle l'agent est actif comme agent traitant de l'action, et non la date à la quelle l'agent est désigné comme agent traitant. calc_date_help: Il s'agit de la date à laquelle l'agent est actif comme agent traitant de l'action, et non la date à la quelle l'agent est désigné comme agent traitant.
"Filtered by treating agent: only %agents%": "Filtré par agent traitant: uniquement %agents%" "Filtered by treating agent: only %agents%": "Filtré par agent traitant: uniquement %agents%"
by_handling3party:
title: Filtrer les actions par tiers traitant
Only 3 parties %3parties%: "Seulement les actions d'accompagnement qui ont pour tiers traitant: %3parties%"
pick_3parties: Tiers traitants des actions
list: list:
person_with_acp: person_with_acp:

View File

@ -25,7 +25,7 @@ class LabelThirdPartyHelper
return $header; return $header;
} }
if (null === $value || null === $thirdParty = $this->thirdPartyRepository->find($value)) { if ('' === $value || null === $value || null === $thirdParty = $this->thirdPartyRepository->find($value)) {
return ''; return '';
} }

View File

@ -128,8 +128,8 @@ export:
thirdParties: Tiers intervenant thirdParties: Tiers intervenant
# exports filters/aggregators # exports filters/aggregators
Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type%
is thirdparty: Le demandeur est un tiers is thirdparty: Le demandeur est un tiers
Filter by person's who have a residential address located at a thirdparty of type: Filtrer les personnes qui ont une addresse de résidence chez un tiers de catégorie "xxx" Filter by person's who have a residential address located at a thirdparty of type: Filtrer les usagers qui ont une addresse de résidence chez un tiers de catégorie "xxx"
"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%" "Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%"