Add household info to activity exports

This commit is contained in:
LenaertsJ 2024-08-30 08:57:27 +00:00
parent 3738c110f8
commit 4587f66402
12 changed files with 301 additions and 12 deletions

View File

@ -0,0 +1,6 @@
kind: Feature
body: Add export aggregator to aggregate activities by household + filter persons
that are not part of an accompanyingperiod during a certain timeframe.
time: 2024-08-30T10:47:31.29306704+02:00
custom:
Issue: ""

View File

@ -0,0 +1,99 @@
<?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\PersonAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Repository\Household\HouseholdRepository;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class HouseholdAggregator implements AggregatorInterface
{
public function __construct(private HouseholdRepository $householdRepository) {}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return function (int|string|null $value): string|int {
if ('_header' === $value) {
return 'export.aggregator.person.by_household.household';
}
if ('' === $value || null === $value || null === $household = $this->householdRepository->find($value)) {
return '';
}
return $household->getId();
};
}
public function getQueryKeys($data)
{
return ['activity_household_agg'];
}
public function getTitle()
{
return 'export.aggregator.person.by_household.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join(
HouseholdMember::class,
'activity_household_agg_household_member',
Join::WITH,
$qb->expr()->andX(
$qb->expr()->eq('activity_household_agg_household_member.person', 'activity.person'),
$qb->expr()->lte('activity_household_agg_household_member.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->gte('activity_household_agg_household_member.endDate', 'activity.date'),
$qb->expr()->isNull('activity_household_agg_household_member.endDate')
)
)
);
$qb->join(
Household::class,
'activity_household_agg_household',
Join::WITH,
$qb->expr()->eq('activity_household_agg_household_member.household', 'activity_household_agg_household')
);
$qb
->addSelect('activity_household_agg_household.id AS activity_household_agg')
->addGroupBy('activity_household_agg');
}
public function applyOn()
{
return Declarations::ACTIVITY_PERSON;
}
}

View File

@ -19,6 +19,7 @@ use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface;
@ -44,6 +45,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
'person_firstname',
'person_lastname',
'person_id',
'household_id',
];
private readonly bool $filterStatsByCenters;
@ -189,19 +191,26 @@ class ListActivity implements ListInterface, GroupedExportInterface
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
// throw an error if any fields are present
// throw an error if no fields are present
if (!\array_key_exists('fields', $data)) {
throw new InvalidArgumentException('Any fields have been checked.');
throw new InvalidArgumentException('No fields have been checked.');
}
$qb = $this->entityManager->createQueryBuilder();
$qb
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'actperson');
->join('activity.person', 'person')
->join(
HouseholdMember::class,
'householdmember',
Query\Expr\Join::WITH,
'person = householdmember.person AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)'
)
->join('householdmember.household', 'household');
if ($this->filterStatsByCenters) {
$qb->join('actperson.centerHistory', 'centerHistory');
$qb->join('person.centerHistory', 'centerHistory');
$qb->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
@ -224,17 +233,22 @@ class ListActivity implements ListInterface, GroupedExportInterface
break;
case 'person_firstname':
$qb->addSelect('actperson.firstName AS person_firstname');
$qb->addSelect('person.firstName AS person_firstname');
break;
case 'person_lastname':
$qb->addSelect('actperson.lastName AS person_lastname');
$qb->addSelect('person.lastName AS person_lastname');
break;
case 'person_id':
$qb->addSelect('actperson.id AS person_id');
$qb->addSelect('person.id AS person_id');
break;
case 'household_id':
$qb->addSelect('household.id AS household_id');
break;
@ -284,7 +298,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return ActivityStatsVoter::LISTS;
}
public function supportsModifiers()
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,

View File

@ -73,7 +73,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt
$qb->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = activity.accompanyingPeriod"
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = acp"
)
);

View File

@ -39,7 +39,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
public function alterQuery(QueryBuilder $qb, $data): void
{
// create a subquery for activity
$sqb = $qb->getEntityManager()->createQueryBuilder();
@ -121,7 +121,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
];
}
public function describeAction($data, $format = 'string')
public function describeAction($data, $format = 'string'): array
{
return [
[] === $data['reasons'] ?
@ -141,7 +141,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
];
}
public function getTitle()
public function getTitle(): string
{
return 'export.filter.activity.person_between_dates.title';
}

View File

@ -243,3 +243,7 @@ services:
Chill\ActivityBundle\Export\Aggregator\PersonAggregators\PersonAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_person_agg }
Chill\ActivityBundle\Export\Aggregator\PersonAggregators\HouseholdAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_household_agg }

