[export] add a filter and aggregator on accompanying periods: group by activity type (accompanying course having at least one activity from this type)

This commit is contained in:
Julien Fastré 2023-10-18 16:56:35 +02:00
parent 11fb9bcd0b
commit 981dc6a959
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
6 changed files with 229 additions and 0 deletions

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,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

@ -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

@ -208,3 +208,7 @@ services:
Chill\ActivityBundle\Export\Aggregator\PersonsAggregator: Chill\ActivityBundle\Export\Aggregator\PersonsAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: activity_by_persons_aggregator } - { 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

@ -383,6 +383,12 @@ export:
persons taking part on the activity: Usagers participants à l'échange 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

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