diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php
new file mode 100644
index 000000000..4438bde8d
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php
@@ -0,0 +1,141 @@
+similarPersonMatcher = $similarPersonMatcher;
+ $this->translator = $translator;
+ $this->personRepository = $personRepository;
+ }
+
+ public function viewAction($person_id)
+ {
+ $person = $this->_getPerson($person_id);
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found on this server");
+ }
+
+ $duplicatePersons = $this->similarPersonMatcher->matchPerson($person, 0.5);
+
+ return $this->render('ChillPersonBundle:PersonDuplicate:view.html.twig', [
+ "person" => $person,
+ 'duplicatePersons' => $duplicatePersons
+ ]);
+ }
+
+ public function confirmAction($person_id, $person2_id, Request $request)
+ {
+ if ($person_id === $person2_id) {
+ throw new InvalidArgumentException('Can not merge same person');
+ }
+
+ if ($person_id > $person2_id) {
+ $tmpId = $person2_id;
+ $person2_id = $person_id;
+ $person_id = $tmpId;
+ unset($tmpId);
+ }
+
+ $person = $this->_getPerson($person_id);
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found on this server");
+ }
+
+ $person2 = $this->_getPerson($person2_id);
+ if ($person2 === null) {
+ throw $this->createNotFoundException("Person with id $person2_id not"
+ . " found on this server");
+ }
+
+ $form = $this->createForm(PersonConfimDuplicateType::class);
+
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ dd('todo');
+ }
+
+ return $this->render('ChillPersonBundle:PersonDuplicate:confirm.html.twig', [
+ 'person' => $person,
+ 'person2' => $person2,
+ 'form' => $form->createView(),
+ ]);
+ }
+
+ public function notDuplicateAction($person_id, $person2_id)
+ {
+ if ($person_id === $person2_id) {
+ throw new InvalidArgumentException('Can not merge same person');
+ }
+
+ if ($person_id > $person2_id) {
+ $tmpId = $person2_id;
+ $person2_id = $person_id;
+ $person_id = $tmpId;
+ unset($tmpId);
+ }
+
+ $person = $this->_getPerson($person_id);
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found on this server");
+ }
+
+ $person2 = $this->_getPerson($person2_id);
+ if ($person2 === null) {
+ throw $this->createNotFoundException("Person with id $person2_id not"
+ . " found on this server");
+ }
+
+ $personNotDuplicate = new PersonNotDuplicate();
+ $personNotDuplicate->setPerson1($person);
+ $personNotDuplicate->setPerson2($person2);
+ $personNotDuplicate->setUser($this->getUser());
+
+ $this->getDoctrine()->getManager()->persist($personNotDuplicate);
+ $this->getDoctrine()->getManager()->flush();
+
+ return $this->redirectToRoute('chill_person_duplicate_view', ['person_id' => $person->getId()]);
+ }
+
+ /**
+ * easy getting a person by his id
+ */
+ private function _getPerson($id): ?Person
+ {
+ return $this->personRepository->find($id);
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php
new file mode 100644
index 000000000..e8b79698f
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php
@@ -0,0 +1,107 @@
+date = new \DateTime();
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setId($id)
+ {
+ $this->id = $id;
+ }
+
+ public function getPerson1()
+ {
+ return $this->person1;
+ }
+
+ public function setPerson1(Person $person1)
+ {
+ $this->person1 = $person1;
+ }
+
+ public function getPerson2()
+ {
+ return $this->person2;
+ }
+
+ public function setPerson2(Person $person2)
+ {
+ $this->person2 = $person2;
+ }
+
+ public function getDate()
+ {
+ return $this->date;
+ }
+
+ public function setDate(\DateTime $date)
+ {
+ $this->date = $date;
+ }
+
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php b/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php
new file mode 100644
index 000000000..acc385f43
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Form/PersonConfimDuplicateType.php
@@ -0,0 +1,31 @@
+add('confirm', CheckboxType::class, [
+ 'label' => 'Je confirme la fusion de ces 2 personnes',
+ 'mapped' => false,
+ ]);
+ }
+
+ /**
+ * @return string
+ */
+ public function getBlockPrefix()
+ {
+ return 'chill_personbundle_person_confirm_duplicate';
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php
index 3a757d898..b39b14254 100644
--- a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php
+++ b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php
@@ -63,6 +63,16 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
->setExtras([
'order' => 50
]);
+
+ $menu->addChild($this->translator->trans('Person duplicate'), [
+ 'route' => 'chill_person_duplicate_view',
+ 'routeParameters' => [
+ 'person_id' => $parameters['person']->getId()
+ ]
+ ])
+ ->setExtras([
+ 'order' => 51
+ ]);
if ($this->showAccompanyingPeriod === 'visible') {
$menu->addChild($this->translator->trans('Accompanying period list'), [
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonDuplicate/confirm.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonDuplicate/confirm.html.twig
new file mode 100644
index 000000000..fcd314a62
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonDuplicate/confirm.html.twig
@@ -0,0 +1,36 @@
+{% extends "@ChillPerson/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_person_duplicate' %}
+
+{% block title %}{{ 'Person duplicate'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~
+' ' ~ person.lastName }}{% endblock %}
+
+{% block personcontent %}
+
Ancien dossier
+ {{ person2 }}
+
+
+
+ Nouveau Dossier
+ {{ person }}
+
+
+ {{ form_start(form) }}
+
+ {{ form_rest(form) }}
+
+
+
+ {{ form_end(form) }}
+
+{% endblock %}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonDuplicate/view.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonDuplicate/view.html.twig
new file mode 100644
index 000000000..691f55e1c
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonDuplicate/view.html.twig
@@ -0,0 +1,65 @@
+{% extends "@ChillPerson/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_person_duplicate' %}
+
+{% block title %}{{ 'Person duplicate'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~
+ ' ' ~ person.lastName }}{% endblock %}
+
+
+{% block personcontent %}
+
+{{ title|default('Person duplicate')|trans }}
+
+
+{% endblock %}
diff --git a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php
index ab13e607a..4b22bd656 100644
--- a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php
+++ b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php
@@ -61,35 +61,32 @@ class SimilarPersonMatcher
}
- public function matchPerson(Person $person)
+ public function matchPerson(Person $person, $precision = 0.15)
{
- $centers = $this->authorizationHelper
- ->getReachableCenters(
- $this->tokenStorage->getToken()->getUser(),
- new Role(PersonVoter::SEE)
- );
+ $centers = $this->authorizationHelper->getReachableCenters(
+ $this->tokenStorage->getToken()->getUser(),
+ new Role(PersonVoter::SEE)
+ );
- $dql = 'SELECT p from ChillPersonBundle:Person p WHERE'
- . ' ('
- . ' UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:firstName)) '
- . ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:lastName)) '
- . ' OR UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:lastName)) '
- . ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:firstName)) '
- . ' OR SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= 0.15 '
+ $dql = 'SELECT p from ChillPersonBundle:Person p '
+ . ' WHERE ('
+ . ' SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= :precision '
+ . ' OR SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullNameInverted))) >= :precision '
. ' ) '
. ' AND p.center IN (:centers)'
+ . ' AND p.id != :personId'
. ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC '
- ;
+ ;
- $query =
- $this->em
+ $query = $this->em
->createQuery($dql)
- ->setParameter('firstName', $person->getFirstName())
- ->setParameter('lastName', $person->getLastName())
->setParameter('fullName', $person->getFirstName() . ' ' . $person->getLastName())
+ ->setParameter('fullNameInverted', $person->getLastName() . ' ' . $person->getFirstName())
->setParameter('centers', $centers)
- ;
-
+ ->setParameter('personId', $person->getId())
+ ->setParameter('precision', $precision)
+ ;
+
return $query->getResult();
}
}
diff --git a/src/Bundle/ChillPersonBundle/config/routes.yaml b/src/Bundle/ChillPersonBundle/config/routes.yaml
index a9049840d..9db6ec12f 100644
--- a/src/Bundle/ChillPersonBundle/config/routes.yaml
+++ b/src/Bundle/ChillPersonBundle/config/routes.yaml
@@ -98,6 +98,10 @@ chill_person_admin_redirect_to_admin_index:
order: 0
label: Main admin menu
+chill_person_duplicate_view:
+ path: /{_locale}/person/{person_id}/duplicate/view
+ controller: Chill\PersonBundle\Controller\PersonDuplicateController::viewAction
+
chill_person_closingmotive_admin:
path: /{_locale}/admin/closing-motive
controller: cscrud_closing_motive_controller:index
@@ -107,6 +111,10 @@ chill_person_closingmotive_admin:
order: 90
label: 'person_admin.closing motives'
+chill_person_duplicate_confirm:
+ path: /{_locale}/person/{person_id}/duplicate/{person2_id}/confirm
+ controller: Chill\PersonBundle\Controller\PersonDuplicateController::confirmAction
+
chill_person_maritalstatus_admin:
path: /{_locale}/admin/marital-status
controller: cscrud_marital_status_controller:index
@@ -114,4 +122,8 @@ chill_person_maritalstatus_admin:
menus:
admin_person:
order: 120
- label: 'person_admin.marital status'
\ No newline at end of file
+ label: 'person_admin.marital status'
+
+chill_person_duplicate_not_duplicate:
+ path: /{_locale}/person/{person_id}/duplicate/{person2_id}/not-duplicate
+ controller: Chill\PersonBundle\Controller\PersonDuplicateController::notDuplicateAction
diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml
index 112d609b5..a6e446ebf 100644
--- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml
@@ -20,5 +20,12 @@ services:
arguments:
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
tags: ['controller.service_arguments']
-
+
Chill\PersonBundle\Controller\AdminController: ~
+
+ Chill\PersonBundle\Controller\PersonDuplicateController:
+ arguments:
+ $similarPersonMatcher: '@Chill\PersonBundle\Search\SimilarPersonMatcher'
+ $translator: '@Symfony\Component\Translation\TranslatorInterface'
+ $personRepository: '@Chill\PersonBundle\Repository\PersonRepository'
+ tags: ['controller.service_arguments']
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210128152747.php b/src/Bundle/ChillPersonBundle/migrations/Version20210128152747.php
new file mode 100644
index 000000000..8dbcb91da
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20210128152747.php
@@ -0,0 +1,39 @@
+addSql('CREATE SEQUENCE chill_person_not_duplicate_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
+ $this->addSql('CREATE TABLE chill_person_not_duplicate (id INT NOT NULL, person1_id INT DEFAULT NULL, person2_id INT DEFAULT NULL, user_id INT DEFAULT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX IDX_BD211EE23EF5821B ON chill_person_not_duplicate (person1_id)');
+ $this->addSql('CREATE INDEX IDX_BD211EE22C402DF5 ON chill_person_not_duplicate (person2_id)');
+ $this->addSql('CREATE INDEX IDX_BD211EE2A76ED395 ON chill_person_not_duplicate (user_id)');
+ $this->addSql('ALTER TABLE chill_person_not_duplicate ADD CONSTRAINT FK_BD211EE23EF5821B FOREIGN KEY (person1_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE chill_person_not_duplicate ADD CONSTRAINT FK_BD211EE22C402DF5 FOREIGN KEY (person2_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('ALTER TABLE chill_person_not_duplicate ADD CONSTRAINT FK_BD211EE2A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ }
+
+ public function down(Schema $schema) : void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql('DROP SEQUENCE chill_person_not_duplicate_id_seq CASCADE');
+ $this->addSql('DROP TABLE chill_person_not_duplicate');
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/translations/messages.en.yml b/src/Bundle/ChillPersonBundle/translations/messages.en.yml
index 4c0c13d83..cd9212d5d 100644
--- a/src/Bundle/ChillPersonBundle/translations/messages.en.yml
+++ b/src/Bundle/ChillPersonBundle/translations/messages.en.yml
@@ -66,6 +66,7 @@ Reset: 'Remise à zéro'
'Create accompanying period': 'Create accompanying period'
'Closing motive': 'Motif de clôture'
'Person details': 'Détails de la personne'
+'Person duplicate': 'Find duplicate'
Create an accompanying period: Create an accompanying period
'Create': Create
diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
index 08e1219e6..354625f05 100644
--- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
@@ -119,6 +119,7 @@ Update accompanying period: Mettre à jour une période d'accompagnement
"Period not opened : form is invalid": "La période n'a pas été ouverte: le formulaire est invalide."
'Closing motive': 'Motif de clôture'
'Person details': 'Détails de la personne'
+'Person duplicate': 'Trouver des doublons'
'Update details for %name%': 'Modifier détails de %name%'
Any accompanying periods are open: Aucune période d'accompagnement ouverte
An accompanying period is open: Une période d'accompagnement est ouverte