View File

@ -428,6 +428,9 @@ export:
by_person:
title: Grouper les échanges par usager (dossier d'usager dans lequel l'échange est enregistré)
person: Usager
by_household:
title: Grouper les échanges par ménage
household: Identifiant ménage
acp:
by_activity_type:
title: Grouper les parcours par type d'échange

View File

@ -0,0 +1,90 @@
<?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\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class WithoutParticipationBetweenDatesFilter implements FilterInterface
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = 'without_participation_between_dates_filter';
$qb
->andWhere(
$qb->expr()->not(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class." {$p}_acp JOIN {$p}_acp.accompanyingPeriod {$p}_acpp ".
"WHERE {$p}_acp.person = person ".
"AND OVERLAPSI({$p}_acp.startDate, {$p}_acp.endDate), (:{$p}_date_after, :{$p}_date_before) = TRUE ".
"AND OVERLAPSI({$p}_acpp.openingDate, {$p}_acpp.closingDate), (:{$p}_date_after, :{$p}_date_before) = TRUE"
)
)
)
->setParameter("{$p}_date_after", $this->rollingDateConverter->convert($data['date_after']), Types::DATE_IMMUTABLE)
->setParameter("{$p}_date_before", $this->rollingDateConverter->convert($data['date_before']), Types::DATE_IMMUTABLE);
}
public function applyOn(): string
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_after', PickRollingDateType::class, [
'label' => 'export.filter.person.without_participation_between_dates.date_after',
]);
$builder->add('date_before', PickRollingDateType::class, [
'label' => 'export.filter.person.without_participation_between_dates.date_before',
]);
}
public function getFormDefaultData(): array
{
return [
'date_after' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'date_before' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string')
{
return ['exports.filter.person.without_participation_between_dates.Filtered by having no participations during period: between', [
'dateafter' => $this->rollingDateConverter->convert($data['date_after']),
'datebefore' => $this->rollingDateConverter->convert($data['date_before']),
]];
}
public function getTitle()
{
return 'export.filter.person.without_participation_between_dates.title';
}
}

View File

@ -0,0 +1,62 @@
<?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 Export\Filter\PersonFilters;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Export\Filter\PersonFilters\WithoutParticipationBetweenDatesFilter;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
final class WithoutParticipationBetweenDatesFilterTest extends AbstractFilterTest
{
private WithoutParticipationBetweenDatesFilter $filter;
protected function setUp(): void
{
self::bootKernel();
$this->filter = self::getContainer()->get(WithoutParticipationBetweenDatesFilter::class);
}
public function getFilter()
{
return $this->filter;
}
public static function getFormData(): array
{
return [
[
'date_after' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'date_before' => new RollingDate(RollingDate::T_TODAY),
],
];
}
public static function getQueryBuilders(): iterable
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('person.id')
->from(Person::class, 'person'),
];
}
}

View File

@ -124,6 +124,10 @@ services:
tags:
- { name: chill.export_filter, alias: person_with_participation_between_dates_filter }
Chill\PersonBundle\Export\Filter\PersonFilters\WithoutParticipationBetweenDatesFilter:
tags:
- { name: chill.export_filter, alias: person_without_participation_between_dates_filter }
## Aggregators
chill.person.export.aggregator_nationality:
class: Chill\PersonBundle\Export\Aggregator\PersonAggregators\NationalityAggregator

View File

@ -136,6 +136,9 @@ exports:
Filtered by person\'s geographical unit (based on address) computed at date, only units:
"Filtré par zone géographique sur base de l'adresse, calculé à {datecalc, date, short}, seulement les zones suivantes: {units}"
filter:
person:
without_participation_between_dates:
"Filtered by having no participations during period: between": "Uniquement les usagers qui n'ont été concerné par aucun parcours entre le {dateafter, date, short} et le {datebefore, date, short}"
course:
not_having_address_reference:
describe: >-

View File

@ -1184,6 +1184,10 @@ export:
date_before: Concerné par un parcours avant le
title: Filtrer les usagers ayant été associés à un parcours ouverts un jour dans la période de temps indiquée
'Filtered by participations during period: between %dateafter% and %datebefore%': 'Filtré par personne concerné par un parcours dans la periode entre: %dateafter% et %datebefore%'
without_participation_between_dates:
date_after: Après le
date_before: Avant le
title: Filtrer les usagers n'ayant été associés à aucun parcours
course:
not_having_address_reference: