mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
New permission for duplicate & export duplicate persons
Signed-off-by: Mathieu Jaumotte <mathieu.jaumotte@champs-libres.coop>
This commit is contained in:
parent
0149457fba
commit
c34b992437
@ -65,6 +65,9 @@ class PersonDuplicateController extends Controller
|
|||||||
. " found on this server");
|
. " found on this server");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person,
|
||||||
|
"You are not allowed to see this person.");
|
||||||
|
|
||||||
$duplicatePersons = $this->similarPersonMatcher->
|
$duplicatePersons = $this->similarPersonMatcher->
|
||||||
matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL);
|
matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL);
|
||||||
|
|
||||||
@ -82,6 +85,9 @@ class PersonDuplicateController extends Controller
|
|||||||
{
|
{
|
||||||
[$person1, $person2] = $this->_getPersonsByPriority($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.");
|
||||||
|
|
||||||
$form = $this->createForm(PersonConfimDuplicateType::class);
|
$form = $this->createForm(PersonConfimDuplicateType::class);
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
@ -118,6 +124,9 @@ class PersonDuplicateController extends Controller
|
|||||||
{
|
{
|
||||||
[$person1, $person2] = $this->_getPersonsByPriority($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)
|
$personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class)
|
||||||
->findOneBy(['person1' => $person1, 'person2' => $person2]);
|
->findOneBy(['person1' => $person1, 'person2' => $person2]);
|
||||||
|
|
||||||
@ -138,6 +147,9 @@ class PersonDuplicateController extends Controller
|
|||||||
{
|
{
|
||||||
[$person1, $person2] = $this->_getPersonsByPriority($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)
|
$personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class)
|
||||||
->findOneBy(['person1' => $person1, 'person2' => $person2]);
|
->findOneBy(['person1' => $person1, 'person2' => $person2]);
|
||||||
|
|
||||||
|
@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Export\Export;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Export\DirectExportInterface;
|
||||||
|
use Chill\MainBundle\Export\ExportElementValidatedInterface;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Chill\PersonBundle\Export\Declarations;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
|
use Symfony\Component\Translation\TranslatorInterface;
|
||||||
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a list of duplicate peoples
|
||||||
|
*/
|
||||||
|
class ListPersonDuplicate implements DirectExportInterface, ExportElementValidatedInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @var EntityManagerInterface
|
||||||
|
*/
|
||||||
|
protected $entityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Symfony\Component\Translation\TranslatorInterface
|
||||||
|
*/
|
||||||
|
private $translator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
|
||||||
|
*/
|
||||||
|
private $router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $baseUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private const PRECISION_DEFAULT_VALUE = 0.7;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
TranslatorInterface $translator,
|
||||||
|
UrlGeneratorInterface $router,
|
||||||
|
$routeParameters
|
||||||
|
) {
|
||||||
|
$this->entityManager = $em;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->router = $router;
|
||||||
|
$this->baseUrl = $routeParameters['scheme'].
|
||||||
|
'://'.$routeParameters['host'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@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' => 'Precision',
|
||||||
|
'data' => self::PRECISION_DEFAULT_VALUE,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateForm($data, ExecutionContextInterface $context)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(array $acl, array $data = []): Response
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
$values[] = $this->getHeaders();
|
||||||
|
|
||||||
|
$result = $this->getResult($data);
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$values[] = [
|
||||||
|
$row['id1'],
|
||||||
|
$row['firstname1'],
|
||||||
|
$row['lastname1'],
|
||||||
|
$this->baseUrl.$this->router->generate('chill_person_view', ['person_id' => $row['id1']]),
|
||||||
|
$row['id2'],
|
||||||
|
$row['firstname2'],
|
||||||
|
$row['lastname2'],
|
||||||
|
$this->baseUrl.$this->router->generate('chill_person_view', ['person_id' => $row['id2']]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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($data = [])
|
||||||
|
{
|
||||||
|
$precision = $data['precision'] ?? self::PRECISION_DEFAULT_VALUE;
|
||||||
|
|
||||||
|
$sql = '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) > :precision
|
||||||
|
AND p.id < p2.id)
|
||||||
|
OR (UNACCENT(LOWER(p.firstname)) = UNACCENT(LOWER(p2.lastname))
|
||||||
|
AND UNACCENT(LOWER(p.lastname)) = UNACCENT(LOWER(p2.firstname)))
|
||||||
|
JOIN centers AS p1center
|
||||||
|
ON p1center.id = p.center_id
|
||||||
|
JOIN centers AS p2center
|
||||||
|
ON p2center.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)
|
||||||
|
)
|
||||||
|
ORDER BY p.fullnamecanonical, p.id, p2.id';
|
||||||
|
|
||||||
|
$statement = $this->entityManager->getConnection()->prepare($sql);
|
||||||
|
$statement->bindValue('precision', $precision);
|
||||||
|
$statement->execute();
|
||||||
|
|
||||||
|
return $statement->fetchAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHeaders(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->translator->trans('Departure folder number'),
|
||||||
|
$this->translator->trans('Last name'),
|
||||||
|
$this->translator->trans('First name'),
|
||||||
|
$this->translator->trans('Link'),
|
||||||
|
$this->translator->trans('Arrival folder number'),
|
||||||
|
$this->translator->trans('Last name'),
|
||||||
|
$this->translator->trans('First name'),
|
||||||
|
$this->translator->trans('Link'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requiredRole(): Role
|
||||||
|
{
|
||||||
|
return new Role(PersonVoter::DUPLICATE);
|
||||||
|
}
|
||||||
|
}
|
@ -38,19 +38,16 @@ class SimilarPersonMatcher
|
|||||||
CONST SIMILAR_SEARCH_ORDER_BY_SIMILARITY = 'similarity';
|
CONST SIMILAR_SEARCH_ORDER_BY_SIMILARITY = 'similarity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @var EntityManagerInterface
|
* @var EntityManagerInterface
|
||||||
*/
|
*/
|
||||||
protected $em;
|
protected $em;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @var AuthorizationHelper
|
* @var AuthorizationHelper
|
||||||
*/
|
*/
|
||||||
protected $authorizationHelper;
|
protected $authorizationHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @var TokenStorageInterface
|
* @var TokenStorageInterface
|
||||||
*/
|
*/
|
||||||
protected $tokenStorage;
|
protected $tokenStorage;
|
||||||
@ -65,7 +62,6 @@ class SimilarPersonMatcher
|
|||||||
$this->tokenStorage = $tokenStorage;
|
$this->tokenStorage = $tokenStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function matchPerson(Person $person, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY)
|
public function matchPerson(Person $person, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY)
|
||||||
{
|
{
|
||||||
$centers = $this->authorizationHelper->getReachableCenters(
|
$centers = $this->authorizationHelper->getReachableCenters(
|
||||||
@ -97,7 +93,6 @@ class SimilarPersonMatcher
|
|||||||
$dql .= ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC ';
|
$dql .= ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC ';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$query = $this->em
|
$query = $this->em
|
||||||
->createQuery($dql)
|
->createQuery($dql)
|
||||||
->setParameter('fullName', $person->getFirstName() . ' ' . $person->getLastName())
|
->setParameter('fullName', $person->getFirstName() . ' ' . $person->getLastName())
|
||||||
|
@ -40,6 +40,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
|||||||
const SEE = 'CHILL_PERSON_SEE';
|
const SEE = 'CHILL_PERSON_SEE';
|
||||||
const STATS = 'CHILL_PERSON_STATS';
|
const STATS = 'CHILL_PERSON_STATS';
|
||||||
const LISTS = 'CHILL_PERSON_LISTS';
|
const LISTS = 'CHILL_PERSON_LISTS';
|
||||||
|
const DUPLICATE = 'CHILL_PERSON_DUPLICATE';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -56,11 +57,11 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
|||||||
{
|
{
|
||||||
if ($subject instanceof Person) {
|
if ($subject instanceof Person) {
|
||||||
return \in_array($attribute, [
|
return \in_array($attribute, [
|
||||||
self::CREATE, self::UPDATE, self::SEE
|
self::CREATE, self::UPDATE, self::SEE, self::DUPLICATE
|
||||||
]);
|
]);
|
||||||
} elseif ($subject instanceof Center) {
|
} elseif ($subject instanceof Center) {
|
||||||
return \in_array($attribute, [
|
return \in_array($attribute, [
|
||||||
self::STATS, self::LISTS
|
self::STATS, self::LISTS, self::DUPLICATE
|
||||||
]);
|
]);
|
||||||
} elseif ($subject === null) {
|
} elseif ($subject === null) {
|
||||||
return $attribute === self::CREATE;
|
return $attribute === self::CREATE;
|
||||||
@ -87,7 +88,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
|
|||||||
|
|
||||||
private function getAttributes()
|
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()
|
public function getRoles()
|
||||||
|
@ -15,7 +15,17 @@ services:
|
|||||||
- "@chill.custom_field.provider"
|
- "@chill.custom_field.provider"
|
||||||
tags:
|
tags:
|
||||||
- { name: chill.export, alias: list_person }
|
- { 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"
|
||||||
|
- '%chill_main.notifications%'
|
||||||
|
tags:
|
||||||
|
- { name: chill.export, alias: list_person_duplicate }
|
||||||
|
|
||||||
chill.person.export.filter_gender:
|
chill.person.export.filter_gender:
|
||||||
class: Chill\PersonBundle\Export\Filter\GenderFilter
|
class: Chill\PersonBundle\Export\Filter\GenderFilter
|
||||||
arguments:
|
arguments:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user