From 2096e175d4999be818ca73621e0fbe28c2d7e1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 25 Oct 2022 10:23:02 +0200 Subject: [PATCH] Fixed: [export][list person] use address from household for person list --- exports_alias_conventions.md | 133 +++++----- .../Export/Helper/ExportAddressHelper.php | 247 ++++++++++++++++++ .../config/services/export.yaml | 3 + .../translations/messages.fr.yml | 167 ++++++------ .../ChillPersonBundle/Entity/Person.php | 2 +- .../Export/Export/ListPerson.php | 200 +++++++------- 6 files changed, 515 insertions(+), 237 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php diff --git a/exports_alias_conventions.md b/exports_alias_conventions.md index 62fc745d8..fd7844691 100644 --- a/exports_alias_conventions.md +++ b/exports_alias_conventions.md @@ -5,69 +5,70 @@ Add condition with distinct alias on each export join clauses (Indicators + Filt These are alias conventions : -| Entity | Join | Attribute | Alias | -|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:----------------------------------| -| AccompanyingPeriod::class | | | acp | -| | AccompanyingPeriodWork::class | acp.works | acpw | -| | AccompanyingPeriodParticipation::class | acp.participations | acppart | -| | Location::class | acp.administrativeLocation | acploc | -| | ClosingMotive::class | acp.closingMotive | acpmotive | -| | UserJob::class | acp.job | acpjob | -| | Origin::class | acp.origin | acporigin | -| | Scope::class | acp.scopes | acpscope | -| | SocialIssue::class | acp.socialIssues | acpsocialissue | -| | User::class | acp.user | acpuser | -| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | -| AccompanyingPeriodWork::class | | | acpw | -| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | -| | User::class | acpw.referrers | acpwuser | -| | SocialAction::class | acpw.socialAction | acpwsocialaction | -| | Goal::class | acpw.goals | goal | -| | Result::class | acpw.results | result | -| AccompanyingPeriodParticipation::class | | | acppart | -| | Person::class | acppart.person | partperson | -| AccompanyingPeriodWorkEvaluation::class | | | workeval | -| | Evaluation::class | workeval.evaluation | eval | -| Goal::class | | | goal | -| | Result::class | goal.results | goalresult | -| Person::class | | | person | -| | Center::class | person.center | center | -| | HouseholdMember::class | partperson.householdParticipations | householdmember | -| | MaritalStatus::class | person.maritalStatus | personmarital | -| | VendeePerson::class | | vp | -| | VendeePersonMineur::class | | vpm | -| ResidentialAddress::class | | | resaddr | -| | ThirdParty::class | resaddr.hostThirdParty | tparty | -| ThirdParty::class | | | tparty | -| | ThirdPartyCategory::class | tparty.categories | tpartycat | -| HouseholdMember::class | | | householdmember | -| | Household::class | householdmember.household | household | -| | Person::class | householdmember.person | memberperson | -| | | memberperson.center | membercenter | -| Household::class | | | household | -| | HouseholdComposition::class | household.compositions | composition | -| Activity::class | | | activity | -| | Person::class | activity.person | actperson | -| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | -| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | -| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | -| | ActivityType::class | activity.activityType | acttype | -| | Location::class | activity.location | actloc | -| | SocialAction::class | activity.socialActions | actsocialaction | -| | SocialIssue::class | activity.socialIssues | actsocialssue | -| | ThirdParty::class | activity.thirdParties | acttparty | -| | User::class | activity.user | actuser | -| | User::class | activity.users | actusers | -| | ActivityReason::class | activity.reasons | actreasons | -| | Center::class | actperson.center | actcenter | -| | Person::class | activity.createdBy | actcreator | -| ActivityReason::class | | | actreasons | -| | ActivityReasonCategory::class | actreason.category | actreasoncat | -| Calendar::class | | | cal | -| | CancelReason::class | cal.cancelReason | calcancel | -| | Location::class | cal.location | calloc | -| | User::class | cal.user | caluser | -| VendeePerson::class | | | vp | -| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | -| | StatutLogement::class | vp.statutLogement | vplog | -| | TempsDeTravail::class | vp.tempsDeTravail | vptt | +| Entity | Join | Attribute | Alias | +|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:---------------------------------------| +| AccompanyingPeriod::class | | | acp | +| | AccompanyingPeriodWork::class | acp.works | acpw | +| | AccompanyingPeriodParticipation::class | acp.participations | acppart | +| | Location::class | acp.administrativeLocation | acploc | +| | ClosingMotive::class | acp.closingMotive | acpmotive | +| | UserJob::class | acp.job | acpjob | +| | Origin::class | acp.origin | acporigin | +| | Scope::class | acp.scopes | acpscope | +| | SocialIssue::class | acp.socialIssues | acpsocialissue | +| | User::class | acp.user | acpuser | +| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories | +| AccompanyingPeriodWork::class | | | acpw | +| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval | +| | User::class | acpw.referrers | acpwuser | +| | SocialAction::class | acpw.socialAction | acpwsocialaction | +| | Goal::class | acpw.goals | goal | +| | Result::class | acpw.results | result | +| AccompanyingPeriodParticipation::class | | | acppart | +| | Person::class | acppart.person | partperson | +| AccompanyingPeriodWorkEvaluation::class | | | workeval | +| | Evaluation::class | workeval.evaluation | eval | +| Goal::class | | | goal | +| | Result::class | goal.results | goalresult | +| Person::class | | | person | +| | Center::class | person.center | center | +| | HouseholdMember::class | partperson.householdParticipations | householdmember | +| | MaritalStatus::class | person.maritalStatus | personmarital | +| | VendeePerson::class | | vp | +| | VendeePersonMineur::class | | vpm | +| | CurrentPersonAddress::class | person.currentPersonAddress | currentPersonAddress (on a given date) | +| ResidentialAddress::class | | | resaddr | +| | ThirdParty::class | resaddr.hostThirdParty | tparty | +| ThirdParty::class | | | tparty | +| | ThirdPartyCategory::class | tparty.categories | tpartycat | +| HouseholdMember::class | | | householdmember | +| | Household::class | householdmember.household | household | +| | Person::class | householdmember.person | memberperson | +| | | memberperson.center | membercenter | +| Household::class | | | household | +| | HouseholdComposition::class | household.compositions | composition | +| Activity::class | | | activity | +| | Person::class | activity.person | actperson | +| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp | +| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity | +| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity | +| | ActivityType::class | activity.activityType | acttype | +| | Location::class | activity.location | actloc | +| | SocialAction::class | activity.socialActions | actsocialaction | +| | SocialIssue::class | activity.socialIssues | actsocialssue | +| | ThirdParty::class | activity.thirdParties | acttparty | +| | User::class | activity.user | actuser | +| | User::class | activity.users | actusers | +| | ActivityReason::class | activity.reasons | actreasons | +| | Center::class | actperson.center | actcenter | +| | Person::class | activity.createdBy | actcreator | +| ActivityReason::class | | | actreasons | +| | ActivityReasonCategory::class | actreason.category | actreasoncat | +| Calendar::class | | | cal | +| | CancelReason::class | cal.cancelReason | calcancel | +| | Location::class | cal.location | calloc | +| | User::class | cal.user | caluser | +| VendeePerson::class | | | vp | +| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof | +| | StatutLogement::class | vp.statutLogement | vplog | +| | TempsDeTravail::class | vp.tempsDeTravail | vptt | diff --git a/src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php new file mode 100644 index 000000000..09aaa7a1d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php @@ -0,0 +1,247 @@ + self::COUNTRY, + 'postal_code' => self::POSTAL_CODE, + 'street' => self::STREET, + 'building' => self::BUILDING, + 'string' => self::STRING, + 'geom' => self::GEOM, + 'attributes' => self::ATTRIBUTES, + ]; + + private const COLUMN_MAPPING = [ + 'country' => ['country'], + 'postal_code' => ['postcode_code', 'postcode_name'], + 'street' => ['street', 'streetNumber'], + 'building' => ['buildingName', 'corridor', 'distribution', 'extra', 'flat', 'floor'], + 'string' => ['_as_string'], + 'attributes' => ['isNoAddress', 'confidential', 'id'], + 'geom' => ['_lat', '_lon'], + ]; + + private AddressRender $addressRender; + + private AddressRepository $addressRepository; + + private PropertyAccessor $propertyAccess; + + private TranslatableStringHelperInterface $translatableStringHelper; + + public function __construct( + AddressRepository $addressRepository, + TranslatableStringHelperInterface $translatableStringHelper, + AddressRender $addressRender + ) { + $this->addressRepository = $addressRepository; + $this->propertyAccess = PropertyAccess::createPropertyAccessor(); + $this->translatableStringHelper = $translatableStringHelper; + $this->addressRender = $addressRender; + } + + /** + * @return array|string[] + */ + public function getKeys(int $params, string $prefix = ''): array + { + $prefixes = []; + + foreach (self::ALL as $key => $bitmask) { + if (($params & $bitmask) === $bitmask) { + $prefixes = array_merge( + $prefixes, + array_map( + static function ($item) use ($prefix) { return $prefix . $item; }, + self::COLUMN_MAPPING[$key] + ) + ); + } + } + + return $prefixes; + } + + public function getLabel($key, array $values, $data, string $prefix = '', string $translationPrefix = 'export.address_helper.'): callable + { + $sanitizedKey = substr($key, strlen($prefix)); + + switch ($sanitizedKey) { + case 'id': + case 'street': + case 'streetNumber': + case 'buildingName': + case 'corridor': + case 'distribution': + case 'extra': + case 'flat': + case 'floor': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + + return $this->propertyAccess->getValue($address, $sanitizedKey); + }; + + case '_lat': + case '_lon': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + $geom = $address->getPoint(); + + if (null === $geom) { + return ''; + } + + switch ($sanitizedKey) { + case '_lat': + return $geom->getLat(); + + case '_lon': + return $geom->getLon(); + + default: + throw new LogicException('only _lat or _lon accepted, given: ' . $sanitizedKey); + } + }; + + case 'isNoAddress': + case 'confidential': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + + switch ($val = $this->propertyAccess->getValue($address, $sanitizedKey)) { + case null: + return ''; + + case true: + return 1; + + case false: + return 0; + + default: + throw new LogicException('this value is not supported for ' . $sanitizedKey . ': ' . $val); + } + }; + + case 'country': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + + return $this->translatableStringHelper->localize($address->getPostcode()->getCountry()->getName()); + }; + + case '_as_string': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + + return $this->addressRender->renderString($address, []); + }; + + case 'postcode_code': + case 'postcode_name': + return function ($value) use ($sanitizedKey, $translationPrefix) { + if ('_header' === $value) { + return $translationPrefix . $sanitizedKey; + } + + if (null === $value) { + return ''; + } + + $address = $this->addressRepository->find($value); + + switch ($sanitizedKey) { + case 'postcode_code': + return $address->getPostcode()->getCode(); + + case 'postcode_name': + return $address->getPostcode()->getName(); + + default: + throw new LogicException('this key is not supported: ' . $sanitizedKey); + } + }; + + default: + throw new LogicException('this key is not supported: ' . $sanitizedKey); + } + } +} diff --git a/src/Bundle/ChillMainBundle/config/services/export.yaml b/src/Bundle/ChillMainBundle/config/services/export.yaml index d330f30f5..ea7328839 100644 --- a/src/Bundle/ChillMainBundle/config/services/export.yaml +++ b/src/Bundle/ChillMainBundle/config/services/export.yaml @@ -3,6 +3,9 @@ services: autowire: true autoconfigure: true + Chill\MainBundle\Export\Helper\: + resource: '../../Export/Helper' + chill.main.export_element_validator: class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator tags: diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index b4ce910c4..01fcf0b8c 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -79,17 +79,17 @@ Postal code: Code postal Valid from: Valide à partir du Choose a postal code: Choisir un code postal address: - address_homeless: L'adresse est-elle celle d'un domicile fixe ? - real address: Adresse d'un domicile - consider homeless: Cette adresse est incomplète + address_homeless: L'adresse est-elle celle d'un domicile fixe ? + real address: Adresse d'un domicile + consider homeless: Cette adresse est incomplète address more: - floor: ét - corridor: coul - steps: esc - flat: appart - buildingName: résidence - extra: "" - distribution: cedex + floor: ét + corridor: coul + steps: esc + flat: appart + buildingName: résidence + extra: "" + distribution: cedex Create a new address: Créer une nouvelle adresse Create an address: Créer une adresse Update address: Modifier l'adresse @@ -125,7 +125,7 @@ Location and location type: Localisations et types de localisation Back to the admin: Menu d'administration "Administration interface": Interface d'administration Welcome to the admin section !: > - Bienvenue dans l'interface d'administration ! + Bienvenue dans l'interface d'administration ! #permissions Permissions Menu: Gestion des droits @@ -334,69 +334,69 @@ Impersonate: Incarner l'utilisateur Impersonate mode: Mode fantôme crud: - # general items - new: - button_action_form: Créer - link_edit: Modifier - save_and_close: Créer & fermer - save_and_show: Créer & voir - save_and_new: Créer & nouveau - success: Les données ont été créées - edit: - button_action_form: Enregistrer - back_to_view: Voir - save_and_close: Enregistrer & fermer - save_and_show: Enregistrer & voir - success: Les données ont été modifiées - delete: - success: Les données ont été supprimées - link_to_form: Supprimer - default: - success: Les données ont été enregistrées - view: - link_duplicate: Dupliquer - admin_user: - index: - title: Utilisateurs - add_new: Créer - title_edit: Modifier un utilisateur - title_new: Créer un utilisateur - admin_user_job: - index: - title: Métiers - add_new: Créer - title_new: Nouveau métier - title_edit: Modifier un métier - main_location_type: - index: - title: Liste des types de localisations - add_new: Ajouter un type de localisation - title_new: Nouveau type de localisation - title_edit: Modifier un type de localisation - main_location: - index: - title: Liste des localisations - add_new: Ajouter une localisation - title_new: Nouvelle localisation - title_edit: Modifier une localisation - main_language: - index: - title: Liste des langues - add_new: Ajouter une langue - title_new: Nouvelle langue - title_edit: Modifier une langue - main_country: - index: - title: Liste des pays - add_new: Ajouter un pays - title_new: Nouveau pays - title_edit: Modifier un pays - main_civility: - index: - title: Liste des civilités - add_new: Ajouter une civilité - title_new: Nouvelle civilité - title_edit: Modifier une civilité + # general items + new: + button_action_form: Créer + link_edit: Modifier + save_and_close: Créer & fermer + save_and_show: Créer & voir + save_and_new: Créer & nouveau + success: Les données ont été créées + edit: + button_action_form: Enregistrer + back_to_view: Voir + save_and_close: Enregistrer & fermer + save_and_show: Enregistrer & voir + success: Les données ont été modifiées + delete: + success: Les données ont été supprimées + link_to_form: Supprimer + default: + success: Les données ont été enregistrées + view: + link_duplicate: Dupliquer + admin_user: + index: + title: Utilisateurs + add_new: Créer + title_edit: Modifier un utilisateur + title_new: Créer un utilisateur + admin_user_job: + index: + title: Métiers + add_new: Créer + title_new: Nouveau métier + title_edit: Modifier un métier + main_location_type: + index: + title: Liste des types de localisations + add_new: Ajouter un type de localisation + title_new: Nouveau type de localisation + title_edit: Modifier un type de localisation + main_location: + index: + title: Liste des localisations + add_new: Ajouter une localisation + title_new: Nouvelle localisation + title_edit: Modifier une localisation + main_language: + index: + title: Liste des langues + add_new: Ajouter une langue + title_new: Nouvelle langue + title_edit: Modifier une langue + main_country: + index: + title: Liste des pays + add_new: Ajouter un pays + title_new: Nouveau pays + title_edit: Modifier un pays + main_civility: + index: + title: Liste des civilités + add_new: Ajouter une civilité + title_new: Nouvelle civilité + title_edit: Modifier une civilité No entities: Aucun élément @@ -515,3 +515,22 @@ notification: Remove an email: Supprimer l'adresse email Email with access link: Adresse email ayant reçu un lien d'accès +export: + address_helper: + id: Identifiant de l'adresse + street: Voie + streetNumber: Numéro de voie + buildingName: Résidence + corridor: Couloir + distribution: Distribution + extra: Extra + flat: Appartement + floor: Étage + postcode_code: Code postal + postcode_name: Libellé du code postal + country: Pays + _as_string: Adresse formattée + confidential: Adresse confidentielle ? + isNoAddress: Adresse incomplète ? + _lat: Latitude + _lon: Longitude diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 1134674df..a477f9adb 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -1768,7 +1768,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI } /** - * @param type $spokenLanguages + * @param Collection $spokenLanguages */ public function setSpokenLanguages($spokenLanguages): self { diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php index 5f5d95034..e799a53e4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php @@ -17,28 +17,33 @@ use Chill\CustomFieldsBundle\Service\CustomFieldProvider; use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; +use Chill\MainBundle\Export\Helper\ExportAddressHelper; use Chill\MainBundle\Export\ListInterface; +use Chill\MainBundle\Form\Type\ChillDateType; +use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Security\Authorization\PersonVoter; use DateTime; +use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query; use Exception; +use PhpOffice\PhpSpreadsheet\Shared\Date; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use function addcslashes; use function array_key_exists; use function array_keys; use function array_merge; use function count; use function in_array; +use function strlen; use function strtolower; use function uniqid; @@ -47,31 +52,40 @@ use function uniqid; */ class ListPerson implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface { - protected CustomFieldProvider $customFieldProvider; - - protected EntityManagerInterface $entityManager; - - protected array $fields = [ - 'id', 'firstName', 'lastName', 'birthdate', + public const FIELDS = [ + 'id', + 'firstName', + 'lastName', + 'birthdate', 'placeOfBirth', 'gender', 'memo', 'email', 'phonenumber', 'mobilenumber', 'contactInfo', 'countryOfBirth', 'nationality', - 'address_street_address_1', 'address_street_address_2', - 'address_valid_from', 'address_postcode_label', 'address_postcode_code', - 'address_country_name', 'address_country_code', 'address_isnoaddress', + 'address', ]; - protected TranslatableStringHelper $translatableStringHelper; + private ExportAddressHelper $addressHelper; - protected TranslatorInterface $translator; + private CountryRepository $countryRepository; + + private CustomFieldProvider $customFieldProvider; + + private EntityManagerInterface $entityManager; private $slugs = []; + private TranslatableStringHelper $translatableStringHelper; + + private TranslatorInterface $translator; + public function __construct( + CountryRepository $countryRepository, + ExportAddressHelper $addressHelper, EntityManagerInterface $em, TranslatorInterface $translator, TranslatableStringHelper $translatableStringHelper, CustomFieldProvider $customFieldProvider ) { + $this->addressHelper = $addressHelper; + $this->countryRepository = $countryRepository; $this->entityManager = $em; $this->translator = $translator; $this->translatableStringHelper = $translatableStringHelper; @@ -80,7 +94,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou public function buildForm(FormBuilderInterface $builder) { - $choices = array_combine($this->fields, $this->fields); + $choices = array_combine(self::FIELDS, self::FIELDS); foreach ($this->getCustomFields() as $cf) { $choices[$this->translatableStringHelper->localize($cf->getName())] @@ -96,7 +110,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou 'label' => 'Fields to include in export', 'choice_attr' => static function (string $val): array { // add a 'data-display-target' for address fields - if (substr($val, 0, 8) === 'address_') { + if (substr($val, 0, 7) === 'address') { return ['data-display-target' => 'address_date']; } @@ -111,17 +125,14 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou } }, ])], + 'data' => array_values($choices), ]); // add a date field for addresses - $builder->add('address_date', DateType::class, [ + $builder->add('address_date', ChillDateType::class, [ 'label' => 'Address valid at this date', - 'data' => new DateTime(), - 'attr' => ['class' => 'datepicker'], - 'widget' => 'single_text', - 'format' => 'dd-MM-yyyy', - 'required' => false, - 'block_name' => 'list_export_form_address_date', + 'data' => new DateTimeImmutable(), + 'input' => 'datetime_immutable', ]); } @@ -142,6 +153,10 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou public function getLabels($key, array $values, $data) { + if (substr($key, 0, strlen('address')) === 'address') { + return $this->addressHelper->getLabel($key, $values, $data, 'address_'); + } + switch ($key) { case 'birthdate': // for birthdate, we have to transform the string into a date @@ -151,10 +166,11 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou return 'birthdate'; } - if (empty($value)) { + if (null === $value) { return ''; } + // warning: won't work with DateTimeImmutable as we reset time a few lines later $date = DateTime::createFromFormat('Y-m-d', $value); // check that the creation could occurs. if (false === $date) { @@ -162,7 +178,9 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou . 'not be converted to %s', $value, DateTime::class)); } - return $date->format('d-m-Y'); + $date->setTime(0, 0, 0); + + return $date; }; case 'gender': @@ -177,29 +195,6 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou case 'countryOfBirth': case 'nationality': - $countryRepository = $this->entityManager - ->getRepository(\Chill\MainBundle\Entity\Country::class); - - // load all countries in a single query - $countryRepository->findBy(['countryCode' => $values]); - - return function ($value) use ($key, $countryRepository) { - if ('_header' === $value) { - return strtolower($key); - } - - if (null === $value) { - return $this->translator->trans('no data'); - } - - $country = $countryRepository->find($value); - - return $this->translatableStringHelper->localize( - $country->getName() - ); - }; - - case 'address_country_name': return function ($value) use ($key) { if ('_header' === $value) { return strtolower($key); @@ -209,25 +204,16 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou return ''; } - return $this->translatableStringHelper->localize(json_decode($value, true)); - }; + $country = $this->countryRepository->find($value); - case 'address_isnoaddress': - return static function (?string $value): string { - if ('_header' === $value) { - return 'address.address_homeless'; - } - - if (null !== $value) { - return 'X'; - } - - return ''; + return $this->translatableStringHelper->localize( + $country->getName() + ); }; default: // for fields which are associated with person - if (in_array($key, $this->fields, true)) { + if (in_array($key, self::FIELDS, true)) { return static function ($value) use ($key) { if ('_header' === $value) { return strtolower($key); @@ -246,9 +232,13 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou $fields = []; foreach ($data['fields'] as $key) { - if (in_array($key, $this->fields, true)) { - $fields[] = $key; + if (substr($key, 0, strlen('address')) === 'address') { + $fields = array_merge($fields, $this->addressHelper->getKeys(0b01111111, 'address_')); + + continue; } + + $fields[] = $key; } // add the key from slugs and return @@ -270,6 +260,9 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou 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 function ($el) { @@ -284,36 +277,57 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou $qb = $this->entityManager->createQueryBuilder(); - foreach ($this->fields as $f) { - if (in_array($f, $data['fields'], true)) { - switch ($f) { - case 'countryOfBirth': - case 'nationality': - $qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f)); + $qb + ->from(Person::class, 'person') + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . Person\PersonCenterHistory::class . ' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)' + ) + ) + ->setParameter('authorized_centers', $centers); - break; + foreach (self::FIELDS as $f) { + if (!in_array($f, $data['fields'], true)) { + continue; + } - case 'address_street_address_1': - case 'address_street_address_2': - case 'address_valid_from': - case 'address_postcode_label': - case 'address_postcode_code': - case 'address_country_name': - case 'address_country_code': - case 'address_isnoaddress': - $qb->addSelect(sprintf( - 'GET_PERSON_ADDRESS_%s(person.id, :address_date) AS %s', - // get the part after address_ - strtoupper(substr($f, 8)), - $f - )); - $qb->setParameter('address_date', $data['address_date']); + switch ($f) { + case 'countryOfBirth': + case 'nationality': + $qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f)); - break; + break; - default: - $qb->addSelect(sprintf('person.%s as %s', $f, $f)); - } + case 'address': + foreach ($this->addressHelper->getKeys(0b01111111, 'address_') as $key) { + $qb + ->addSelect(sprintf('IDENTITY(currentPersonAddress.address) AS %s', $key)); + } + + if (!(in_array('currentPersonAddress', $qb->getAllAliases(), true))) { + $qb + ->leftJoin('person.currentPersonAddress', 'currentPersonAddress') + ->andWhere( + $qb->expr()->orX( + // no address at this time + $qb->expr()->isNull('currentPersonAddress'), + // there is one address... + $qb->expr()->andX( + $qb->expr()->lte('currentPersonAddress.validFrom', ':address_date'), + $qb->expr()->orX( + $qb->expr()->isNull('currentPersonAddress.validTo'), + $qb->expr()->gt('currentPersonAddress.validTo', ':address_date') + ) + ) + ) + ) + ->setParameter('address_date', $data['address_date']); + } + + break; + + default: + $qb->addSelect(sprintf('person.%s as %s', $f, $f)); } } @@ -345,12 +359,6 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou } } - $qb - ->from('ChillPersonBundle:Person', 'person') - ->join('person.center', 'center') - ->andWhere('center IN (:authorized_centers)') - ->setParameter('authorized_centers', $centers); - return $qb; } @@ -368,7 +376,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou { // get the field starting with address_ $addressFields = array_filter( - $this->fields, + self::FIELDS, static fn (string $el): bool => substr($el, 0, 8) === 'address_' );