diff --git a/.changes/unreleased/Feature-20230707-123609.yaml b/.changes/unreleased/Feature-20230707-123609.yaml new file mode 100644 index 000000000..51ff94d4c --- /dev/null +++ b/.changes/unreleased/Feature-20230707-123609.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: '[export] Add a list for people with their associated course' +time: 2023-07-07T12:36:09.596469063+02:00 +custom: + Issue: "125" diff --git a/.changes/unreleased/Feature-20230707-124132.yaml b/.changes/unreleased/Feature-20230707-124132.yaml new file mode 100644 index 000000000..4ad93ad22 --- /dev/null +++ b/.changes/unreleased/Feature-20230707-124132.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: '[export] Add ordering by person''s lastname or course opening date in list + which concerns accompanying course or peoples' +time: 2023-07-07T12:41:32.112725962+02:00 +custom: + Issue: "" diff --git a/src/Bundle/ChillMainBundle/Export/ExportInterface.php b/src/Bundle/ChillMainBundle/Export/ExportInterface.php index f357a9fdb..a11a51746 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportInterface.php @@ -97,7 +97,7 @@ interface ExportInterface extends ExportElementInterface * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * @param mixed $data The data from the export's form (as defined in `buildForm`) * - * @return callable(null|string|int|float|'_header' $value): string|int|\DateTimeInterface where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * @return (callable(null|string|int|float|'_header' $value): string|int|\DateTimeInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ public function getLabels($key, array $values, $data); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php index af66ab312..ab9c0db2f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php @@ -29,6 +29,7 @@ use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress; use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Export\Helper\ListAccompanyingPeriodHelper; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; @@ -45,95 +46,13 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function strlen; -class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface +final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface { - private const FIELDS = [ - 'id', - 'step', - 'stepSince', - 'openingDate', - 'closingDate', - 'referrer', - 'referrerSince', - 'administrativeLocation', - 'locationIsPerson', - 'locationIsTemp', - 'locationPersonName', - 'locationPersonId', - 'origin', - 'closingMotive', - 'confidential', - 'emergency', - 'intensity', - 'job', - 'isRequestorPerson', - 'isRequestorThirdParty', - 'requestorPerson', - 'requestorPersonId', - 'requestorThirdParty', - 'requestorThirdPartyId', - 'scopes', - 'socialIssues', - 'createdAt', - 'createdBy', - 'updatedAt', - 'updatedBy', - ]; - - private ExportAddressHelper $addressHelper; - - private DateTimeHelper $dateTimeHelper; - - private EntityManagerInterface $entityManager; - - private PersonRenderInterface $personRender; - - private PersonRepository $personRepository; - - private RollingDateConverterInterface $rollingDateConverter; - - private SocialIssueRender $socialIssueRender; - - private SocialIssueRepository $socialIssueRepository; - - private ThirdPartyRender $thirdPartyRender; - - private ThirdPartyRepository $thirdPartyRepository; - - private TranslatableStringHelperInterface $translatableStringHelper; - - private TranslatorInterface $translator; - - private UserHelper $userHelper; - public function __construct( - ExportAddressHelper $addressHelper, - DateTimeHelper $dateTimeHelper, - EntityManagerInterface $entityManager, - PersonRenderInterface $personRender, - PersonRepository $personRepository, - ThirdPartyRepository $thirdPartyRepository, - ThirdPartyRender $thirdPartyRender, - SocialIssueRepository $socialIssueRepository, - SocialIssueRender $socialIssueRender, - TranslatableStringHelperInterface $translatableStringHelper, - TranslatorInterface $translator, - RollingDateConverterInterface $rollingDateConverter, - UserHelper $userHelper + private EntityManagerInterface $entityManager, + private RollingDateConverterInterface $rollingDateConverter, + private ListAccompanyingPeriodHelper $listAccompanyingPeriodHelper, ) { - $this->addressHelper = $addressHelper; - $this->dateTimeHelper = $dateTimeHelper; - $this->entityManager = $entityManager; - $this->personRender = $personRender; - $this->personRepository = $personRepository; - $this->socialIssueRender = $socialIssueRender; - $this->socialIssueRepository = $socialIssueRepository; - $this->thirdPartyRender = $thirdPartyRender; - $this->thirdPartyRepository = $thirdPartyRepository; - $this->translatableStringHelper = $translatableStringHelper; - $this->translator = $translator; - $this->rollingDateConverter = $rollingDateConverter; - $this->userHelper = $userHelper; } public function buildForm(FormBuilderInterface $builder) @@ -169,141 +88,12 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface public function getLabels($key, array $values, $data) { - if (substr($key, 0, strlen('address_fields')) === 'address_fields') { - return $this->addressHelper->getLabel($key, $values, $data, 'address_fields'); - } - - switch ($key) { - case 'stepSince': - case 'openingDate': - case 'closingDate': - case 'referrerSince': - case 'createdAt': - case 'updatedAt': - return $this->dateTimeHelper->getLabel('export.list.acp.' . $key); - - case 'origin': - case 'closingMotive': - case 'job': - return function ($value) use ($key) { - if ('_header' === $value) { - return 'export.list.acp.' . $key; - } - - if (null === $value) { - return ''; - } - - return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); - }; - - case 'locationPersonName': - case 'requestorPerson': - return function ($value) use ($key) { - if ('_header' === $value) { - return 'export.list.acp.' . $key; - } - - if (null === $value || null === $person = $this->personRepository->find($value)) { - return ''; - } - - return $this->personRender->renderString($person, []); - }; - - case 'requestorThirdParty': - return function ($value) use ($key) { - if ('_header' === $value) { - return 'export.list.acp.' . $key; - } - - if (null === $value || null === $thirdparty = $this->thirdPartyRepository->find($value)) { - return ''; - } - - return $this->thirdPartyRender->renderString($thirdparty, []); - }; - - case 'scopes': - return function ($value) use ($key) { - if ('_header' === $value) { - return 'export.list.acp.' . $key; - } - - if (null === $value) { - return ''; - } - - return implode( - '|', - array_map( - fn ($s) => $this->translatableStringHelper->localize($s), - json_decode($value, true, 512, JSON_THROW_ON_ERROR) - ) - ); - }; - - case 'socialIssues': - return function ($value) use ($key) { - if ('_header' === $value) { - return 'export.list.acp.' . $key; - } - - if (null === $value) { - return ''; - } - - return implode( - '|', - array_map( - fn ($s) => $this->socialIssueRender->renderString($this->socialIssueRepository->find($s), []), - json_decode($value, true, 512, JSON_THROW_ON_ERROR) - ) - ); - }; - - case 'step': - return fn ($value) => match ($value) { - '_header' => 'export.list.acp.step', - null => '', - AccompanyingPeriod::STEP_DRAFT => $this->translator->trans('course.draft'), - AccompanyingPeriod::STEP_CONFIRMED => $this->translator->trans('course.confirmed'), - AccompanyingPeriod::STEP_CLOSED => $this->translator->trans('course.closed'), - AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT => $this->translator->trans('course.inactive_short'), - AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG => $this->translator->trans('course.inactive_long'), - default => $value, - }; - - case 'intensity': - return fn ($value) => match ($value) { - '_header' => 'export.list.acp.intensity', - null => '', - AccompanyingPeriod::INTENSITY_OCCASIONAL => $this->translator->trans('occasional'), - AccompanyingPeriod::INTENSITY_REGULAR => $this->translator->trans('regular'), - default => $value, - }; - - default: - return static function ($value) use ($key) { - if ('_header' === $value) { - return 'export.list.acp.' . $key; - } - - if (null === $value) { - return ''; - } - - return $value; - }; - } + return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data); } public function getQueryKeys($data) { - return array_merge( - self::FIELDS, - $this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields') - ); + return $this->listAccompanyingPeriodHelper->getQueryKeys($data); } public function getResult($query, $data) @@ -341,7 +131,12 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface ->setParameter('list_acp_step', AccompanyingPeriod::STEP_DRAFT) ->setParameter('authorized_centers', $centers); - $this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date'])); + $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date'])); + + $qb + ->addOrderBy('acp.openingDate') + ->addOrderBy('acp.closingDate') + ->addOrderBy('acp.id'); return $qb; } @@ -357,91 +152,4 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface Declarations::ACP_TYPE, ]; } - - private function addSelectClauses(QueryBuilder $qb, DateTimeImmutable $calcDate): void - { - // add the regular fields - foreach (['id', 'openingDate', 'closingDate', 'confidential', 'emergency', 'intensity', 'createdAt', 'updatedAt'] as $field) { - $qb->addSelect(sprintf('acp.%s AS %s', $field, $field)); - } - - // add the field which are simple association - foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'createdBy' => 'label', 'updatedBy' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) { - $qb - ->leftJoin(sprintf('acp.%s', $entity), "{$entity}_t") - ->addSelect(sprintf('%s_t.%s AS %s', $entity, $field, $entity)); - } - - // step at date - $qb - ->addSelect('stepHistory.step AS step') - ->addSelect('stepHistory.startDate AS stepSince') - ->leftJoin('acp.stepHistories', 'stepHistory') - ->andWhere( - $qb->expr()->andX( - $qb->expr()->lte('stepHistory.startDate', ':calcDate'), - $qb->expr()->orX($qb->expr()->isNull('stepHistory.endDate'), $qb->expr()->gt('stepHistory.endDate', ':calcDate')) - ) - ); - - // referree at date - $qb - ->addSelect('referrer_t.label AS referrer') - ->addSelect('userHistory.startDate AS referrerSince') - ->leftJoin('acp.userHistories', 'userHistory') - ->leftJoin('userHistory.user', 'referrer_t') - ->andWhere( - $qb->expr()->orX( - $qb->expr()->isNull('userHistory'), - $qb->expr()->andX( - $qb->expr()->lte('userHistory.startDate', ':calcDate'), - $qb->expr()->orX($qb->expr()->isNull('userHistory.endDate'), $qb->expr()->gt('userHistory.endDate', ':calcDate')) - ) - ) - ); - - // location of the acp - $qb - ->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 1 ELSE 0 END AS locationIsPerson') - ->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 0 ELSE 1 END AS locationIsTemp') - ->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonName') - ->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonId') - ->leftJoin('acp.locationHistories', 'locationHistory') - ->andWhere( - $qb->expr()->orX( - $qb->expr()->isNull('locationHistory'), - $qb->expr()->andX( - $qb->expr()->lte('locationHistory.startDate', ':calcDate'), - $qb->expr()->orX($qb->expr()->isNull('locationHistory.endDate'), $qb->expr()->gt('locationHistory.endDate', ':calcDate')) - ) - ) - ) - ->leftJoin( - PersonHouseholdAddress::class, - 'personAddress', - Join::WITH, - 'locationHistory.personLocation = personAddress.person AND (personAddress.validFrom <= :calcDate AND (personAddress.validTo IS NULL OR personAddress.validTo > :calcDate))' - ) - ->leftJoin(Address::class, 'acp_address', Join::WITH, 'COALESCE(IDENTITY(locationHistory.addressLocation), IDENTITY(personAddress.address)) = acp_address.id'); - - $this->addressHelper->addSelectClauses(ExportAddressHelper::F_ALL, $qb, 'acp_address', 'address_fields'); - - // requestor - $qb - ->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 1 ELSE 0 END AS isRequestorPerson') - ->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 0 ELSE 1 END AS isRequestorThirdParty') - ->addSelect('IDENTITY(acp.requestorPerson) AS requestorPersonId') - ->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdPartyId') - ->addSelect('IDENTITY(acp.requestorPerson) AS requestorPerson') - ->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdParty'); - - $qb - // scopes - ->addSelect('(SELECT AGGREGATE(scope.name) FROM ' . Scope::class . ' scope WHERE scope MEMBER OF acp.scopes) AS scopes') - // social issues - ->addSelect('(SELECT AGGREGATE(socialIssue.id) FROM ' . SocialIssue::class . ' socialIssue WHERE socialIssue MEMBER OF acp.socialIssues) AS socialIssues'); - - // add parameter - $qb->setParameter('calcDate', $calcDate); - } } diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php similarity index 95% rename from src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriod.php rename to src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php index 370046232..408d0b3af 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php @@ -35,7 +35,12 @@ use function count; use function in_array; use function strlen; -class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface +/** + * List the persons, having an accompanying period. + * + * Details of the accompanying period are not included + */ +class ListPersonHavingAccompanyingPeriod implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface { private ExportAddressHelper $addressHelper; @@ -185,6 +190,11 @@ class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterfac $this->listPersonHelper->addSelect($qb, $fields, $data['address_date']); + $qb + ->addOrderBy('person.lastName') + ->addOrderBy('person.firstName') + ->addOrderBy('person.id'); + return $qb; } diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php new file mode 100644 index 000000000..ddb16bb2d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php @@ -0,0 +1,155 @@ +add('address_date', PickRollingDateType::class, [ + 'label' => 'Data valid at this date', + 'help' => 'Data regarding center, addresses, and so on will be computed at this date', + ]); + } + public function getFormDefaultData(): array + { + return ['address_date' => new RollingDate(RollingDate::T_TODAY)]; + } + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_LIST]; + } + + public function getDescription() + { + return 'export.list.person_with_acp.Create a list of people having an accompaying periods with details of period, according to various filters.'; + } + + public function getGroup(): string + { + return 'Exports of persons'; + } + + public function getLabels($key, array $values, $data) + { + if (in_array($key, $this->listPersonHelper->getAllKeys(), true)) { + return $this->listPersonHelper->getLabels($key, $values, $data); + } + + return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data); + } + + public function getQueryKeys($data) + { + return array_merge( + $this->listPersonHelper->getAllKeys(), + $this->listAccompanyingPeriodHelper->getQueryKeys($data), + ); + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); + } + + public function getTitle() + { + return 'export.list.person_with_acp.List peoples having an accompanying period with period details'; + } + + public function getType() + { + return Declarations::PERSON_TYPE; + } + + /** + * param array{fields: string[], address_date: DateTimeImmutable} $data. + */ + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $centers = array_map(static fn ($el) => $el['center'], $acl); + + $qb = $this->entityManager->createQueryBuilder(); + + $qb->from(Person::class, 'person') + ->join('person.accompanyingPeriodParticipations', 'acppart') + ->join('acppart.accompanyingPeriod', 'acp') + ->andWhere($qb->expr()->neq('acp.step', "'" . AccompanyingPeriod::STEP_DRAFT . "'")) + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . PersonCenterHistory::class . ' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)' + ) + )->setParameter('authorized_centers', $centers); + + $this->listPersonHelper->addSelect($qb, ListPersonHelper::FIELDS, $this->rollingDateConverter->convert($data['address_date'])); + $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['address_date'])); + + $qb + ->addOrderBy('person.lastName') + ->addOrderBy('person.firstName') + ->addOrderBy('person.id') + ->addOrderBy('acp.id'); + + return $qb; + } + + public function requiredRole(): string + { + return PersonVoter::LISTS; + } + + public function supportsModifiers() + { + return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN, Declarations::ACP_TYPE]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php new file mode 100644 index 000000000..5fa2252cd --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Helper/ListAccompanyingPeriodHelper.php @@ -0,0 +1,317 @@ +addressHelper->getKeys(ExportAddressHelper::F_ALL, 'acp_address_fields') + ); + } + + public function getLabels($key, array $values, $data) + { + if (str_starts_with($key, 'acp_address_fields')) { + return $this->addressHelper->getLabel($key, $values, $data, 'acp_address_fields'); + } + + switch ($key) { + case 'stepSince': + case 'openingDate': + case 'closingDate': + case 'referrerSince': + case 'acpCreatedAt': + case 'acpUpdatedAt': + return $this->dateTimeHelper->getLabel('export.list.acp.' . $key); + + case 'origin': + case 'closingMotive': + case 'job': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp.' . $key; + } + + if (null === $value) { + return ''; + } + + return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR)); + }; + + case 'locationPersonName': + case 'requestorPerson': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp.' . $key; + } + + if (null === $value || null === $person = $this->personRepository->find($value)) { + return ''; + } + + return $this->personRender->renderString($person, []); + }; + + case 'requestorThirdParty': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp.' . $key; + } + + if (null === $value || null === $thirdparty = $this->thirdPartyRepository->find($value)) { + return ''; + } + + return $this->thirdPartyRender->renderString($thirdparty, []); + }; + + case 'scopes': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp.' . $key; + } + + if (null === $value) { + return ''; + } + + return implode( + '|', + array_map( + fn ($s) => $this->translatableStringHelper->localize($s), + json_decode($value, true, 512, JSON_THROW_ON_ERROR) + ) + ); + }; + + case 'socialIssues': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp.' . $key; + } + + if (null === $value) { + return ''; + } + + return implode( + '|', + array_map( + fn ($s) => $this->socialIssueRender->renderString($this->socialIssueRepository->find($s), []), + json_decode($value, true, 512, JSON_THROW_ON_ERROR) + ) + ); + }; + + case 'step': + return fn ($value) => match ($value) { + '_header' => 'export.list.acp.step', + null => '', + AccompanyingPeriod::STEP_DRAFT => $this->translator->trans('course.draft'), + AccompanyingPeriod::STEP_CONFIRMED => $this->translator->trans('course.confirmed'), + AccompanyingPeriod::STEP_CLOSED => $this->translator->trans('course.closed'), + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT => $this->translator->trans('course.inactive_short'), + AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG => $this->translator->trans('course.inactive_long'), + default => $value, + }; + + case 'intensity': + return fn ($value) => match ($value) { + '_header' => 'export.list.acp.intensity', + null => '', + AccompanyingPeriod::INTENSITY_OCCASIONAL => $this->translator->trans('occasional'), + AccompanyingPeriod::INTENSITY_REGULAR => $this->translator->trans('regular'), + default => $value, + }; + + default: + return static function ($value) use ($key) { + if ('_header' === $value) { + return 'export.list.acp.' . $key; + } + + if (null === $value) { + return ''; + } + + return $value; + }; + } + } + + public function addSelectClauses(QueryBuilder $qb, \DateTimeImmutable $calcDate): void + { + $qb->addSelect('acp.id AS acpId'); + $qb->addSelect('acp.createdAt AS acpCreatedAt'); + $qb->addSelect('acp.updatedAt AS acpUpdatedAt'); + + // add the regular fields + foreach (['openingDate', 'closingDate', 'confidential', 'emergency', 'intensity'] as $field) { + $qb->addSelect(sprintf('acp.%s AS %s', $field, $field)); + } + + // add the field which are simple association + $qb + ->leftJoin('acp.createdBy', "acp_created_by_t") + ->addSelect('acp_created_by_t.label AS acpCreatedBy'); + $qb + ->leftJoin('acp.updatedBy', "acp_updated_by_t") + ->addSelect('acp_updated_by_t.label AS acpUpdatedBy'); + + foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) { + $qb + ->leftJoin(sprintf('acp.%s', $entity), "{$entity}_t") + ->addSelect(sprintf('%s_t.%s AS %s', $entity, $field, $entity)); + } + + // step at date + $qb + ->addSelect('stepHistory.step AS step') + ->addSelect('stepHistory.startDate AS stepSince') + ->leftJoin('acp.stepHistories', 'stepHistory') + ->andWhere( + $qb->expr()->andX( + $qb->expr()->lte('stepHistory.startDate', ':calcDate'), + $qb->expr()->orX($qb->expr()->isNull('stepHistory.endDate'), $qb->expr()->gt('stepHistory.endDate', ':calcDate')) + ) + ); + + // referree at date + $qb + ->addSelect('referrer_t.label AS referrer') + ->addSelect('userHistory.startDate AS referrerSince') + ->leftJoin('acp.userHistories', 'userHistory') + ->leftJoin('userHistory.user', 'referrer_t') + ->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('userHistory'), + $qb->expr()->andX( + $qb->expr()->lte('userHistory.startDate', ':calcDate'), + $qb->expr()->orX($qb->expr()->isNull('userHistory.endDate'), $qb->expr()->gt('userHistory.endDate', ':calcDate')) + ) + ) + ); + + // location of the acp + $qb + ->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 1 ELSE 0 END AS locationIsPerson') + ->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 0 ELSE 1 END AS locationIsTemp') + ->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonName') + ->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonId') + ->leftJoin('acp.locationHistories', 'locationHistory') + ->andWhere( + $qb->expr()->orX( + $qb->expr()->isNull('locationHistory'), + $qb->expr()->andX( + $qb->expr()->lte('locationHistory.startDate', ':calcDate'), + $qb->expr()->orX($qb->expr()->isNull('locationHistory.endDate'), $qb->expr()->gt('locationHistory.endDate', ':calcDate')) + ) + ) + ) + ->leftJoin( + PersonHouseholdAddress::class, + 'acpPersonAddress', + Join::WITH, + 'locationHistory.personLocation = acpPersonAddress.person AND (acpPersonAddress.validFrom <= :calcDate AND (acpPersonAddress.validTo IS NULL OR acpPersonAddress.validTo > :calcDate))' + ) + ->leftJoin(Address::class, 'acp_address', Join::WITH, 'COALESCE(IDENTITY(locationHistory.addressLocation), IDENTITY(acpPersonAddress.address)) = acp_address.id'); + + $this->addressHelper->addSelectClauses(ExportAddressHelper::F_ALL, $qb, 'acp_address', 'acp_address_fields'); + + // requestor + $qb + ->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 1 ELSE 0 END AS isRequestorPerson') + ->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 0 ELSE 1 END AS isRequestorThirdParty') + ->addSelect('IDENTITY(acp.requestorPerson) AS requestorPersonId') + ->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdPartyId') + ->addSelect('IDENTITY(acp.requestorPerson) AS requestorPerson') + ->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdParty'); + + $qb + // scopes + ->addSelect('(SELECT AGGREGATE(scope.name) FROM ' . Scope::class . ' scope WHERE scope MEMBER OF acp.scopes) AS scopes') + // social issues + ->addSelect('(SELECT AGGREGATE(socialIssue.id) FROM ' . SocialIssue::class . ' socialIssue WHERE socialIssue MEMBER OF acp.socialIssues) AS socialIssues'); + + // add parameter + $qb->setParameter('calcDate', $calcDate); + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/ListPersonHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/ListPersonHelper.php index 77a1d9c86..198794326 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/ListPersonHelper.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/ListPersonHelper.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Helper; +use Chill\MainBundle\Entity\Language; use Chill\MainBundle\Export\Helper\ExportAddressHelper; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CivilityRepositoryInterface; @@ -42,7 +43,7 @@ use function strlen; class ListPersonHelper { public const FIELDS = [ - 'id', + 'personId', 'civility', 'firstName', 'lastName', @@ -114,7 +115,26 @@ class ListPersonHelper } /** - * @param array|value-of[] $fields + * Those keys are the "direct" keys, which are created when we decide to use to list all the keys. + * + * This method must be used in `getKeys` instead of the `self::FIELDS` + * + * @return array + */ + public function getAllKeys(): array + { + return [ + ...array_filter( + ListPersonHelper::FIELDS, + fn (string $key) => !in_array($key, ['address_fields', 'lifecycleUpdate'], true) + ), + ...$this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields'), + ...['createdAt', 'createdBy', 'updatedAt', 'updatedBy'], + ]; + } + + /** + * @param array> $fields */ public function addSelect(QueryBuilder $qb, array $fields, DateTimeImmutable $computedDate): void { @@ -124,6 +144,11 @@ class ListPersonHelper } switch ($f) { + case 'personId': + $qb->addSelect('person.id AS personId'); + + break; + case 'countryOfBirth': case 'nationality': $qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f)); @@ -138,25 +163,7 @@ class ListPersonHelper break; case 'spokenLanguages': - $qb - ->leftJoin('person.spokenLanguages', 'spokenLanguage') - ->addSelect('AGGREGATE(spokenLanguage.id) AS spokenLanguages') - ->addGroupBy('person'); - - if (in_array('center', $fields, true)) { - $qb->addGroupBy('center'); - } - - if (in_array('address_fields', $fields, true)) { - $qb - ->addGroupBy('address_fieldsid') - ->addGroupBy('address_fieldscountry_t.id') - ->addGroupBy('address_fieldspostcode_t.id'); - } - - if (in_array('household_id', $fields, true)) { - $qb->addGroupBy('household_id'); - } + $qb->addSelect('(SELECT AGGREGATE(language.id) FROM ' . Language::class . ' language WHERE language MEMBER OF person.spokenLanguages) AS spokenLanguages'); break; diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml index 64360aee9..43f5556cf 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_person.yaml @@ -4,35 +4,27 @@ services: autowire: true ## Indicators - chill.person.export.count_person: - class: Chill\PersonBundle\Export\Export\CountPerson - autowire: true - autoconfigure: true + Chill\PersonBundle\Export\Export\CountPerson: tags: - { name: chill.export, alias: count_person } - chill.person.export.count_person_with_accompanying_course: - class: Chill\PersonBundle\Export\Export\CountPersonWithAccompanyingCourse - autowire: true - autoconfigure: true + Chill\PersonBundle\Export\Export\CountPersonWithAccompanyingCourse: tags: - { name: chill.export, alias: count_person_with_accompanying_course } Chill\PersonBundle\Export\Export\ListPerson: - autowire: true - autoconfigure: true tags: - { name: chill.export, alias: list_person } - Chill\PersonBundle\Export\Export\ListPersonWithAccompanyingPeriod: - autowire: true - autoconfigure: true + Chill\PersonBundle\Export\Export\ListPersonHavingAccompanyingPeriod: tags: - { name: chill.export, alias: list_person_with_acp } + Chill\PersonBundle\Export\Export\ListPersonWithAccompanyingPeriodDetails: + tags: + - { name: chill.export, alias: list_person_with_acp_details } + Chill\PersonBundle\Export\Export\ListAccompanyingPeriod: - autowire: true - autoconfigure: true tags: - { name: chill.export, alias: list_acp } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 1dbba76d9..809d082b4 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -998,6 +998,8 @@ notification: Notify referrer: Notifier le référent Notify any: Notifier d'autres utilisateurs +personId: Identifiant de l'usager + export: export: acp_stats: @@ -1147,13 +1149,15 @@ export: list: person_with_acp: List peoples having an accompanying period: Liste des usagers ayant un parcours d'accompagnement + List peoples having an accompanying period with period details: Liste des usagers concernés avec détail de chaque parcours Create a list of people having an accompaying periods, according to various filters.: Génère une liste des usagers ayant un parcours d'accompagnement, selon différents critères liés au parcours ou à l'usager + Create a list of people having an accompaying periods with details of period, according to various filters.: Génère une liste des usagers ayant un parcours d'accompagnement, selon différents critères liés au parcours ou à l'usager. Ajoute les détails du parcours à la liste. acp: List of accompanying periods: Liste des parcours d'accompagnements Generate a list of accompanying periods, filtered on different parameters.: Génère une liste des parcours d'accompagnement, filtrée sur différents paramètres. Date of calculation for associated elements: Date de calcul des éléments associés The associated referree, localisation, and other elements will be valid at this date: Les éléments associés, comme la localisation, le référent et d'autres éléments seront valides à cette date - id: Identifiant du parcours + acpId: Identifiant du parcours openingDate: Date d'ouverture du parcours closingDate: Date de fermeture du parcours closingMotive: Motif de cloture @@ -1161,14 +1165,14 @@ export: confidential: Confidentiel emergency: Urgent intensity: Intensité - createdAt: Créé le - updatedAt: Dernière mise à jour le + acpCreatedAt: Créé le + acpUpdatedAt: Dernière mise à jour le acpOrigin: Origine du parcours origin: Origine du parcours acpClosingMotive: Motif de fermeture acpJob: Métier du parcours - createdBy: Créé par - updatedBy: Dernière modification par + acpCreatedBy: Créé par + acpUpdatedBy: Dernière modification par administrativeLocation: Location administrative step: Etape stepSince: Dernière modification de l'étape @@ -1176,7 +1180,7 @@ export: referrerSince: Référent depuis le locationIsPerson: Parcours localisé auprès d'un usager concerné locationIsTemp: Parcours avec une localisation temporaire - acpLocationPersonName: Usager auprès duquel le parcours est localisé + locationPersonName: Usager auprès duquel le parcours est localisé locationPersonId: Identifiant de l'usager auprès duquel le parcours est localisé acpaddress_fieldscountry: Pays de l'adresse isRequestorPerson: Le demandeur est-il un usager ?