mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
import duplication from 2.0 branch
This commit is contained in:
parent
fe54c76317
commit
c32ba2bee4
@ -49,7 +49,8 @@
|
||||
"sensio/distribution-bundle": "^5.0",
|
||||
"knplabs/knp-menu-bundle": "^2.2",
|
||||
"league/csv": "^9.0",
|
||||
"champs-libres/async-uploader-bundle": "~1.0"
|
||||
"champs-libres/async-uploader-bundle": "~1.0",
|
||||
"laminas/laminas-zendframework-bridge": "~1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/dom-crawler": "~3.4",
|
||||
|
@ -169,7 +169,6 @@ class PhonenumberHelper
|
||||
} catch (ClientException $e) {
|
||||
$this->logger->error("[phonenumber helper] Could not format number "
|
||||
. "due to client error", [
|
||||
"message" => $e->getResponseBodySummary($e->getResponse()),
|
||||
"status_code" => $e->getResponse()->getStatusCode(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
@ -178,7 +177,6 @@ class PhonenumberHelper
|
||||
} catch (ServerException $e) {
|
||||
$this->logger->error("[phonenumber helper] Could not format number "
|
||||
. "due to server error", [
|
||||
"message" => $e->getResponseBodySummary($e->getResponse()),
|
||||
"status_code" => $e->getResponse()->getStatusCode(),
|
||||
"phonenumber" => $phonenumber
|
||||
]);
|
||||
|
@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Controller;
|
||||
|
||||
use Chill\PersonBundle\Actions\Remove\PersonMove;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\PersonNotDuplicate;
|
||||
use Chill\PersonBundle\Form\PersonConfimDuplicateType;
|
||||
use Chill\PersonBundle\Form\PersonFindManuallyDuplicateType;
|
||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\PersonBundle\Search\SimilarPersonMatcher;
|
||||
use http\Exception\InvalidArgumentException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
class PersonDuplicateController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \Chill\PersonBundle\Search\SimilarPersonMatcher
|
||||
*/
|
||||
private $similarPersonMatcher;
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Translation\TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @var \Chill\PersonBundle\Repository\PersonRepository
|
||||
*/
|
||||
private $personRepository;
|
||||
|
||||
/**
|
||||
* @var \Chill\PersonBundle\Actions\Remove\PersonMove
|
||||
*/
|
||||
private $personMove;
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(
|
||||
SimilarPersonMatcher $similarPersonMatcher,
|
||||
TranslatorInterface $translator,
|
||||
PersonRepository $personRepository,
|
||||
PersonMove $personMove,
|
||||
EventDispatcherInterface $eventDispatcher
|
||||
) {
|
||||
$this->similarPersonMatcher = $similarPersonMatcher;
|
||||
$this->translator = $translator;
|
||||
$this->personRepository = $personRepository;
|
||||
$this->personMove = $personMove;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person,
|
||||
"You are not allowed to see this person.");
|
||||
|
||||
$duplicatePersons = $this->similarPersonMatcher->
|
||||
matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL);
|
||||
|
||||
$notDuplicatePersons = $this->getDoctrine()->getRepository(PersonNotDuplicate::class)
|
||||
->findByNotDuplicatePerson($person);
|
||||
|
||||
return $this->render('ChillPersonBundle:PersonDuplicate:view.html.twig', [
|
||||
'person' => $person,
|
||||
'duplicatePersons' => $duplicatePersons,
|
||||
'notDuplicatePersons' => $notDuplicatePersons,
|
||||
]);
|
||||
}
|
||||
|
||||
public function confirmAction($person1_id, $person2_id, Request $request)
|
||||
{
|
||||
[$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id);
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1,
|
||||
"You are not allowed to see this person.");
|
||||
|
||||
$form = $this->createForm(PersonConfimDuplicateType::class);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$event = new PrivacyEvent($person1, array(
|
||||
'element_class' => Person::class,
|
||||
'action' => 'move'
|
||||
));
|
||||
$event->addPerson($person2);
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
$sqls = $this->personMove->getSQL($person2, $person1);
|
||||
|
||||
$connection = $this->getDoctrine()->getConnection();
|
||||
|
||||
$connection->beginTransaction();
|
||||
foreach($sqls as $sql) {
|
||||
$connection->executeQuery($sql);
|
||||
}
|
||||
$connection->commit();
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('The de-duplicate operation success'));
|
||||
|
||||
return $this->redirectToRoute('chill_person_duplicate_view', ['person_id' => $person1->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('ChillPersonBundle:PersonDuplicate:confirm.html.twig', [
|
||||
'person' => $person1,
|
||||
'person2' => $person2,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function notDuplicateAction($person1_id, $person2_id)
|
||||
{
|
||||
[$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id);
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1,
|
||||
"You are not allowed to see this person.");
|
||||
|
||||
$personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class)
|
||||
->findOneBy(['person1' => $person1, 'person2' => $person2]);
|
||||
|
||||
if (!$personNotDuplicate instanceof PersonNotDuplicate) {
|
||||
$personNotDuplicate = new PersonNotDuplicate();
|
||||
$personNotDuplicate->setPerson1($person1);
|
||||
$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' => $person1->getId()]);
|
||||
}
|
||||
|
||||
public function removeNotDuplicateAction($person1_id, $person2_id)
|
||||
{
|
||||
[$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id);
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1,
|
||||
"You are not allowed to see this person.");
|
||||
|
||||
$personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class)
|
||||
->findOneBy(['person1' => $person1, 'person2' => $person2]);
|
||||
|
||||
if ($personNotDuplicate instanceof PersonNotDuplicate) {
|
||||
$this->getDoctrine()->getManager()->remove($personNotDuplicate);
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_person_duplicate_view', ['person_id' => $person1->getId()]);
|
||||
}
|
||||
|
||||
public function findManuallyDuplicateAction($person_id, Request $request)
|
||||
{
|
||||
$person = $this->_getPerson($person_id);
|
||||
if ($person === null) {
|
||||
throw $this->createNotFoundException("Person with id $person_id not"
|
||||
. " found on this server");
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person,
|
||||
"You are not allowed to see this person.");
|
||||
|
||||
$form = $this->createForm(PersonFindManuallyDuplicateType::class);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$person2 = $form->get('person')->getData();
|
||||
|
||||
if ($person2 === null) {
|
||||
throw $this->createNotFoundException("Person with id $person2->getId() not"
|
||||
. " found on this server");
|
||||
}
|
||||
|
||||
if ($person === $person2) {
|
||||
$this->addFlash('error', $this->translator->trans('You cannot add duplicate with same person'));
|
||||
} elseif ($person->getCenter() !== $person2->getCenter()) {
|
||||
$this->addFlash('error', $this->translator->trans('You cannot duplicate two persons in two different centers'));
|
||||
} else {
|
||||
|
||||
$direction = $form->get('direction')->getData();
|
||||
|
||||
if ($direction === 'starting') {
|
||||
$params = [
|
||||
'person1_id' => $person->getId(),
|
||||
'person2_id' => $person2->getId(),
|
||||
];
|
||||
} else {
|
||||
$params = [
|
||||
'person1_id' => $person2->getId(),
|
||||
'person2_id' => $person->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_person_duplicate_confirm', $params);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('ChillPersonBundle:PersonDuplicate:find_manually.html.twig', [
|
||||
'person' => $person,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* easy getting a person by his id
|
||||
*/
|
||||
private function _getPerson($id): ?Person
|
||||
{
|
||||
return $this->personRepository->find($id);
|
||||
}
|
||||
|
||||
private function _getPersonsByPriority($person1_id, $person2_id)
|
||||
{
|
||||
if ($person1_id === $person2_id) {
|
||||
throw new InvalidArgumentException('Can not merge same person');
|
||||
}
|
||||
|
||||
if ($person1_id > $person2_id) {
|
||||
$person1 = $this->_getPerson($person2_id);
|
||||
$person2 = $this->_getPerson($person1_id);
|
||||
} else {
|
||||
$person1 = $this->_getPerson($person1_id);
|
||||
$person2 = $this->_getPerson($person2_id);
|
||||
}
|
||||
|
||||
if ($person1 === null) {
|
||||
throw $this->createNotFoundException("Person with id $person1_id not"
|
||||
. " found on this server");
|
||||
}
|
||||
|
||||
if ($person2 === null) {
|
||||
throw $this->createNotFoundException("Person with id $person2_id not"
|
||||
. " found on this server");
|
||||
}
|
||||
|
||||
return [$person1, $person2];
|
||||
}
|
||||
}
|
107
src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php
Normal file
107
src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* PersonNotDuplicate
|
||||
*
|
||||
* @ORM\Table(name="chill_person_not_duplicate")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class PersonNotDuplicate
|
||||
{
|
||||
/**
|
||||
* The person's id
|
||||
* @var integer
|
||||
*
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var Person
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
|
||||
*/
|
||||
private $person1;
|
||||
|
||||
/**
|
||||
* @var Person
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
|
||||
*/
|
||||
private $person2;
|
||||
|
||||
/**
|
||||
* @var \DateTime
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
private $date;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
|
||||
*/
|
||||
private $user;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class PersonConfimDuplicateType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('confirm', CheckboxType::class, [
|
||||
'label' => 'I confirm the merger of these 2 people',
|
||||
'mapped' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'chill_personbundle_person_confirm_duplicate';
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Form;
|
||||
|
||||
use Chill\PersonBundle\Form\Type\PickPersonType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotEqualTo;
|
||||
|
||||
class PersonFindManuallyDuplicateType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('person', PickPersonType::class, [
|
||||
'label' => 'Find duplicate',
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('direction', HiddenType::class, [
|
||||
'data' => 'starting',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'chill_personbundle_person_find_manually_duplicate';
|
||||
}
|
||||
}
|
@ -18,7 +18,12 @@
|
||||
namespace Chill\PersonBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@ -44,12 +49,16 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
protected AuthorizationCheckerInterface $security;
|
||||
|
||||
public function __construct(
|
||||
$showAccompanyingPeriod,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
AuthorizationCheckerInterface $security
|
||||
) {
|
||||
$this->showAccompanyingPeriod = $showAccompanyingPeriod;
|
||||
$this->translator = $translator;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
@ -75,6 +84,18 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
'order' => 100
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->security->isGranted(PersonVoter::DUPLICATE, $parameters['person'])) {
|
||||
$menu->addChild($this->translator->trans('Person duplicate'), [
|
||||
'route' => 'chill_person_duplicate_view',
|
||||
'routeParameters' => [
|
||||
'person_id' => $parameters['person']->getId()
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 99999
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\PersonNotDuplicate;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
final class PersonNotDuplicateRepository extends EntityRepository
|
||||
{
|
||||
public function findByNotDuplicatePerson(Person $person): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('pnd');
|
||||
$qb->select('pnd')
|
||||
->where('pnd.person1 = :person OR pnd.person2 = :person')
|
||||
;
|
||||
$qb->setParameter('person', $person);
|
||||
$result = $qb->getQuery()->getResult();
|
||||
|
||||
$persons = [];
|
||||
|
||||
foreach ($result as $row) {
|
||||
if ($row->getPerson1() === $person) {
|
||||
$persons[] = $row->getPerson2();
|
||||
} elseif ($row->getPerson2() === $person) {
|
||||
$persons[] = $row->getPerson1();
|
||||
}
|
||||
}
|
||||
|
||||
return $persons;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
Chill\PersonBundle\Entity\PersonNotDuplicate:
|
||||
type: entity
|
||||
table: chill_person_not_duplicate
|
||||
repositoryClass: Chill\PersonBundle\Repository\PersonNotDuplicateRepository
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
id: true
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
date:
|
||||
type: datetime
|
||||
manyToOne:
|
||||
person1:
|
||||
targetEntity: Chill\PersonBundle\Entity\Person
|
||||
person2:
|
||||
targetEntity: Chill\PersonBundle\Entity\Person
|
||||
user:
|
||||
targetEntity: Chill\MainBundle\Entity\User
|
@ -88,3 +88,23 @@ chill_person_timeline:
|
||||
chill_person_admin:
|
||||
path: "/{_locale}/admin/person"
|
||||
defaults: { _controller: ChillPersonBundle:Admin:index }
|
||||
|
||||
chill_person_duplicate_view:
|
||||
path: /{_locale}/person/{person_id}/duplicate/view
|
||||
controller: Chill\PersonBundle\Controller\PersonDuplicateController::viewAction
|
||||
|
||||
chill_person_duplicate_confirm:
|
||||
path: /{_locale}/person/{person1_id}/duplicate/{person2_id}/confirm
|
||||
controller: Chill\PersonBundle\Controller\PersonDuplicateController::confirmAction
|
||||
|
||||
chill_person_duplicate_not_duplicate:
|
||||
path: /{_locale}/person/{person1_id}/duplicate/{person2_id}/not-duplicate
|
||||
controller: Chill\PersonBundle\Controller\PersonDuplicateController::notDuplicateAction
|
||||
|
||||
chill_person_remove_duplicate_not_duplicate:
|
||||
path: /{_locale}/person/{person1_id}/duplicate/{person2_id}/remove-not-duplicate
|
||||
controller: Chill\PersonBundle\Controller\PersonDuplicateController::removeNotDuplicateAction
|
||||
|
||||
chill_person_find_manually_duplicate:
|
||||
path: /{_locale}/person/{person_id}/find-manually
|
||||
controller: Chill\PersonBundle\Controller\PersonDuplicateController::findManuallyDuplicateAction
|
||||
|
@ -17,3 +17,7 @@ services:
|
||||
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
|
||||
|
||||
Chill\PersonBundle\Controller\AdminController: ~
|
||||
|
||||
Chill\PersonBundle\Controller\PersonDuplicateController:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -12,8 +12,10 @@ services:
|
||||
- { name: 'chill.menu_builder' }
|
||||
|
||||
Chill\PersonBundle\Menu\PersonMenuBuilder:
|
||||
autowire: true
|
||||
arguments:
|
||||
$showAccompanyingPeriod: '%chill_person.accompanying_period%'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
# $translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
# $security: '@Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface'
|
||||
tags:
|
||||
- { name: 'chill.menu_builder' }
|
||||
|
@ -12,6 +12,12 @@ services:
|
||||
arguments:
|
||||
- 'Chill\PersonBundle\Entity\Person'
|
||||
|
||||
Chill\PersonBundle\Repository\PersonNotDuplicateRepository:
|
||||
class: Chill\PersonBundle\Person\PersonNotDuplicateRepository
|
||||
factory: [ '@doctrine.orm.entity_manager', getRepository ]
|
||||
arguments:
|
||||
- 'Chill\PersonBundle\Entity\PersonNotDuplicate'
|
||||
|
||||
Chill\PersonBundle\Repository\ClosingMotiveRepository:
|
||||
class: Chill\PersonBundle\Repository\ClosingMotiveRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
|
@ -24,7 +24,4 @@ services:
|
||||
- { name: chill.search, alias: 'person_similarity' }
|
||||
|
||||
Chill\PersonBundle\Search\SimilarPersonMatcher:
|
||||
arguments:
|
||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||
autowire: true
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20210128152747 extends AbstractMigration
|
||||
{
|
||||
public function getDescription() : string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->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');
|
||||
}
|
||||
}
|
@ -149,6 +149,37 @@ Add an address: Ajouter une adresse
|
||||
Back to the person details: Retour aux détails de la personne
|
||||
Move to another address: Nouvelle adresse
|
||||
|
||||
# dédoublonnage
|
||||
Old person: Doublon
|
||||
Old person explain: sera supprimé lors de la fusion
|
||||
New person: Dossier cible
|
||||
New person explain: sera conservé lors de la fusion
|
||||
I confirm the merger of these 2 people : Je confime la fusion de ces 2 dossiers
|
||||
Person duplicate explained: Chill a détecté des doublons potentiels ! Vous pouvez confirmer, infirmer, ou encore désigner manuellement un autre doublon.
|
||||
Person flaged as duplicate: Dossiers marqués comme faux-positif
|
||||
Person flaged as duplicate explained: Les dossiers suivants sont marqués comme faux-positifs. Ce ne sont pas des doublons !
|
||||
Associate manually a duplicate person: Désigner manuellement un doublon
|
||||
Invert: Inverser le sens de la fusion
|
||||
Find duplicate: Trouver un doublon
|
||||
Person duplicate: Traiter les doublons
|
||||
Open in another window: Ouvrir dans une nouvelle fenêtre
|
||||
Deleted datas: Données supprimées
|
||||
Keeped datas: Données conservées
|
||||
Moved links: Relations déplacées
|
||||
Keeped links: Relations conservées
|
||||
Merge duplicate persons folders: Fusion de dossiers
|
||||
Merge: Fusionner
|
||||
duplicate: Doublon
|
||||
not-duplicate: Faux-positif
|
||||
Switch to truefalse: Marquer comme faux-positif
|
||||
Switch to duplicate: Marquer comme doublon potentiel
|
||||
No duplicate candidates: Il n'y a pas de doublons détectés, ni de faux-positifs
|
||||
You cannot add duplicate with same person: Indiquez une autre personne. Il n'est pas possible de fusionner un dossier de personne avec elle-même.
|
||||
You cannot duplicate two persons in two different centers: Il n'est pas possible de fusionner les dossiers dans deux centres différents
|
||||
CHILL_PERSON_DUPLICATE: Gestion des doublons
|
||||
The de-duplicate operation success: L'opération de dé-doublonnage s'est terminée avec succès
|
||||
|
||||
|
||||
#timeline
|
||||
Timeline: Historique
|
||||
Closing the accompanying period: Fermeture de la période d'accompagnement
|
||||
|
@ -0,0 +1,38 @@
|
||||
{%- macro details(person, options) -%}
|
||||
|
||||
<ul>
|
||||
<li><b>Identifiant</b>: {{ person.id }}</li>
|
||||
<li><b>{{ 'gender'|trans }}</b>:
|
||||
{{ person.gender|trans }}</li>
|
||||
<li><b>{{ 'Marital status'|trans }}</b>:
|
||||
{% if person.maritalStatus is not null %}{{ person.maritalStatus.name|localize_translatable_string }}{% endif %}</li>
|
||||
<li><b>{{ 'birthdate'|trans }}</b>:
|
||||
{% if person.birthdate is not null %}{{ person.birthdate|localizeddate('short', 'none') }}{% endif %}</li>
|
||||
<li><b>{{ 'placeOfBirth'|trans }}</b>:
|
||||
{% if person.placeOfBirth is not empty %}{{ person.placeOfBirth }}{% endif %}</li>
|
||||
<li><b>{{ 'countryOfBirth'|trans }}</b>:
|
||||
{% if person.countryOfBirth is not null %}{{ person.countryOfBirth.name|localize_translatable_string }}{% endif %}</li>
|
||||
<li><b>{{ 'nationality'|trans }}</b>:
|
||||
{% if person.nationality is not null %}{{ person.nationality.name|localize_translatable_string }}{% endif %}</li>
|
||||
<li><b>{{ 'phonenumber'|trans }}</b>:
|
||||
{{ person.phonenumber|chill_format_phonenumber }}</li>
|
||||
<li><b>{{ 'mobilenumber'|trans }}</b>:
|
||||
{{ person.mobilenumber|chill_format_phonenumber }}</li>
|
||||
<li><b>{{ 'email'|trans }}</b>:
|
||||
{{ person.email }}</li>
|
||||
<li><b>{{ 'memo'|trans }}</b>:
|
||||
{{ person.memo }}</li>
|
||||
<li><b>{{ 'Address'|trans }}</b>:
|
||||
{%- if person.lastAddress is not empty -%}
|
||||
{{ person.lastAddress|chill_entity_render_box({'with_valid_from': false}) }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'Any address'|trans }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li><b>{{ 'Spoken languages'|trans }}</b>:
|
||||
{% for lang in person.spokenLanguages %}{{ lang.name|localize_translatable_string }}{% if not loop.last %},{% endif %}{% endfor %}</li>
|
||||
<li><b>{{ 'Contact information'|trans }}</b>:
|
||||
{% if person.contactInfo is not empty %}{{ person.contactInfo|nl2br }}{% endif %}</li>
|
||||
|
||||
</ul>
|
||||
{% endmacro %}
|
@ -0,0 +1,93 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_person_duplicate' %}
|
||||
{% import '@ChillPerson/PersonDuplicate/_sidepane.html.twig' as sidepane %}
|
||||
|
||||
{% block title %}{{ 'Person duplicate'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~
|
||||
' ' ~ person.lastName }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
div.duplicate-content {
|
||||
margin: 0 2rem;
|
||||
}
|
||||
div.col {
|
||||
padding: 1em;
|
||||
border: 3px solid #cccccc;
|
||||
}
|
||||
div.border {
|
||||
border: 4px solid #3c9f8d;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<div class="container-fluid content"><div class="duplicate-content">
|
||||
|
||||
<h1>{{ 'Merge duplicate persons folders'|trans }}</h1>
|
||||
|
||||
<div class="grid-6 grid-tablet-12 gid-mobile-12">
|
||||
<p><b>{{ 'Old person'|trans }}</b>:
|
||||
{{ 'Old person explain'|trans }}
|
||||
</p>
|
||||
<div class="col">
|
||||
|
||||
<h1><span><a class="btn btn-show" target="_blank" title="{{ 'Open in another window'|trans }}" href="{{ path('chill_person_view', { person_id : person2.id }) }}"></a></span>
|
||||
{{ person2 }}
|
||||
</h1>
|
||||
|
||||
<h4>{{ 'Deleted datas'|trans ~ ':' }}</h4>
|
||||
{{ sidepane.details(person2) }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-6 grid-tablet-12 gid-mobile-12">
|
||||
<p><b>{{ 'New person'|trans }}</b>:
|
||||
{{ 'New person explain'|trans }}
|
||||
</p>
|
||||
<div class="col border">
|
||||
|
||||
<h1><span><a class="btn btn-show" target="_blank" title="{{ 'Open in another window'|trans }}" href="{{ path('chill_person_view', { person_id : person.id }) }}"></a></span>
|
||||
{{ person }}
|
||||
</h1>
|
||||
|
||||
<h4>{{ 'Keeped datas'|trans ~ ':' }}</h4>
|
||||
{{ sidepane.details(person) }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
<div class="grid-4 grid-tablet-12 gid-mobile-12 centered">
|
||||
|
||||
<div class="container-fluid" style="padding-top: 1em;">
|
||||
<div class="col-1 clear" style="padding-top: 10px;">
|
||||
{{ form_widget(form.confirm) }}
|
||||
{{ form_label(form.confirm) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="sticky-form-buttons record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('chill_person_duplicate_view', {'person_id': person.id }) }}" class="sc-button bt-cancel btn btn-chill-gray center margin-5">
|
||||
{{ 'Return'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="{{ path('chill_person_duplicate_confirm', { person1_id : person2.id, person2_id : person.id }) }}"
|
||||
class="sc-button bt-orange btn btn-action">
|
||||
<i class="fa fa-exchange"></i>
|
||||
{{ 'Invert'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="sc-button bt-green btn btn-submit" type="submit"><i class="fa fa-cog fa-fw"></i>{{ 'Merge'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div></div>
|
||||
{% endblock %}
|
@ -0,0 +1,31 @@
|
||||
{% extends "@ChillPerson/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_person_duplicate' %}
|
||||
|
||||
{% block title %}{{ 'Find duplicate'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~
|
||||
' ' ~ person.lastName }}{% endblock %}
|
||||
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="person-duplicate">
|
||||
|
||||
<h1>{{ 'Désigner un dossier doublon'|trans }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_rest(form) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_person_duplicate_view', {'person_id' : person.id}) }}" class="sc-button bt-cancel btn btn-cancel">
|
||||
{{ 'Return'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<button class="sc-button bt-green btn btn-save" type="submit">{{ 'Next'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,159 @@
|
||||
{% extends "@ChillPerson/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_person_duplicate' %}
|
||||
|
||||
{% block title %}{{ 'Person duplicate'|trans|capitalize ~ ' ' ~ person.firstName|capitalize ~
|
||||
' ' ~ person.lastName }}{% endblock %}
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="person-duplicate">
|
||||
|
||||
<h1>{{ title|default('Person duplicate')|trans }}</h1>
|
||||
|
||||
{% if duplicatePersons|length > 0 %}
|
||||
<p>{{ title|default('Person duplicate explained')|trans }}</p>
|
||||
|
||||
<table class="table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="chill-green">{% trans %}Name{% endtrans %}</th>
|
||||
<th class="chill-green">{% trans %}Date of birth{% endtrans %}</th>
|
||||
<th class="chill-green">{% trans %}Nationality{% endtrans %}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for duplicatePerson in duplicatePersons %}
|
||||
<tr>
|
||||
<td>
|
||||
{% set is_open = duplicatePerson.isOpen() %}
|
||||
<a href="{{ path('chill_person_view', { person_id : duplicatePerson.getId }) }}" {% if chill_person.fields.accompanying_period == 'visible' %}{% if is_open %} alt="{{ 'An accompanying period is open'|trans|e('html_attr') }}"{% else %} alt="{{ 'Any accompanying periods are open'|trans|e('html_attr') }}" {% endif %}{% endif %}>
|
||||
{{ duplicatePerson|chill_entity_render_box }}
|
||||
{% apply spaceless %}
|
||||
{% if chill_person.fields.accompanying_period == 'visible' %}
|
||||
{% if is_open == false %}
|
||||
<i class="fa fa-lock" ></i>
|
||||
{% else %}
|
||||
<i class="fa fa-unlock" ></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endapply %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if duplicatePerson.birthdate is not null %}
|
||||
{{ duplicatePerson.birthdate|localizeddate('long', 'none') }}
|
||||
{% else %}
|
||||
{{ 'Unknown date of birth'|trans }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if duplicatePerson.nationality is not null %}
|
||||
{{ duplicatePerson.nationality.name|localize_translatable_string }}
|
||||
{% else %}
|
||||
{{ 'Without nationality'|trans }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="sc-button bt-show btn btn-show" target="_blank" href="{{ path('chill_person_view', { person_id : duplicatePerson.id }) }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sc-button bt-orange btn btn-action" href="{{ path('chill_person_duplicate_confirm', { person1_id : person.id, person2_id : duplicatePerson.id }) }}">
|
||||
<i class="fa fa-cog fa-fw"></i>{{ 'Merge'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sc-button btn btn-misc" title="{{ 'Switch to truefalse'|trans }}"
|
||||
href="{{ path('chill_person_duplicate_not_duplicate', {person1_id : person.id, person2_id : duplicatePerson.id}) }}">
|
||||
{{ 'Switch to truefalse'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if notDuplicatePersons|length > 0 %}
|
||||
<h2>{{ 'Person flaged as duplicate' | trans }}</h2>
|
||||
<p>{{ 'Person flaged as duplicate explained' | trans }}</p>
|
||||
|
||||
<table class="table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="chill-orange">{% trans %}Name{% endtrans %}</th>
|
||||
<th class="chill-orange">{% trans %}Date of birth{% endtrans %}</th>
|
||||
<th class="chill-orange">{% trans %}Nationality{% endtrans %}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for notDuplicatePerson in notDuplicatePersons %}
|
||||
<tr>
|
||||
<td>
|
||||
{% set is_open = notDuplicatePerson.isOpen() %}
|
||||
<a href="{{ path('chill_person_view', { person_id : notDuplicatePerson.getId }) }}" {% if chill_person.fields.accompanying_period == 'visible' %}{% if is_open %} alt="{{ 'An accompanying period is open'|trans|e('html_attr') }}"{% else %} alt="{{ 'Any accompanying periods are open'|trans|e('html_attr') }}" {% endif %}{% endif %}>
|
||||
{{ notDuplicatePerson|chill_entity_render_box }}
|
||||
{% apply spaceless %}
|
||||
{% if chill_person.fields.accompanying_period == 'visible' %}
|
||||
{% if is_open == false %}
|
||||
<i class="fa fa-lock" ></i>
|
||||
{% else %}
|
||||
<i class="fa fa-unlock" ></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endapply %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if notDuplicatePerson.birthdate is not null %}
|
||||
{{ notDuplicatePerson.birthdate|localizeddate('long', 'none') }}
|
||||
{% else %}
|
||||
{{ 'Unknown date of birth'|trans }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if notDuplicatePerson.nationality is not null %}
|
||||
{{ notDuplicatePerson.nationality.name|localize_translatable_string }}
|
||||
{% else %}
|
||||
{{ 'Without nationality'|trans }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="sc-button bt-show btn btn-show" target="_blank" href="{{ path('chill_person_view', { person_id : notDuplicatePerson.id }) }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sc-button btn btn-misc" title="{{ 'Switch to duplicate'|trans }}"
|
||||
href="{{ path('chill_person_remove_duplicate_not_duplicate', {person1_id : person.id, person2_id : notDuplicatePerson.id}) }}">
|
||||
{{ 'Switch to duplicate'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if notDuplicatePersons|length == 0 and duplicatePersons|length == 0 %}
|
||||
<span class="chill-no-data-statement">{{ 'No duplicate candidates'|trans }}</span>
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_person_view', {person_id: person.id }) }}" class="sc-button bt-cancel btn btn-cancel">
|
||||
{{ 'Return'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_person_find_manually_duplicate', {person_id: person.id}) }}" class="sc-button bt-create btn btn-action">
|
||||
{{ 'Associate manually a duplicate person' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@ -18,9 +18,12 @@
|
||||
*/
|
||||
namespace Chill\PersonBundle\Search;
|
||||
|
||||
use Chill\PersonBundle\Entity\PersonNotDuplicate;
|
||||
use Chill\PersonBundle\Templating\Entity\PersonRender;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\PersonBundle\Repository\PersonNotDuplicateRepository;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
@ -32,62 +35,89 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
*/
|
||||
class SimilarPersonMatcher
|
||||
{
|
||||
CONST SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL = 'alphabetical';
|
||||
|
||||
CONST SIMILAR_SEARCH_ORDER_BY_SIMILARITY = 'similarity';
|
||||
|
||||
/**
|
||||
*
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
protected PersonNotDuplicateRepository $personNotDuplicateRepository;
|
||||
|
||||
protected PersonRender $personRender;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
TokenStorageInterface $tokenStorage
|
||||
TokenStorageInterface $tokenStorage,
|
||||
PersonNotDuplicateRepository $personNotDuplicateRepository,
|
||||
PersonRender $personRender
|
||||
) {
|
||||
$this->em = $em;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->personNotDuplicateRepository = $personNotDuplicateRepository;
|
||||
$this->personRender = $personRender;
|
||||
}
|
||||
|
||||
|
||||
public function matchPerson(Person $person)
|
||||
{
|
||||
$centers = $this->authorizationHelper
|
||||
->getReachableCenters(
|
||||
public function matchPerson(
|
||||
Person $person,
|
||||
float $precision = 0.15,
|
||||
string $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY,
|
||||
bool $addYearComparison = false
|
||||
) {
|
||||
$centers = $this->authorizationHelper->getReachableCenters(
|
||||
$this->tokenStorage->getToken()->getUser(),
|
||||
new Role(PersonVoter::SEE)
|
||||
);
|
||||
$query = $this->em->createQuery();
|
||||
|
||||
$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 '
|
||||
. ' ) '
|
||||
. ' AND p.center IN (:centers)'
|
||||
. ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC '
|
||||
|
||||
;
|
||||
|
||||
$query =
|
||||
$this->em
|
||||
->createQuery($dql)
|
||||
->setParameter('firstName', $person->getFirstName())
|
||||
->setParameter('lastName', $person->getLastName())
|
||||
->setParameter('fullName', $person->getFirstName() . ' ' . $person->getLastName())
|
||||
if ($person->getId() !== NULL) {
|
||||
$dql .= ' AND p.id != :personId ';
|
||||
$notDuplicatePersons = $this->personNotDuplicateRepository->findByNotDuplicatePerson($person);
|
||||
|
||||
$query->setParameter('personId', $person->getId());
|
||||
|
||||
if (count($notDuplicatePersons)) {
|
||||
$dql .= ' AND p.id not in (:notDuplicatePersons)';
|
||||
$query->setParameter('notDuplicatePersons', $notDuplicatePersons);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($orderBy) {
|
||||
case self::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL:
|
||||
$dql .= ' ORDER BY p.fullnameCanonical ASC ';
|
||||
break;
|
||||
case self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY:
|
||||
default :
|
||||
$dql .= ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC ';
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->setDQL($dql)
|
||||
->setParameter('fullName', $this->personRender->renderString($person, []))
|
||||
->setParameter('centers', $centers)
|
||||
->setParameter('precision', $precision)
|
||||
;
|
||||
|
||||
return $query->getResult();
|
||||
|
@ -40,6 +40,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
||||
const SEE = 'CHILL_PERSON_SEE';
|
||||
const STATS = 'CHILL_PERSON_STATS';
|
||||
const LISTS = 'CHILL_PERSON_LISTS';
|
||||
const DUPLICATE = 'CHILL_PERSON_DUPLICATE';
|
||||
|
||||
/**
|
||||
*
|
||||
@ -56,11 +57,11 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
||||
{
|
||||
if ($subject instanceof Person) {
|
||||
return \in_array($attribute, [
|
||||
self::CREATE, self::UPDATE, self::SEE
|
||||
self::CREATE, self::UPDATE, self::SEE, self::DUPLICATE
|
||||
]);
|
||||
} elseif ($subject instanceof Center) {
|
||||
return \in_array($attribute, [
|
||||
self::STATS, self::LISTS
|
||||
self::STATS, self::LISTS, self::DUPLICATE
|
||||
]);
|
||||
} elseif ($subject === null) {
|
||||
return $attribute === self::CREATE;
|
||||
@ -87,7 +88,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
||||
|
||||
private function getAttributes()
|
||||
{
|
||||
return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS);
|
||||
return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS, self::DUPLICATE);
|
||||
}
|
||||
|
||||
public function getRoles()
|
||||
|
Loading…
x
Reference in New Issue
Block a user