Merge branch '343-social_issue_action_export' into 'master'

Resolve "Permettre de télécharger la liste des problématiques et la liste des actions en CSV"

Closes #343

See merge request Chill-Projet/chill-bundles!785
This commit is contained in:
Julien Fastré 2025-02-19 11:18:05 +00:00
commit 8de63de6d6
13 changed files with 463 additions and 152 deletions

View File

@ -0,0 +1,6 @@
kind: Feature
body: Add possibility to export a csv with all social issues and social actions
time: 2025-01-30T12:04:59.754998541+01:00
custom:
Issue: "343"
SchemaChange: No schema change

View File

@ -735,6 +735,25 @@ export:
steps: Escaliers steps: Escaliers
_lat: Latitude _lat: Latitude
_lon: Longitude _lon: Longitude
social_action_list:
id: Identifiant de l'action
social_issue_id: Identifiant de la problématique sociale
social_issue: Problématique sociale
social_issue_ordering: Ordre de la problématique sociale
action_label: Action d'accompagnement
action_ordering: Ordre
goal_label: Objectif
goal_id: Identifiant de l'objectif
goal_result_label: Résultat
goal_result_id: Identifiant du résultat
result_without_goal_label: Résultat (sans objectif)
result_without_goal_id: Identifiant du résultat (sans objectif)
evaluation_title: Évaluation
evaluation_id: Identifiant de l'évaluation
evaluation_url: Adresse URL externe (évaluation)
evaluation_delay_month: Délai de notification (mois)
evaluation_delay_week: Délai de notification (semaine)
evaluation_delay_day: Délai de notification (jours)
rolling_date: rolling_date:
year_previous_start: Début de l'année précédente year_previous_start: Début de l'année précédente

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\Controller;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Service\SocialWork\SocialActionCSVExportService;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class SocialActionCSVExportController extends AbstractController
{
public function __construct(
private readonly SocialActionRepository $socialActionRepository,
private readonly Security $security,
private readonly SocialActionCSVExportService $socialActionCSVExportService,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-action/export/list.{_format}', name: 'chill_person_social_action_export_list', requirements: ['_format' => 'csv'])]
public function socialActionList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$actions = $this->socialActionRepository->findAllOrdered();
$csv = $this->socialActionCSVExportService->generateCsv($actions);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename=results.csv',
]
);
}
}

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\Controller;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Service\SocialWork\SocialIssueCSVExportService;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class SocialIssueCSVExportController extends AbstractController
{
public function __construct(
private readonly SocialIssueRepository $socialIssueRepository,
private readonly Security $security,
private readonly SocialIssueCSVExportService $socialIssueCSVExportService,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-issue/export/list.{_format}', name: 'chill_person_social_issue_export_list', requirements: ['_format' => 'csv'])]
public function socialIssueList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$socialIssues = $this->socialIssueRepository->findAllOrdered();
$csv = $this->socialIssueCSVExportService->generateCsv($socialIssues);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
}

View File

@ -1,149 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use League\Csv\Writer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class SocialWorkExportController extends AbstractController
{
public function __construct(
private readonly SocialIssueRepository $socialIssueRepository,
private readonly SocialActionRepository $socialActionRepository,
private readonly Security $security,
private readonly TranslatorInterface $translator,
private readonly SocialIssueRender $socialIssueRender,
private readonly SocialActionRender $socialActionRender,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-issue/export/list.{_format}', name: 'chill_person_social_issue_export_list', requirements: ['_format' => 'csv'])]
public function socialIssueList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$socialIssues = $this->socialIssueRepository->findAll();
$socialIssues = array_map(fn ($issue) => [
'id' => $issue->getId(),
'title' => $this->socialIssueRender->renderString($issue, []),
'ordering' => $issue->getOrdering(),
'desactivationDate' => $issue->getDesactivationDate(),
], $socialIssues);
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans($e),
[
'Id',
'Title',
'Ordering',
'goal.desactivationDate',
]
)
);
$csv->addFormatter(fn (array $row) => null !== ($row['desactivationDate'] ?? null) ? array_merge($row, ['desactivationDate' => $row['desactivationDate']->format('Y-m-d')]) : $row);
$csv->insertAll($socialIssues);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
#[Route(path: '/{_locale}/admin/social-work/social-action/export/list.{_format}', name: 'chill_person_social_action_export_list', requirements: ['_format' => 'csv'])]
public function socialActionList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$socialActions = $this->socialActionRepository->findAll();
$socialActions = array_map(fn ($action) => [
'id' => $action->getId(),
'title' => $this->socialActionRender->renderString($action, []),
'desactivationDate' => $action->getDesactivationDate(),
'socialIssue' => $this->socialIssueRender->renderString($action->getIssue(), []),
'ordering' => $action->getOrdering(),
], $socialActions);
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans($e),
[
'Id',
'Title',
'goal.desactivationDate',
'Social issue',
'Ordering',
]
)
);
$csv->addFormatter(fn (array $row) => null !== ($row['desactivationDate'] ?? null) ? array_merge($row, ['desactivationDate' => $row['desactivationDate']->format('Y-m-d')]) : $row);
$csv->insertAll($socialActions);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
}

