From 25cad2f11d146cb8652caae71a09f5c484d5e1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 31 Jan 2017 12:18:07 +0100 Subject: [PATCH] add ListPerson Add / complete list of person. Some SQL function are added to allow to get the last address at a given date. --- Controller/PersonController.php | 30 ---- DependencyInjection/ChillPersonExtension.php | 31 ++++ Doctrine/DQL/AddressPart.php | 103 ++++++++++++ .../DQL/AddressPart/AddressPartAddressId.php | 33 ++++ .../AddressPart/AddressPartCountryCode.php | 33 ++++ .../DQL/AddressPart/AddressPartCountryId.php | 33 ++++ .../AddressPart/AddressPartCountryName.php | 33 ++++ .../AddressPart/AddressPartPostCodeCode.php | 33 ++++ .../DQL/AddressPart/AddressPartPostCodeId.php | 33 ++++ .../AddressPart/AddressPartPostCodeLabel.php | 33 ++++ .../AddressPart/AddressPartStreetAddress1.php | 33 ++++ .../AddressPart/AddressPartStreetAddress2.php | 33 ++++ .../DQL/AddressPart/AddressPartValidFrom.php | 33 ++++ Export/Export/ListPerson.php | 143 +++++++++++++++-- Resources/config/routing.yml | 9 -- Resources/config/services/exports.yml | 1 + .../migrations/Version20170117131924.php | 123 +++++++++++++++ Resources/translations/messages.fr.yml | 13 ++ Resources/views/Person/export.csv.twig | 71 --------- .../Controller/PersonControllerExportTest.php | 149 ------------------ Tests/Export/Export/ListPersonTest.php | 14 +- 21 files changed, 747 insertions(+), 270 deletions(-) create mode 100644 Doctrine/DQL/AddressPart.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartAddressId.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartCountryCode.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartCountryId.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartCountryName.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartPostCodeId.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php create mode 100644 Doctrine/DQL/AddressPart/AddressPartValidFrom.php create mode 100644 Resources/migrations/Version20170117131924.php delete mode 100644 Resources/views/Person/export.csv.twig delete mode 100644 Tests/Controller/PersonControllerExportTest.php diff --git a/Controller/PersonController.php b/Controller/PersonController.php index 655bbad1b..cbe0212c5 100644 --- a/Controller/PersonController.php +++ b/Controller/PersonController.php @@ -130,36 +130,6 @@ class PersonController extends Controller return $this->redirect($url); } } - - /** - * Return a csv file with all the persons - * - * @return A csv file with all the persons - */ - public function exportAction() - { - $em = $this->getDoctrine()->getManager(); - $chillSecurityHelper = $this->get('chill.main.security.authorization.helper'); - $user = $this->get('security.context')->getToken()->getUser(); - - $reachableCenters = $chillSecurityHelper->getReachableCenters($user, - new Role('CHILL_PERSON_SEE')); - - $personRepository = $em->getRepository('ChillPersonBundle:Person'); - $qb = $personRepository->createQueryBuilder('p'); - $qb->where($qb->expr()->in('p.center', ':centers')) - ->setParameter('centers', $reachableCenters); - $persons = $qb->getQuery()->getResult(); - - $response = $this->render('ChillPersonBundle:Person:export.csv.twig', - array( - 'persons' => $persons, - 'cf_group' => $this->getCFGroup())); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - $response->headers->set('Content-Disposition', 'attachment; filename="export_person.csv"'); - - return $response; - } public function newAction() { diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index 4121c737b..5fecfa9d7 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Chill\MainBundle\DependencyInjection\MissingBundleException; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\PersonBundle\Doctrine\DQL\AddressPart; /** * This is the class that loads and manages your bundle configuration @@ -91,6 +92,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac { $this->prependRoleHierarchy($container); $this->prependHomepageWidget($container); + $this->prependDoctrineDQL($container); $bundles = $container->getParameter('kernel.bundles'); //add ChillMain to assetic-enabled bundles @@ -162,4 +164,33 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ) )); } + + /** + * Add DQL function linked with person + * + * @param ContainerBuilder $container + */ + protected function prependDoctrineDQL(ContainerBuilder $container) + { + //add DQL function to ORM (default entity_manager) + + $container->prependExtensionConfig('doctrine', array( + 'orm' => array( + 'dql' => array( + 'string_functions' => array( + 'GET_PERSON_ADDRESS_ADDRESS_ID' => AddressPart\AddressPartAddressId::class, + 'GET_PERSON_ADDRESS_STREET_ADDRESS_1' => AddressPart\AddressPartStreetAddress1::class, + 'GET_PERSON_ADDRESS_STREET_ADDRESS_2' => AddressPart\AddressPartStreetAddress2::class, + 'GET_PERSON_ADDRESS_VALID_FROM' => AddressPart\AddressPartValidFrom::class, + 'GET_PERSON_ADDRESS_POSTCODE_LABEL' => AddressPart\AddressPartPostCodeLabel::class, + 'GET_PERSON_ADDRESS_POSTCODE_CODE' => AddressPart\AddressPartPostCodeCode::class, + 'GET_PERSON_ADDRESS_POSTCODE_ID' => AddressPart\AddressPartPostCodeId::class, + 'GET_PERSON_ADDRESS_COUNTRY_NAME' => AddressPart\AddressPartCountryName::class, + 'GET_PERSON_ADDRESS_COUNTRY_CODE' => AddressPart\AddressPartCountryCode::class, + 'GET_PERSON_ADDRESS_COUNTRY_ID' => AddressPart\AddressPartCountryId::class + ) + ) + ) + )); + } } diff --git a/Doctrine/DQL/AddressPart.php b/Doctrine/DQL/AddressPart.php new file mode 100644 index 000000000..baa3863d1 --- /dev/null +++ b/Doctrine/DQL/AddressPart.php @@ -0,0 +1,103 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; + +/** + * + * USAGE GET_ADDRESS_(person.id, :date, 'postcode') where part + * should be replace by the part of the address. + * + * This function return the current address part at the given date, for the + * given person (identified by his id) + * + * The aim of this function is to be used within reports + * + * @author Julien Fastré + */ +abstract class AddressPart extends FunctionNode +{ + public $fields = array( + 'address_id', + 'streetaddress1', + 'streetaddress2', + 'validfrom', + 'postcode_label', + 'postcode_code', + 'postcode_id', + 'country_name', + 'country_code', + 'country_id' + ); + + /** + * + * @var \Doctrine\ORM\Query\AST\Node + */ + private $pid; + + /** + * + * @var \Doctrine\ORM\Query\AST\Node + */ + private $date; + + /** + * + * @var \Doctrine\ORM\Query\AST\Node + */ + private $part; + + /** + * return the part of the address + * + * Should be one value of the "public" amongst + * 'address_id', 'streetaddress1', + * 'streetaddress2', 'validfrom', 'postcode_label', 'postcode_code', + * 'postcode_id', 'country_name', 'country_code', 'country_id' + * + * @return string + */ + abstract public function getPart(); + + public function getSql(SqlWalker $sqlWalker) + { + return sprintf( + 'get_last_address_%s(%s, %s)', + $this->getPart(), + $this->pid->dispatch($sqlWalker), + $this->date->dispatch($sqlWalker) + ); + } + + public function parse(Parser $parser) + { + $a = $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + // person id + $this->pid = $parser->SingleValuedPathExpression(); + $parser->match(Lexer::T_COMMA); + // date + $this->date = $parser->ArithmeticPrimary(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartAddressId.php b/Doctrine/DQL/AddressPart/AddressPartAddressId.php new file mode 100644 index 000000000..2f40796cf --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartAddressId.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartAddressId extends AddressPart +{ + public function getPart() + { + return 'address_id'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartCountryCode.php b/Doctrine/DQL/AddressPart/AddressPartCountryCode.php new file mode 100644 index 000000000..e997dbb91 --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartCountryCode.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartCountryCode extends AddressPart +{ + public function getPart() + { + return 'country_code'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartCountryId.php b/Doctrine/DQL/AddressPart/AddressPartCountryId.php new file mode 100644 index 000000000..ab0ab0da2 --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartCountryId.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartCountryId extends AddressPart +{ + public function getPart() + { + return 'country_id'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartCountryName.php b/Doctrine/DQL/AddressPart/AddressPartCountryName.php new file mode 100644 index 000000000..c37902909 --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartCountryName.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartCountryName extends AddressPart +{ + public function getPart() + { + return 'country_name'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php b/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php new file mode 100644 index 000000000..ce792cede --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartPostCodeCode extends AddressPart +{ + public function getPart() + { + return 'postcode_code'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php b/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php new file mode 100644 index 000000000..5400a04a7 --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartPostCodeId extends AddressPart +{ + public function getPart() + { + return 'postcode_id'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php b/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php new file mode 100644 index 000000000..ab36a8c46 --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartPostCodeLabel extends AddressPart +{ + public function getPart() + { + return 'postcode_label'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php b/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php new file mode 100644 index 000000000..c06cf2b07 --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartStreetAddress1 extends AddressPart +{ + public function getPart() + { + return 'streetaddress1'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php b/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php new file mode 100644 index 000000000..739120e5e --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartStreetAddress2 extends AddressPart +{ + public function getPart() + { + return 'streetaddress2'; + } +} diff --git a/Doctrine/DQL/AddressPart/AddressPartValidFrom.php b/Doctrine/DQL/AddressPart/AddressPartValidFrom.php new file mode 100644 index 000000000..4a366bf1a --- /dev/null +++ b/Doctrine/DQL/AddressPart/AddressPartValidFrom.php @@ -0,0 +1,33 @@ + + * + * 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 . + */ +namespace Chill\PersonBundle\Doctrine\DQL\AddressPart; + +use Chill\PersonBundle\Doctrine\DQL\AddressPart; + +/** + * + * + * @author Julien Fastré + */ +class AddressPartValidFrom extends AddressPart +{ + public function getPart() + { + return 'validfrom'; + } +} diff --git a/Export/Export/ListPerson.php b/Export/Export/ListPerson.php index 597ada954..1804a4895 100644 --- a/Export/Export/ListPerson.php +++ b/Export/Export/ListPerson.php @@ -15,7 +15,10 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Chill\PersonBundle\Entity\Person; +use Chill\CustomFieldsBundle\Entity\CustomField; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\CustomFieldsBundle\Service\CustomFieldProvider; /** * Render a list of peoples @@ -37,26 +40,35 @@ class ListPerson implements ListInterface protected $translator; /** - * + * * @var TranslatableStringHelper */ protected $translatableStringHelper; + /** + * + * @var CustomFieldProvider + */ + protected $customFieldProvider; + protected $fields = array( 'id', 'firstName', 'lastName', 'birthdate', 'placeOfBirth', 'gender', 'memo', 'email', 'phonenumber', - 'countryOfBirth', 'nationality' + 'countryOfBirth', 'nationality', 'address_street_address_1', + 'address_street_address_2', 'address_valid_from', 'address_postcode_label', + 'address_postcode_code', 'address_country_name', 'address_country_code' ); public function __construct( EntityManagerInterface $em, TranslatorInterface $translator, - TranslatableStringHelper $translatableStringHelper - ) - { + TranslatableStringHelper $translatableStringHelper, + CustomFieldProvider $customFieldProvider + ) { $this->entityManager = $em; $this->translator = $translator; $this->translatableStringHelper = $translatableStringHelper; + $this->customFieldProvider = $customFieldProvider; } /** @@ -66,11 +78,21 @@ class ListPerson implements ListInterface */ public function buildForm(FormBuilderInterface $builder) { + $choices = array_combine($this->fields, $this->fields); + + foreach ($this->getCustomFields() as $cf) { + $choices + [$this->translatableStringHelper->localize($cf->getName())] + = + $cf->getSlug(); + } + $builder->add('fields', ChoiceType::class, array( 'multiple' => true, 'expanded' => true, - 'choices' => array_combine($this->fields, $this->fields), + 'choices' => $choices, + 'choices_as_values' => true, 'label' => 'Fields to include in export', 'constraints' => [new Callback(array( 'callback' => function($selected, ExecutionContextInterface $context) { @@ -84,6 +106,25 @@ class ListPerson implements ListInterface )); } + + /** + * Get custom fields associated with person + * + * @return CustomField[] + */ + private function getCustomFields() + { + return $this->entityManager + ->createQuery("SELECT cf " + . "FROM ChillCustomFieldsBundle:CustomField cf " + . "JOIN cf.customFieldGroup g " + . "WHERE cf.type != :title AND g.entity LIKE :entity") + ->setParameters(array( + 'title' => 'title', + 'entity' => \addcslashes(Person::class, "\\") + )) + ->getResult(); + } /** * {@inheritDoc} @@ -163,13 +204,42 @@ class ListPerson implements ListInterface return $this->translatableStringHelper->localize( $country->getName()); }; - default: + case 'address_country_name': return function($value) use ($key) { - if ($value === '_header') { return \strtolower($key); } + if ($value === '_header') { return \strtolower($key); } - return $value; + if ($value === NULL) { + return ''; + } + return $this->translatableStringHelper->localize(json_decode($value, true)); + }; + default: + // for fields which are associated with person + if (in_array($key, $this->fields)) { + return function($value) use ($key) { + if ($value === '_header') { return \strtolower($key); } + + return $value; + + }; + } else { + // for fields which are custom fields + /* @var $cf CustomField */ + $cf = $this->entityManager + ->getRepository(CustomField::class) + ->findOneBy(array('slug' => $this->DQLToSlug($key))); + + return function($value) use ($cf) { + if ($value === '_header') { + return $this->translatableStringHelper->localize($cf->getName()); + } + + return $this->customFieldProvider + ->getCustomFieldByType($cf->getType()) + ->render(json_decode($value, true), $cf, 'csv'); }; + } } } @@ -182,7 +252,34 @@ class ListPerson implements ListInterface */ public function getQueryKeys($data) { - return $data['fields']; + $fields = array(); + + foreach ($data['fields'] as $key) { + if (in_array($key, $this->fields)) { + $fields[] = $key; + } else { + // this should be a slug from custom field, we have to clean it + $fields[] = $this->slugToDQL($key); + } + } + + return $fields; + } + + /** + * clean a slug to be usable by DQL + * + * @param string $slugsanitize + * @return string + */ + private function slugToDQL($slug) + { + return "cf____".\str_replace("-", "____", $slug); + } + + private function DQLToSlug($cleanedSlug) + { + return \str_replace("____", "-", \substr($cleanedSlug, 6)); } /** @@ -236,12 +333,38 @@ class ListPerson implements ListInterface case 'nationality': $qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f)); break; + case 'address_street_address_1': + case 'address_street_address_2': + case 'address_valid_from': + case 'address_postcode_label': + case 'address_postcode_code': + case 'address_country_name': + case 'address_country_code': + + $qb->addSelect(sprintf( + 'GET_PERSON_ADDRESS_%s(person.id, :address_date) AS %s', + // get the part after address_ + strtoupper(substr($f, 8)), + $f)); + $qb->setParameter('address_date', new \DateTime()); + break; default: $qb->addSelect(sprintf('person.%s as %s', $f, $f)); } } } + foreach ($this->getCustomFields() as $cf) { + if (in_array($cf->getSlug(), $data['fields'])) { + $slug = $this->slugToDQL($cf->getSlug()); + $qb->addSelect( + sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s', + $slug, $slug)); + $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug()); + //$qb->setParameter(sprintf('name%s', $slug), $cf->getSlug()); + } + } + $qb ->from('ChillPersonBundle:Person', 'person') ->join('person.center', 'center') diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index e377cf36c..6792ca184 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -92,15 +92,6 @@ chill_person_address_update: path: /{_locale}/person/{person_id}/address/{address_id}/update defaults: { _controller: ChillPersonBundle:PersonAddress:update } -chill_person_export: - path: /{_locale}/person/export/ - defaults: { _controller: ChillPersonBundle:Person:export } - options: - menus: - export: - order: 200 - label: Export persons - chill_person_timeline: path: /{_locale}/person/{person_id}/timeline defaults: { _controller: ChillPersonBundle:TimelinePerson:person } diff --git a/Resources/config/services/exports.yml b/Resources/config/services/exports.yml index b2fed1274..3d2fde74a 100644 --- a/Resources/config/services/exports.yml +++ b/Resources/config/services/exports.yml @@ -12,6 +12,7 @@ services: - "@doctrine.orm.entity_manager" - "@translator" - "@chill.main.helper.translatable_string" + - "@chill.custom_field.provider" tags: - { name: chill.export, alias: list_person } diff --git a/Resources/migrations/Version20170117131924.php b/Resources/migrations/Version20170117131924.php new file mode 100644 index 000000000..5b6e9e68a --- /dev/null +++ b/Resources/migrations/Version20170117131924.php @@ -0,0 +1,123 @@ + 'integer', + 'streetaddress1' => 'varchar(255)', + 'streetaddress2' => 'varchar(255)', + 'validfrom' => 'date', + 'postcode_label' => 'varchar(255)', + 'postcode_code' => 'varchar(100)', + 'postcode_id' => 'integer', + 'country_name' => 'json', + 'country_code' => 'varchar(3)', + 'country_id' => 'integer' + ); + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + $this->addSql(<<<'SQL' +CREATE OR REPLACE FUNCTION public.get_last_address ( + pid integer, + before_date date) +RETURNS TABLE( + person_id integer, + address_id integer, + streetaddress1 varchar(255), + streetaddress2 varchar(255), + validfrom date, + postcode_label varchar(255), + postcode_code varchar(100), + postcode_id integer, + country_name json, + country_code varchar(3), + country_id integer) + AS +$BODY$ +SELECT + pid AS person_id, + chill_main_address.id AS address_id, + chill_main_address.streetaddress1, + chill_main_address.streetaddress2, + chill_main_address.validfrom, + chill_main_postal_code.label, + chill_main_postal_code.code, + chill_main_postal_code.id AS postal_code_id, + country.name, + country.countrycode, + country.id AS country_id +FROM chill_main_address +JOIN ( + SELECT + chill_main_address.id AS address_id, + validfrom, + rank() OVER (PARTITION BY person_id ORDER BY validfrom DESC) as pos + FROM chill_person_persons_to_addresses + JOIN chill_main_address ON chill_person_persons_to_addresses.address_id = chill_main_address.id + WHERE person_id = pid + AND chill_main_address.validfrom <= before_date +) AS ranking ON ranking.address_id = chill_main_address.id +JOIN chill_main_postal_code ON chill_main_address.postcode_id = chill_main_postal_code.id +JOIN country ON chill_main_postal_code.country_id = country.id +WHERE ranking.pos = 1 +$BODY$ +LANGUAGE sql VOLATILE +COST 100; +SQL + ); + + // create function to get part of address + foreach ($this->fields as $var => $type) { + $this->addSql(sprintf(<<<'SQL' +CREATE OR REPLACE FUNCTION get_last_address_%s ( + pid integer, + before_date date) +RETURNS %s AS +$BODY$ +SELECT %s FROM get_last_address(pid, before_date) +$BODY$ +LANGUAGE sql volatile +COST 100; +SQL + , $var, $type, $var)); + } + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + // drop function to get parts of address + foreach ($this->fields as $var => $type) { + $this->addSql(<<addSQL(<<, - * - * 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 . - */ - -namespace Chill\PersonBundle\Tests\Controller; - -use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -use Symfony\Component\Security\Core\Role\Role; - -/** - * Tests for the export of a person - */ - -class PersonControllerExportTest extends WebTestCase -{ - /** @var \Doctrine\ORM\EntityManagerInterface The entity manager */ - private $em; - - /** - * Prepare the client and the entity manager. The client send a Gest requestion - * on the route chill_person_export. - */ - protected function setUp() - { - $this->client = static::createClient(array(), array( - 'PHP_AUTH_USER' => 'center a_social', - 'PHP_AUTH_PW' => 'password', - )); - - $exportUrl = $this->client->getContainer()->get('router')->generate('chill_person_export', - array('_locale' => 'fr')); - - $this->client->request('GET', $exportUrl); - - $this->em = $this->client->getContainer()->get('doctrine.orm.entity_manager'); - } - - /** - * Test the format of the exportAction : - * - check if the file has the csv format - * - check if each row as the same number of cell than the first row - * - check if the number of row of the csv file is the same than the number of persons - * - * @return The content of the export - */ - public function testExportActionFormat() - { - $response = $this->client->getResponse(); - - $this->assertTrue( - strpos($response->headers->get('Content-Type'),'text/csv') !== false, - 'The csv file is well received'); - - $content = $response->getContent(); - $rows = str_getcsv($content, "\n"); - $headerRow = array_pop($rows); - $header = str_getcsv($headerRow); - $headerSize = sizeof($header); - $numberOfRows = 0; - - foreach ($rows as $row) { - $rowContent = str_getcsv($row); - - $this->assertTrue( - sizeof($rowContent) == $headerSize, - 'Each row of the csv contains the good number of elements (' - . 'regarding to the first row'); - - $numberOfRows ++; - } - - - $chillSecurityHelper = $this->client->getContainer()->get('chill.main.security.authorization.helper'); - $user = $this->client->getContainer()->get('security.context')->getToken()->getUser(); - - $reachableCenters = $chillSecurityHelper->getReachableCenters($user, - new Role('CHILL_PERSON_SEE')); - - $personRepository = $this->em->getRepository('ChillPersonBundle:Person'); - $qb = $personRepository->createQueryBuilder('p'); - $qb->where($qb->expr()->in('p.center', ':centers')) - ->setParameter('centers', $reachableCenters); - $persons = $qb->getQuery()->getResult(); - - - $this->assertTrue( - $numberOfRows == (sizeof($persons)), - 'The csv file has a number of row equivalent than the number of ' - . 'person in the db' - ); - - return $content; - } - - - /** - * Check that the export file do not contain a person form center B - * - * @depends testExportActionFormat - */ - public function testExportActionNotContainingPersonFromCenterB($content) - { - $centerB = $this->em->getRepository('ChillMainBundle:Center') - ->findOneBy(array('name' => 'Center B')); - - $person = $this->em->getRepository('ChillPersonBundle:Person') - ->findOneBy(array('center' => $centerB)); - - $this->assertFalse(strpos($person->getFirstName(), $content)); - } - - /** - * Check that the export file contains information about a random person - * of center A - * @depends testExportActionFormat - */ - public function testExportActionContainsARandomPersonFromCenterA($content) - { - $centerA = $this->em->getRepository('ChillMainBundle:Center') - ->findOneBy(array('name' => 'Center A')); - - $person = $this->em->getRepository('ChillPersonBundle:Person') - ->findOneBy(array('center' => $centerA)); - - $this->assertContains($person->getFirstName(), $content); - $this->assertContains($person->getLastName(), $content); - $this->assertContains($person->getGender(), $content); - - $this->markTestIncomplete('Test other information of the person'); - } -} diff --git a/Tests/Export/Export/ListPersonTest.php b/Tests/Export/Export/ListPersonTest.php index 837d0da5f..da546bddc 100644 --- a/Tests/Export/Export/ListPersonTest.php +++ b/Tests/Export/Export/ListPersonTest.php @@ -44,6 +44,15 @@ class ListPersonTest extends AbstractExportTest $container = self::$kernel->getContainer(); $this->export = $container->get('chill.person.export.list_person'); + + // add a fake request with a default locale (used in translatable string) + $prophet = new \Prophecy\Prophet; + $request = $prophet->prophesize(); + $request->willExtend(\Symfony\Component\HttpFoundation\Request::class); + $request->getLocale()->willReturn('fr'); + + $container->get('request_stack') + ->push($request->reveal()); } /** @@ -62,7 +71,10 @@ class ListPersonTest extends AbstractExportTest array('fields' => ['id', 'birthdate', 'gender', 'memo', 'email', 'phonenumber']), array('fields' => ['firstName', 'lastName', 'phonenumber']), array('fields' => ['id', 'nationality']), - array('fields' => ['id', 'countryOfBirth']) + array('fields' => ['id', 'countryOfBirth']), + array('fields' => ['id', 'address_street_address_1', + 'address_street_address_2', 'address_valid_from', 'address_postcode_label', + 'address_postcode_code', 'address_country_name', 'address_country_code']) ); }