diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php new file mode 100644 index 000000000..bf561705b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php @@ -0,0 +1,201 @@ +entityManager = $em; + $this->translator = $translator; + $this->router = $router; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getTitle() + { + return "List duplicates"; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getDescription() + { + return "Create a list of duplicate people."; + } + + /** + * {@inheritDoc} + * + * @param FormBuilderInterface $builder + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('precision', NumberType::class, [ + 'label' => 'Duplicate similarity', + 'data' => self::PRECISION_DEFAULT_VALUE, + 'constraints' => [ + new Range([ + 'min' => 0.1, + 'max' => 1.0, + ]) + ] + ]); + } + + public function validateForm($data, ExecutionContextInterface $context) + { + + } + + public function generate(array $acl, array $data = []): Response + { + $values = []; + $values[] = $this->getHeaders(); + + $result = $this->getResult($acl, $data); + foreach ($result as $row) { + $values[] = [ + $row['id1'], + $row['firstname1'], + $row['lastname1'], + $this->router->generate('chill_person_view', ['person_id' => $row['id1']], UrlGeneratorInterface::ABSOLUTE_URL), + $row['id2'], + $row['firstname2'], + $row['lastname2'], + $this->router->generate('chill_person_view', ['person_id' => $row['id2']], UrlGeneratorInterface::ABSOLUTE_URL), + ]; + } + + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->fromArray($values); + + // Make links clickable + for ($i = 1; $i <= $spreadsheet->getActiveSheet()->getHighestDataRow(); $i++) { + $spreadsheet->getActiveSheet()->getCell('D'.$i)->getHyperlink() + ->setUrl($spreadsheet->getActiveSheet()->getCell('D'.$i)->getValue()); + $spreadsheet->getActiveSheet()->getCell('H'.$i)->getHyperlink() + ->setUrl($spreadsheet->getActiveSheet()->getCell('H'.$i)->getValue()); + } + + $writer = new Xlsx($spreadsheet); + $temp_file = sys_get_temp_dir().'/'.uniqid('export_').'.xlsx'; + $writer->save($temp_file); + + $response = new BinaryFileResponse($temp_file); + $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'export_duplicate.xlsx'); + + return $response; + } + + protected function getResult($acl, $data = []) + { + $precision = $data['precision'] ?? self::PRECISION_DEFAULT_VALUE; + + $baseSql = 'SELECT + p.id as id1, p.firstname as firstname1, p.lastname as lastname1, p.fullnamecanonical as fullnamecanonical1, + p2.id as id2, p2.firstname as firstname2, p2.lastname as lastname2, p2.fullnamecanonical as fullnamecanonical2, + SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) AS "similarity nom + prenom", + SIMILARITY(p.lastname, p2.lastname) AS "similarity nom", + SIMILARITY(p.firstname, p2.firstname) AS "similarity prenom" + FROM chill_person_person AS p + JOIN chill_person_person AS p2 + ON p.id < p2.id + AND ( + SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) > ? + OR (UNACCENT(LOWER(p.firstname)) = UNACCENT(LOWER(p2.lastname)) + AND UNACCENT(LOWER(p.lastname)) = UNACCENT(LOWER(p2.firstname))) + ) + AND p.center_id = p2.center_id + WHERE NOT EXISTS ( + SELECT id + FROM chill_person_not_duplicate as pnd + WHERE (pnd.person1_id = p.id + AND pnd.person2_id = p2.id) + OR (pnd.person2_id = p.id + AND pnd.person1_id = p2.id) + ) + AND p.center_id IN (%centers%) + ORDER BY p.fullnamecanonical, p.id, p2.id'; + + $centersId = array_map(function ($r) { return $r['center']->getId(); }, $acl); + $sql = strtr($baseSql, ['%centers%' => implode(', ', array_fill(0, count($centersId), '?'))]); + + $statement = $this->entityManager->getConnection()->prepare($sql); + return $statement->executeQuery([$precision, ...$centersId]); + } + + protected function getHeaders(): array + { + return [ + $this->translator->trans('Person folder number'), + $this->translator->trans('Last name'), + $this->translator->trans('First name'), + $this->translator->trans('Open person folder'), + $this->translator->trans('Person folder number'), + $this->translator->trans('Last name'), + $this->translator->trans('First name'), + $this->translator->trans('Open person folder'), + ]; + } + + public function requiredRole(): Role + { + return new Role(PersonVoter::DUPLICATE); + } +} diff --git a/src/Bundle/ChillPersonBundle/Resources/config/services/exports.yml b/src/Bundle/ChillPersonBundle/Resources/config/services/exports.yml index 9a20898e6..b87bdeaaa 100644 --- a/src/Bundle/ChillPersonBundle/Resources/config/services/exports.yml +++ b/src/Bundle/ChillPersonBundle/Resources/config/services/exports.yml @@ -15,7 +15,16 @@ services: - "@chill.custom_field.provider" tags: - { name: chill.export, alias: list_person } - + + chill.person.export.list_person.duplicate: + class: Chill\PersonBundle\Export\Export\ListPersonDuplicate + arguments: + - "@doctrine.orm.entity_manager" + - "@translator" + - "@router" + tags: + - { name: chill.export, alias: list_person_duplicate } + chill.person.export.filter_gender: class: Chill\PersonBundle\Export\Filter\GenderFilter arguments: diff --git a/src/Bundle/ChillPersonBundle/Resources/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/Resources/translations/messages.fr.yml index 0aa974e1e..b12e2f41a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/Resources/translations/messages.fr.yml @@ -208,6 +208,12 @@ List peoples: Liste des personnes Create a list of people according to various filters.: Crée une liste des personnes selon différents filtres. Fields to include in export: Champs à inclure dans l'export Address valid at this date: Addresse valide à cette date +### export duplicate +List duplicates: Liste des doublons potentiels +Create a list of duplicate people.: Liste des doublons potentiels, sur base du degré de similarité du nom et prénom des personnes +Person folder number: Identifiant du dossier +Open person folder: Ouvrir le dossier +Duplicate similarity: Degré de similarité des noms et prénoms ## filters Filter by person gender: Filtrer par genre de la personne