View File

@ -44,6 +44,14 @@ final readonly class SocialActionRepository implements ObjectRepository
return $this->repository->findAll(); return $this->repository->findAll();
} }
public function findAllOrdered(): array
{
return $this->repository->createQueryBuilder('si')
->orderBy('si.ordering', 'ASC')
->getQuery()
->getResult();
}
/** /**
* @return array|SocialAction[] * @return array|SocialAction[]
*/ */

View File

@ -39,6 +39,14 @@ final readonly class SocialIssueRepository implements ObjectRepository
return $this->repository->findAll(); return $this->repository->findAll();
} }
public function findAllOrdered(): array
{
return $this->repository->createQueryBuilder('si')
->orderBy('si.ordering', 'ASC')
->getQuery()
->getResult();
}
/** /**
* @return array|SocialIssue[] * @return array|SocialIssue[]
*/ */

View File

@ -0,0 +1,104 @@
<?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\Service\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\Result;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use League\Csv\Writer;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class SocialActionCSVExportService
{
public function __construct(
private SocialIssueRender $socialIssueRender,
private SocialActionRender $socialActionRender,
private TranslatableStringHelperInterface $stringHelper,
private TranslatorInterface $translator,
) {}
/**
* @param list<SocialAction> $actions
*/
public function generateCsv(array $actions): Writer
{
// CSV headers
$headers = array_map(
fn (string $tr) => $this->translator->trans('export.social_action_list.'.$tr),
array_keys($this->formatRow(new SocialAction()))
);
$csv = Writer::createFromPath('php://temp', 'w+');
$csv->insertOne($headers);
foreach ($actions as $action) {
if ($action->getGoals()->isEmpty() && $action->getResults()->isEmpty() && $action->getEvaluations()->isEmpty()) {
$csv->insertOne($this->formatRow($action));
}
foreach ($action->getGoals() as $goal) {
if ($goal->getResults()->isEmpty()) {
$csv->insertOne($this->formatRow($action, $goal));
}
foreach ($goal->getResults() as $goalResult) {
$csv->insertOne($this->formatRow($action, $goal, $goalResult));
}
}
foreach ($action->getResults() as $result) {
if ($result->getGoals()->isEmpty()) {
$csv->insertOne($this->formatRow($action, null, null, $result));
}
}
foreach ($action->getEvaluations() as $evaluation) {
$csv->insertOne($this->formatRow($action, evaluation: $evaluation));
}
}
return $csv;
}
private function formatRow(
SocialAction $action,
?Goal $goal = null,
?Result $goalResult = null,
?Result $resultWithoutGoal = null,
?Evaluation $evaluation = null,
): array {
return [
'action_id' => $action->getId(),
'social_issue_id' => $action->getIssue()?->getId(),
'problematique_label' => null !== $action->getIssue() ? $this->socialIssueRender->renderString($action->getIssue(), []) : null,
'social_issue_ordering' => null !== $action->getIssue() ? $action->getIssue()->getOrdering() : null,
'action_label' => $this->socialActionRender->renderString($action, []),
'action_ordering' => $action->getOrdering(),
'goal_label' => null !== $goal ? $this->stringHelper->localize($goal->getTitle()) : null,
'goal_id' => $goal?->getId(),
'goal_result_label' => null !== $goalResult ? $this->stringHelper->localize($goalResult->getTitle()) : null,
'goal_result_id' => $goalResult?->getId(),
'result_without_goal_label' => null !== $resultWithoutGoal ? $this->stringHelper->localize($resultWithoutGoal->getTitle()) : null,
'result_without_goal_id' => $resultWithoutGoal?->getId(),
'evaluation_title' => null !== $evaluation ? $this->stringHelper->localize($evaluation->getTitle()) : null,
'evaluation_id' => $evaluation?->getId(),
'evaluation_url' => $evaluation?->getUrl(),
'evaluation_delay_month' => $evaluation?->getDelay()?->format('%m'),
'evaluation_delay_week' => $evaluation?->getDelay()?->format('%w'),
'evaluation_delay_day' => $evaluation?->getDelay()?->format('%d'),
];
}
}

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\Service\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\UnavailableStream;
use League\Csv\Writer;
use Symfony\Contracts\Translation\TranslatorInterface;
readonly class SocialIssueCSVExportService
{
public function __construct(
private SocialIssueRender $socialIssueRender,
private TranslatableStringHelperInterface $stringHelper,
private TranslatorInterface $translator,
) {}
/**
* @throws UnavailableStream
* @throws CannotInsertRecord
* @throws Exception
*/
public function generateCsv(array $issues): Writer
{
// CSV headers
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans($e),
[
'Id',
'Label',
'Social issue',
'socialIssue.isParent?',
'socialIssue.Parent id',
]
)
);
foreach ($issues as $issue) {
$csv->insertOne($this->formatRow($issue));
}
return $csv;
}
private function formatRow(
SocialIssue $issue,
): array {
return
[
'id' => $issue->getId(),
'label' => $this->stringHelper->localize($issue->getTitle()),
'title' => $this->socialIssueRender->renderString($issue, []),
'isParent' => $issue->hasChildren() ? 'X' : '',
'parent_id' => null !== $issue->getParent() ? $issue->getParent()->getId() : '',
];
}
}

