mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-13 05:44:24 +00:00
Merge branch 'master' into CRUD-init
This commit is contained in:
commit
98b491063c
23
CHANGELOG.md
23
CHANGELOG.md
@ -49,7 +49,28 @@ Version 1.5.7
|
||||
- add an icon when the file is opened / closed in result list, and in person rendering macro
|
||||
- improve command to move person and all data: allow to delete some entities during move and add events
|
||||
|
||||
Version 1.5.8
|
||||
=============
|
||||
|
||||
- add search by phonenumber, with a custom SearchInterface
|
||||
|
||||
This can be activated or desactivated by config:
|
||||
|
||||
```
|
||||
chill_person:
|
||||
enabled: true
|
||||
search:
|
||||
enabled: true
|
||||
|
||||
# enable search by phone. 'always' show the result on every result. 'on-domain' will show the result only if the domain is given in the search box. 'never' disable this feature
|
||||
search_by_phone: on-domain # One of "always"; "on-domain"; "never"
|
||||
```
|
||||
- format phonenumber using twilio (if available) ;
|
||||
- add `record_actions` in person search result list: users can click on a little eye to open person page ;
|
||||
- add new fields (email, mobilenumber, gender) into importPeopleFromCSV command
|
||||
- configure asset using a function
|
||||
|
||||
|
||||
CRUD-init branch
|
||||
================
|
||||
|
||||
- configure asset using a function
|
||||
|
@ -38,44 +38,39 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
*
|
||||
* Class ImportPeopleFromCSVCommand
|
||||
*
|
||||
* @package Chill\PersonBundle\Command
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class ImportPeopleFromCSVCommand extends ContainerAwareCommand
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Chill\MainBundle\Templating\TranslatableStringHelper
|
||||
*/
|
||||
protected $helper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var \Doctrine\Common\Persistence\ObjectManager
|
||||
*/
|
||||
protected $em;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
@ -88,13 +83,11 @@ class ImportPeopleFromCSVCommand extends ContainerAwareCommand
|
||||
protected $line;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var array where key are column names, and value the custom field slug
|
||||
*/
|
||||
protected $customFieldMapping = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* @var CustomFieldProvider
|
||||
*/
|
||||
protected $customFieldProvider;
|
||||
@ -112,10 +105,13 @@ class ImportPeopleFromCSVCommand extends ContainerAwareCommand
|
||||
['firstname', 'The column header for firstname', 'firstname'],
|
||||
['lastname', 'The column header for lastname', 'lastname'],
|
||||
['birthdate', 'The column header for birthdate', 'birthdate'],
|
||||
['gender', 'The column header for gender', 'gender'],
|
||||
['opening_date', 'The column header for opening date', 'opening_date'],
|
||||
['closing_date', 'The column header for closing date', 'closing_date'],
|
||||
['memo', 'The column header for memo', 'memo'],
|
||||
['email', 'The column header for email', 'email'],
|
||||
['phonenumber', 'The column header for phonenumber', 'phonenumber'],
|
||||
['mobilenumber', 'The column header for mobilenumber', 'mobilenumber'],
|
||||
['street1', 'The column header for street 1', 'street1'],
|
||||
['postalcode', 'The column header for postal code', 'postalcode'],
|
||||
['locality', 'The column header for locality', 'locality'],
|
||||
@ -144,8 +140,10 @@ class ImportPeopleFromCSVCommand extends ContainerAwareCommand
|
||||
|
||||
parent::__construct('chill:person:import');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
@ -256,7 +254,10 @@ EOF
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
protected function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// preparing the basic
|
||||
@ -286,6 +287,9 @@ EOF
|
||||
$this->loadAnswerMatching();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $row
|
||||
*/
|
||||
protected function matchColumnToCustomField($row)
|
||||
{
|
||||
|
||||
@ -359,6 +363,9 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function dumpAnswerMatching()
|
||||
{
|
||||
if ($this->input->hasOption('dump-choice-matching') && !empty($this->input->getOption('dump-choice-matching'))) {
|
||||
@ -372,6 +379,12 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return int|null|void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->input = $input;
|
||||
@ -499,10 +512,11 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param array $row
|
||||
* @param array $headers the processed header : an array as prepared by self::processingHeaders
|
||||
* @return Person
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function createPerson($row, $headers)
|
||||
{
|
||||
@ -534,6 +548,9 @@ EOF
|
||||
case 'birthdate':
|
||||
$this->processBirthdate($person, $value);
|
||||
break;
|
||||
case 'gender':
|
||||
$person->setGender($value);
|
||||
break;
|
||||
case 'opening_date':
|
||||
// we have processed this when creating the person object, skipping;
|
||||
break;
|
||||
@ -543,9 +560,15 @@ EOF
|
||||
case 'memo':
|
||||
$person->setMemo($value);
|
||||
break;
|
||||
case 'email':
|
||||
$person->setEmail($value);
|
||||
break;
|
||||
case 'phonenumber':
|
||||
$person->setPhonenumber($value);
|
||||
break;
|
||||
case 'mobilenumber':
|
||||
$person->setMobilenumber($value);
|
||||
break;
|
||||
|
||||
// we just keep the column number for those data
|
||||
case 'postalcode':
|
||||
@ -563,26 +586,34 @@ EOF
|
||||
// handle address
|
||||
if (\in_array('postalcode', $headers)) {
|
||||
|
||||
$address = new Address();
|
||||
$postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? '');
|
||||
|
||||
if ($postalCode === null) {
|
||||
throw new \Exception("The locality is not found");
|
||||
if (! empty($postalCodeValue)) {
|
||||
|
||||
$address = new Address();
|
||||
$postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? '');
|
||||
|
||||
if ($postalCode === null) {
|
||||
throw new \Exception("The locality is not found");
|
||||
}
|
||||
|
||||
$address->setPostcode($postalCode);
|
||||
|
||||
if (\in_array('street1', $headers)) {
|
||||
$address->setStreetAddress1($street1Value);
|
||||
}
|
||||
$address->setValidFrom(new \DateTime('today'));
|
||||
|
||||
$person->addAddress($address);
|
||||
}
|
||||
|
||||
$address->setPostcode($postalCode);
|
||||
|
||||
if (\in_array('street1', $headers)) {
|
||||
$address->setStreetAddress1($street1Value);
|
||||
}
|
||||
$address->setValidFrom(new \DateTime('today'));
|
||||
|
||||
$person->addAddress($address);
|
||||
}
|
||||
|
||||
return $person;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $row
|
||||
* @param $headers
|
||||
* @return Center|mixed|null|object
|
||||
*/
|
||||
protected function getCenter($row, $headers)
|
||||
{
|
||||
if ($this->input->hasOption('force-center') && !empty($this->input->getOption('force-center'))) {
|
||||
@ -606,6 +637,10 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $centerName
|
||||
* @return Center|mixed|null|object
|
||||
*/
|
||||
protected function guessCenter($centerName)
|
||||
{
|
||||
if (!\array_key_exists('_center_picked', $this->cacheAnswersMapping)) {
|
||||
@ -673,6 +708,11 @@ EOF
|
||||
return $center;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $postalCode
|
||||
* @param $locality
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function guessPostalCode($postalCode, $locality)
|
||||
{
|
||||
if (!\array_key_exists('_postal_code_picked', $this->cacheAnswersMapping)) {
|
||||
@ -738,9 +778,15 @@ EOF
|
||||
return $pc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Person $person
|
||||
* @param $value
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processBirthdate(Person $person, $value)
|
||||
{
|
||||
if (empty($value)) { return; }
|
||||
|
||||
$date = $this->processDate($value, $this->input->getOption('birthdate_format'));
|
||||
|
||||
if ($date instanceof \DateTime) {
|
||||
@ -763,8 +809,15 @@ EOF
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Person $person
|
||||
* @param $value
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processClosingDate(Person $person, $value)
|
||||
{
|
||||
if (empty($value)) { return; }
|
||||
|
||||
// we skip if the opening date is now (or after yesterday)
|
||||
/* @var $period \Chill\PersonBundle\Entity\AccompanyingPeriod */
|
||||
$period = $person->getCurrentAccompanyingPeriod();
|
||||
@ -797,6 +850,11 @@ EOF
|
||||
$value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Person $person
|
||||
* @param $row
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processingCustomFields(Person $person, $row)
|
||||
{
|
||||
/* @var $factory \Symfony\Component\Form\FormFactory */
|
||||
@ -836,9 +894,10 @@ EOF
|
||||
|
||||
/**
|
||||
* Process a text type on a custom field
|
||||
*
|
||||
*
|
||||
* @param type $value
|
||||
* @param \Chill\PersonBundle\Command\Symfony\Component\Form\FormInterface $form
|
||||
* @param \Symfony\Component\Form\FormInterface $form
|
||||
* @return type
|
||||
*/
|
||||
protected function processTextType(
|
||||
$value,
|
||||
@ -861,14 +920,15 @@ EOF
|
||||
|
||||
/**
|
||||
* Process a custom field choice.
|
||||
*
|
||||
*
|
||||
* The method try to guess if the result exists amongst the text of the possible
|
||||
* choices. If the texts exists, then this is picked. Else, ask the user.
|
||||
*
|
||||
*
|
||||
* @param string $value
|
||||
* @param \Symfony\Component\Form\FormInterface $form
|
||||
* @param \Chill\CustomFieldsBundle\Entity\CustomField $cf
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processChoiceType(
|
||||
$value,
|
||||
@ -982,11 +1042,12 @@ EOF
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to collect the possibles answer from a ChoiceType (or
|
||||
* Recursive method to collect the possibles answer from a ChoiceType (or
|
||||
* its inherited types).
|
||||
*
|
||||
*
|
||||
* @param \Symfony\Component\Form\FormInterface $form
|
||||
* @return array where
|
||||
* @return array where
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function collectChoicesAnswers($choices)
|
||||
{
|
||||
@ -1011,8 +1072,11 @@ EOF
|
||||
return $answers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @param $formats
|
||||
* @return bool|\DateTime
|
||||
*/
|
||||
protected function processDate($value, $formats)
|
||||
{
|
||||
$possibleFormats = explode("|", $formats);
|
||||
@ -1046,6 +1110,4 @@ EOF
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -44,10 +44,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
$configuration = new Configuration();
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
// set configuration for double metaphone
|
||||
$container->setParameter('cl_chill_person.search.use_double_metaphone',
|
||||
$config['search']['use_double_metaphone']);
|
||||
|
||||
// set configuration for validation
|
||||
$container->setParameter('chill_person.validation.birtdate_not_before',
|
||||
$config['validation']['birthdate_not_after']);
|
||||
@ -67,6 +63,14 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
$loader->load('services/command.yml');
|
||||
$loader->load('services/actions.yml');
|
||||
$loader->load('services/form.yml');
|
||||
$loader->load('services/repository.yml');
|
||||
|
||||
// load service advanced search only if configure
|
||||
if ($config['search']['search_by_phone'] != 'never') {
|
||||
$loader->load('services/search_by_phone.yml');
|
||||
$container->setParameter('chill_person.search.search_by_phone',
|
||||
$config['search']['search_by_phone']);
|
||||
}
|
||||
|
||||
if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
|
||||
$loader->load('services/exports_accompanying_period.yml');
|
||||
|
@ -30,12 +30,14 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayNode('search')
|
||||
->canBeDisabled()
|
||||
->children()
|
||||
->booleanNode('use_double_metaphone')
|
||||
->defaultFalse()
|
||||
->end() // use_double_metaphone, parent = children for 'search'
|
||||
->booleanNode('use_trigrams')
|
||||
->defaultFalse()
|
||||
->end() // use_trigrams, parent = children of 'search'
|
||||
->enumNode('search_by_phone')
|
||||
->values(['always', 'on-domain', 'never'])
|
||||
->defaultValue('on-domain')
|
||||
->info('enable search by phone. \'always\' show the result '
|
||||
. 'on every result. \'on-domain\' will show the result '
|
||||
. 'only if the domain is given in the search box. '
|
||||
. '\'never\' disable this feature')
|
||||
->end()
|
||||
->end() //children for 'search', parent = array node 'search'
|
||||
->end() // array 'search', parent = children of root
|
||||
->arrayNode('validation')
|
||||
|
@ -1,14 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2014-2019 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
@trigger_error(__CLASS__." is deprecated since 2019-10-30. Use "
|
||||
.\Chill\PersonBundle\Repository\PersonRepository::class.' instead.',
|
||||
E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* PersonRepository
|
||||
*
|
||||
* This class was generated by the Doctrine ORM. Add your own custom
|
||||
* repository methods below.
|
||||
*
|
||||
* @deprecated since 2019-10-30. Use \Chill\PersonBundle\Repository\PersonRepository instead.
|
||||
*/
|
||||
class PersonRepository extends EntityRepository
|
||||
{
|
||||
|
100
Repository/PersonRepository.php
Normal file
100
Repository/PersonRepository.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2019 Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
/**
|
||||
* PersonRepository
|
||||
*
|
||||
*/
|
||||
class PersonRepository extends \Chill\PersonBundle\Entity\PersonRepository
|
||||
{
|
||||
public function findByPhone(
|
||||
string $phonenumber,
|
||||
$centers,
|
||||
$firstResult,
|
||||
$maxResults,
|
||||
array $only = ['mobile', 'phone']
|
||||
) {
|
||||
$qb = $this->createQueryBuilder('p');
|
||||
$qb->select('p');
|
||||
|
||||
$this->addByCenters($qb, $centers);
|
||||
$this->addPhoneNumber($qb, $phonenumber, $only);
|
||||
|
||||
$qb->setFirstResult($firstResult)
|
||||
->setMaxResults($maxResults)
|
||||
;
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function countByPhone(
|
||||
string $phonenumber,
|
||||
$centers,
|
||||
array $only = ['mobile', 'phone']
|
||||
): int
|
||||
{
|
||||
$qb = $this->createQueryBuilder('p');
|
||||
$qb->select('COUNT(p)');
|
||||
|
||||
$this->addByCenters($qb, $centers);
|
||||
$this->addPhoneNumber($qb, $phonenumber, $only);
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
protected function addPhoneNumber(QueryBuilder $qb, string $phonenumber, array $only)
|
||||
{
|
||||
if (count($only) === 0) {
|
||||
throw new \Exception("No array field to search");
|
||||
}
|
||||
|
||||
$phonenumber = $this->parsePhoneNumber($phonenumber);
|
||||
|
||||
$orX = $qb->expr()->orX();
|
||||
|
||||
if (\in_array('mobile', $only)) {
|
||||
$orX->add($qb->expr()->like("REPLACE(p.mobilenumber, ' ', '')", ':phonenumber'));
|
||||
}
|
||||
if (\in_array('phone', $only)) {
|
||||
$orX->add($qb->expr()->like("REPLACE(p.phonenumber, ' ', '')", ':phonenumber'));
|
||||
}
|
||||
|
||||
$qb->andWhere($orX);
|
||||
|
||||
$qb->setParameter('phonenumber', '%'.$phonenumber.'%');
|
||||
}
|
||||
|
||||
|
||||
protected function parsePhoneNumber($phonenumber): string
|
||||
{
|
||||
return \str_replace(' ', '', $phonenumber);
|
||||
}
|
||||
|
||||
protected function addByCenters(QueryBuilder $qb, array $centers)
|
||||
{
|
||||
if (count($centers) > 0) {
|
||||
$qb->andWhere($qb->expr()->in('p.center', ':centers'));
|
||||
$qb->setParameter('centers', $centers);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ Chill\PersonBundle\Entity\Person:
|
||||
indexes:
|
||||
person_names:
|
||||
columns: [firstName, lastName]
|
||||
repositoryClass: Chill\PersonBundle\Entity\PersonRepository
|
||||
repositoryClass: Chill\PersonBundle\Repository\PersonRepository
|
||||
fields:
|
||||
id:
|
||||
type: integer
|
||||
|
@ -47,9 +47,3 @@ services:
|
||||
tags:
|
||||
- { name: form.type, alias: chill_personbundle_person_creation }
|
||||
|
||||
|
||||
chill.person.repository.person:
|
||||
class: Chill\PersonBundle\Entity\PersonRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments:
|
||||
- 'Chill\PersonBundle\Entity\Person'
|
||||
|
13
Resources/config/services/repository.yml
Normal file
13
Resources/config/services/repository.yml
Normal file
@ -0,0 +1,13 @@
|
||||
services:
|
||||
chill.person.repository.person:
|
||||
class: Chill\PersonBundle\Person\PersonRepository
|
||||
deprecated: the service '%service_id%' is deprecated since 2019-10-30 and will be removed soon. Use 'Chill\PersonBundle\Repository\PersonRepository' instead
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments:
|
||||
- 'Chill\PersonBundle\Entity\Person'
|
||||
|
||||
Chill\PersonBundle\Repository\PersonRepository:
|
||||
class: Chill\PersonBundle\Person\PersonRepository
|
||||
factory: ['@doctrine.orm.entity_manager', getRepository]
|
||||
arguments:
|
||||
- 'Chill\PersonBundle\Entity\Person'
|
11
Resources/config/services/search_by_phone.yml
Normal file
11
Resources/config/services/search_by_phone.yml
Normal file
@ -0,0 +1,11 @@
|
||||
services:
|
||||
Chill\PersonBundle\Search\PersonSearchByPhone:
|
||||
arguments:
|
||||
- '@Chill\PersonBundle\Repository\PersonRepository'
|
||||
- '@security.token_storage'
|
||||
- '@chill.main.security.authorization.helper'
|
||||
- '@chill_main.paginator_factory'
|
||||
- '@Symfony\Component\Templating\EngineInterface'
|
||||
- '%chill_person.search.search_by_phone%'
|
||||
tags:
|
||||
- { name: chill.search, alias: 'person_by_phone' }
|
30
Resources/migrations/Version20191106103452.php
Normal file
30
Resources/migrations/Version20191106103452.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Add an index for phonenumber and mobile number
|
||||
*/
|
||||
final class Version20191106103452 extends AbstractMigration
|
||||
{
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
$this->addSql("CREATE INDEX phonenumber_trgm_idx
|
||||
ON public.chill_person_person USING gin
|
||||
(phonenumber gin_trgm_ops)");
|
||||
|
||||
$this->addSql("CREATE INDEX mobilenumber_trgm_idx
|
||||
ON public.chill_person_person USING gin
|
||||
(mobilenumber gin_trgm_ops)");
|
||||
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
{
|
||||
$this->addSql("DROP INDEX phonenumber_trgm_idx");
|
||||
$this->addSql("DROP INDEX mobilenumber_trgm_idx");
|
||||
}
|
||||
}
|
5
Resources/public/sass/index.js
Normal file
5
Resources/public/sass/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
require('./phone-alt-solid.svg');
|
||||
require('./mobile-alt-solid.svg');
|
||||
require('./person_by_phonenumber.scss');
|
||||
|
||||
|
1
Resources/public/sass/mobile-alt-solid.svg
Normal file
1
Resources/public/sass/mobile-alt-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="mobile-alt" class="svg-inline--fa fa-mobile-alt fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M272 0H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h224c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zM160 480c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm112-108c0 6.6-5.4 12-12 12H60c-6.6 0-12-5.4-12-12V60c0-6.6 5.4-12 12-12h200c6.6 0 12 5.4 12 12v312z"></path></svg>
|
After Width: | Height: | Size: 516 B |
25
Resources/public/sass/person_by_phonenumber.scss
Normal file
25
Resources/public/sass/person_by_phonenumber.scss
Normal file
@ -0,0 +1,25 @@
|
||||
.person-list__--by-phonenumber {
|
||||
.person-list__--by-phonenumber__phones {
|
||||
ul {
|
||||
list-style: none inside;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
margin: 0.80rem;
|
||||
|
||||
img {
|
||||
vertical-align: baseline;
|
||||
height: 0.90rem;
|
||||
margin-right: 0.20rem;
|
||||
}
|
||||
pre {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
;
|
1
Resources/public/sass/phone-alt-solid.svg
Normal file
1
Resources/public/sass/phone-alt-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="phone-alt" class="svg-inline--fa fa-phone-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M497.39 361.8l-112-48a24 24 0 0 0-28 6.9l-49.6 60.6A370.66 370.66 0 0 1 130.6 204.11l60.6-49.6a23.94 23.94 0 0 0 6.9-28l-48-112A24.16 24.16 0 0 0 122.6.61l-104 24A24 24 0 0 0 0 48c0 256.5 207.9 464 464 464a24 24 0 0 0 23.4-18.6l24-104a24.29 24.29 0 0 0-14.01-27.6z"></path></svg>
|
After Width: | Height: | Size: 502 B |
@ -93,6 +93,7 @@ Reset: 'Remise à zéro'
|
||||
'%nb% person with similar name. Please verify that this is a new person': '{1} Une personne a un nom similaire. Vérifiez qu''il ne s''agit pas d''elle. | ]1, Inf] %nb% personnes ont un nom similaire. Vérifiez qu''il ne s''agit pas de l''une d''elles.'
|
||||
'The person has been created': 'Le dossier a été créé'
|
||||
'Person search results': 'Recherche de personnes'
|
||||
Person search results by phonenumber: Recherche de personnes par numéro de téléphone
|
||||
'Search within persons': 'Recherche parmi les personnes'
|
||||
'%total% persons matching the search pattern:': '{0} Aucune personne ne correspond aux termes de recherche : | {1} Une personne a été trouvée par la recherche : | ]1,Inf] %total% personnes correspondent aux termes de recherche :'
|
||||
'Last opening since %last_opening%': 'Dernière ouverture le %last_opening%.'
|
||||
|
@ -33,6 +33,7 @@
|
||||
<th class="chill-red">{% trans %}Name{% endtrans %}</th>
|
||||
<th class="chill-green">{% trans %}Date of birth{% endtrans %}</th>
|
||||
<th class="chill-orange">{% trans %}Nationality{% endtrans %}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -63,6 +64,14 @@
|
||||
{{ 'Without nationality'|trans }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li><a class="sc-button bt-show" href="{{ path('chill_person_view', { person_id : person.getId }) }}"></a></li>
|
||||
{% if is_granted('CHILL_PERSON_UPDATE', person) %}
|
||||
<li><a class="sc-button bt-update" href="{{ path('chill_person_general_edit', { person_id : person.getId }) }}"></a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -76,11 +85,14 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if search_name != "person_similarity" %}
|
||||
<li>
|
||||
<a href="{{ path('chill_main_advanced_search', { "name": search_name, "q": pattern } ) }}" class="sc-button bt-action">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> {{ 'Advanced search'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if preview == true and persons|length < total %}
|
||||
<li>
|
||||
<a href="{{ path('chill_main_search', { "name": search_name|default('abcd'), "q" : pattern }) }}" class="sc-button">
|
||||
|
108
Resources/views/Person/list_by_phonenumber.html.twig
Normal file
108
Resources/views/Person/list_by_phonenumber.html.twig
Normal file
@ -0,0 +1,108 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
<h2>{{ title|default('Person search results by phonenumber')|trans }}</h2>
|
||||
|
||||
<p>
|
||||
{{ '%total% persons matching the search pattern:'|transchoice( total, { '%total%' : total}) }}
|
||||
<a href="{{ path('chill_main_advanced_search', { "name": search_name, "q": pattern } ) }}" class="sc-button button-small">
|
||||
<i class="fa fa-search" aria-hidden="true"></i> {{ pattern }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p>{{ 'Results %start%-%end% of %total%'|trans({ '%start%' : start, '%end%': start + persons|length, '%total%' : total } ) }}</p>
|
||||
|
||||
{% if persons|length > 0 %}
|
||||
|
||||
<table class="person-list__--by-phonenumber">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="chill-red">{% trans %}Name{% endtrans %}</th>
|
||||
<th class="chill-green">{% trans %}Date of birth{% endtrans %}</th>
|
||||
<th class="chill-orange">{% trans %}Phonenumber{% endtrans %}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in persons %}
|
||||
<tr>
|
||||
<td>
|
||||
{% set is_open = person.isOpen() %}
|
||||
<a href="{{ path('chill_person_view', { person_id : person.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 %}>
|
||||
{{person.firstName}} {{person.lastName}}
|
||||
{% spaceless %}
|
||||
{% if chill_person.fields.accompanying_period == 'visible' %}
|
||||
{% if is_open == false %}
|
||||
<i class="fa fa-folder" ></i>
|
||||
{% else %}
|
||||
<i class="fa fa-folder-open" ></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if person.birthdate is not null %}{{person.birthdate|localizeddate('long', 'none', app.request.locale) }}{% else %}{{ 'Unknown date of birth'|trans }}{% endif %}
|
||||
</td>
|
||||
<td class="person-list__--by-phonenumber__phones">
|
||||
<ul>
|
||||
{% if person.phonenumber is not empty %}
|
||||
<li>
|
||||
<a href="tel:{{ person.phonenumber }}"><img src="{{ asset('build/images/mobile-alt-solid.svg') }}"> <pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if person.mobilenumber is not empty%}
|
||||
<li>
|
||||
<a href="tel:{{ person.mobilenumber }}"><img src="{{ asset('build/images/phone-alt-solid.svg') }}"> <pre>{{ person.mobilenumber|chill_format_phonenumber }}</pre></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li><a class="sc-button bt-show" href="{{ path('chill_person_view', { person_id : person.getId }) }}"></a></li>
|
||||
{% if is_granted('CHILL_PERSON_UPDATE', person) %}
|
||||
<li><a class="sc-button bt-update" href="{{ path('chill_person_general_edit', { person_id : person.getId }) }}"></a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_PERSON_CREATE') %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_new') }}" class="sc-button bt-create">
|
||||
{{ 'Add a person'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if preview == true and persons|length < total %}
|
||||
<li>
|
||||
<a href="{{ path('chill_main_search', { "name": search_name|default('abcd'), "q" : pattern }) }}" class="sc-button">
|
||||
{{ 'See all results'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if preview == false %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
|
@ -206,13 +206,13 @@ This view should receive those arguments:
|
||||
{%- if chill_person.fields.phonenumber == 'visible' -%}
|
||||
<dl>
|
||||
<dt>{{ 'Phonenumber'|trans }} :</dt>
|
||||
<dd>{% if person.phonenumber is not empty %}<pre>{{ person.phonenumber}}</pre>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
|
||||
<dd>{% if person.phonenumber is not empty %}<a href="tel:{{ person.phonenumber }}"><pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
{%- if chill_person.fields.mobilenumber == 'visible' -%}
|
||||
<dl>
|
||||
<dt>{{ 'Mobilenumber'|trans }} :</dt>
|
||||
<dd>{% if person.mobilenumber is not empty %}<pre>{{ person.mobilenumber}}</pre>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
|
||||
<dd>{% if person.mobilenumber is not empty %}<a href="tel:{{ person.mobilenumber }}"><pre>{{ person.mobilenumber|chill_format_phonenumber }}</pre></a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
{%- if chill_person.fields.contact_info == 'visible' -%}
|
||||
|
133
Search/PersonSearchByPhone.php
Normal file
133
Search/PersonSearchByPhone.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2014-2019, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\PersonBundle\Search;
|
||||
|
||||
use Chill\MainBundle\Search\AbstractSearch;
|
||||
use Chill\PersonBundle\Repository\PersonRepository;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\MainBundle\Search\SearchInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
class PersonSearchByPhone extends AbstractSearch
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* @var PersonRepository
|
||||
*/
|
||||
private $personRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $tokenStorage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
private $helper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var PaginatorFactory
|
||||
*/
|
||||
protected $paginatorFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $activeByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var Templating
|
||||
*/
|
||||
protected $engine;
|
||||
|
||||
const NAME = 'phone';
|
||||
|
||||
public function __construct(
|
||||
PersonRepository $personRepository,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
AuthorizationHelper $helper,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
EngineInterface $engine,
|
||||
$activeByDefault)
|
||||
{
|
||||
$this->personRepository = $personRepository;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->helper = $helper;
|
||||
$this->paginatorFactory = $paginatorFactory;
|
||||
$this->engine = $engine;
|
||||
$this->activeByDefault = $activeByDefault === 'always';
|
||||
}
|
||||
|
||||
public function getOrder(): int
|
||||
{
|
||||
return 110;
|
||||
}
|
||||
|
||||
public function isActiveByDefault(): bool
|
||||
{
|
||||
return $this->activeByDefault;
|
||||
}
|
||||
|
||||
public function renderResult(array $terms, $start = 0, $limit = 50, $options = array(), $format = 'html')
|
||||
{
|
||||
$phonenumber = $terms['_default'];
|
||||
$centers = $this->helper->getReachableCenters($this->tokenStorage
|
||||
->getToken()->getUser(), new Role(PersonVoter::SEE));
|
||||
$total = $this->personRepository
|
||||
->countByPhone($phonenumber, $centers);
|
||||
$persons = $this->personRepository
|
||||
->findByPhone($phonenumber, $centers, $start, $limit)
|
||||
;
|
||||
$paginator = $this->paginatorFactory
|
||||
->create($total);
|
||||
|
||||
return $this->engine->render('ChillPersonBundle:Person:list_by_phonenumber.html.twig',
|
||||
array(
|
||||
'persons' => $persons,
|
||||
'pattern' => $this->recomposePattern($terms, array(), $terms['_domain'] ?? self::NAME),
|
||||
'phonenumber' => $phonenumber,
|
||||
'total' => $total,
|
||||
'start' => $start,
|
||||
'search_name' => self::NAME,
|
||||
'preview' => $options[SearchInterface::SEARCH_PREVIEW_OPTION],
|
||||
'paginator' => $paginator
|
||||
));
|
||||
}
|
||||
|
||||
public function supports($domain, $format): bool
|
||||
{
|
||||
return $domain === 'phone' && $format === 'html';
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user