View File

@ -19,7 +19,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @implements ChillEntityRenderInterface<SocialIssue> * @implements ChillEntityRenderInterface<SocialIssue>
*/ */
final readonly class SocialIssueRender implements ChillEntityRenderInterface class SocialIssueRender implements ChillEntityRenderInterface
{ {
public const AND_CHILDREN_MENTION = 'show_and_children_mention'; public const AND_CHILDREN_MENTION = 'show_and_children_mention';
@ -37,7 +37,7 @@ final readonly class SocialIssueRender implements ChillEntityRenderInterface
*/ */
public const SHOW_AND_CHILDREN = 'show_and_children'; public const SHOW_AND_CHILDREN = 'show_and_children';
public function __construct(private TranslatableStringHelper $translatableStringHelper, private \Twig\Environment $engine, private TranslatorInterface $translator) {} public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {}
public function renderBox($socialIssue, array $options): string public function renderBox($socialIssue, array $options): string
{ {

View File

@ -0,0 +1,102 @@
<?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 Bundle\ChillPersonBundle\Tests\Exporters;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\Result;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Service\SocialWork\SocialActionCSVExportService;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class SocialActionCsvExporterTest extends TestCase
{
public function testGenerateCsv(): void
{
$translator = $this->createMock(TranslatorInterface::class);
$translator->method('trans')->willReturnArgument(0);
$socialIssueRender = $this->createMock(SocialIssueRender::class);
$socialIssueRender->method('renderString')->willReturnCallback(static fn (SocialIssue $socialIssue) => $socialIssue->getTitle()['fr'] ?? '');
$socialActionRender = $this->createMock(SocialActionRender::class);
$socialActionRender->method('renderString')->willReturnCallback(static fn (SocialAction $socialAction) => $socialAction->getTitle()['fr'] ?? '');
$stringHelper = $this->createMock(TranslatableStringHelperInterface::class);
$stringHelper->method('localize')
->willReturnCallback(static fn (array $messages) => $messages['fr'] ?? 'not found');
$exporter = new SocialActionCSVExportService($socialIssueRender, $socialActionRender, $stringHelper, $translator);
// Mock social issue
// Création d'une instance réelle de SocialIssue
$socialIssue = new SocialIssue();
$socialIssue->setTitle(['fr' => 'Issue Title']); // Exemple de définition d'une propriété
// Création d'une instance réelle de SocialAction sans objectifs ni résultats
$actionWithoutGoalsOrResults = new SocialAction();
$actionWithoutGoalsOrResults->setIssue($socialIssue);
$actionWithoutGoalsOrResults->setTitle(['fr' => 'Action without goals or results']);
// Création d'une instance réelle de SocialAction avec des objectifs et des résultats
$goalWithResult = new Goal();
$resultWithAction = new Result();
$goalWithResult->addResult($resultWithAction);
$actionWithGoalsAndResults = new SocialAction();
$actionWithGoalsAndResults->setIssue($socialIssue);
$actionWithGoalsAndResults->setTitle(['fr' => 'Action with goals and results']);
$actionWithGoalsAndResults->addGoal($goalWithResult);
// Création d'une instance réelle de SocialAction avec des objectifs mais sans résultats
$goalWithoutResult = new Goal();
$actionWithGoalsNoResults = new SocialAction();
$actionWithGoalsNoResults->setIssue($socialIssue);
$actionWithGoalsNoResults->setTitle(['fr' => 'Action with goals and no results']);
$actionWithGoalsNoResults->addGoal($goalWithoutResult);
// Création d'une instance réelle de SocialAction avec des résultats mais sans objectifs
$resultWithNoAction = new Result();
$resultWithNoAction->setTitle(['fr' => 'Result without objectives']);
$actionWithResultsNoGoals = new SocialAction();
$actionWithResultsNoGoals->setIssue($socialIssue);
$actionWithResultsNoGoals->setTitle(['fr' => 'Action with results and no goals']);
$actionWithResultsNoGoals->addResult($resultWithNoAction);
// generate
$csv = $exporter->generateCsv([$actionWithGoalsAndResults, $actionWithoutGoalsOrResults,
$actionWithGoalsNoResults, $actionWithResultsNoGoals]);
$content = $csv->toString();
// Assert CSV contains expected values
$this->assertStringContainsString('Action without goals or results', $content);
$this->assertStringContainsString('Action with goals and results', $content);
$this->assertStringContainsString('Action with goals and no results', $content);
$this->assertStringContainsString('Action with results and no goals', $content);
self::assertEquals(<<<'CSV'
export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day
,,"Issue Title",0,"Action with goals and results",0,"not found",,"not found",,,,,,,,,
,,"Issue Title",0,"Action without goals or results",0,,,,,,,,,,,,
,,"Issue Title",0,"Action with goals and no results",0,"not found",,,,,,,,,,,
,,"Issue Title",0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,,
CSV, $content);
}
}

View File

@ -71,6 +71,9 @@ services:
Chill\PersonBundle\Controller\PersonSignatureController: Chill\PersonBundle\Controller\PersonSignatureController:
tags: [ 'controller.service_arguments' ] tags: [ 'controller.service_arguments' ]
Chill\PersonBundle\Controller\SocialWorkExportController: Chill\PersonBundle\Controller\SocialIssueCSVExportController:
tags: [ 'controller.service_arguments' ]
Chill\PersonBundle\Controller\SocialActionCSVExportController:
tags: [ 'controller.service_arguments' ] tags: [ 'controller.service_arguments' ]

View File

@ -755,6 +755,11 @@ socialAction:
defaultNotificationDelay: Délai de notification par défaut defaultNotificationDelay: Délai de notification par défaut
socialIssue: Problématique sociale socialIssue: Problématique sociale
socialIssue:
isParent?: Parent?
Parent id: Identifiant du parent
household_id: Identifiant du ménage household_id: Identifiant du ménage
household: household:
allowHolder: Peut être titulaire allowHolder: Peut être titulaire