diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb8fa1c1..7ffddd110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,3 +77,5 @@ CRUD-init branch - create CRUD - add the ability to add alt names to persons - [UI] set action button bottom of edit form according to crud template +- [closing motive] add an hierarchy for closing motives ; +- [closing motive] Add an admin section for closing motives ; diff --git a/Controller/AdminClosingMotiveController.php b/Controller/AdminClosingMotiveController.php new file mode 100644 index 000000000..e6497fe17 --- /dev/null +++ b/Controller/AdminClosingMotiveController.php @@ -0,0 +1,34 @@ +query->has('parent_id')) { + $parentId = $request->query->getInt('parent_id'); + + $parent = $this->getDoctrine()->getManager() + ->getRepository($this->getEntityClass()) + ->find($parentId); + + if (NULL === $parent) { + throw $this->createNotFoundException('parent id not found'); + } + + $entity->setParent($parent); + } + + return $entity; + } +} diff --git a/Controller/AdminController.php b/Controller/AdminController.php new file mode 100644 index 000000000..c0ea5f027 --- /dev/null +++ b/Controller/AdminController.php @@ -0,0 +1,20 @@ +render('ChillPersonBundle:Admin:index.html.twig', array( + // ... + )); + } + +} diff --git a/DependencyInjection/ChillPersonExtension.php b/DependencyInjection/ChillPersonExtension.php index ace88e01d..0a0ed3827 100644 --- a/DependencyInjection/ChillPersonExtension.php +++ b/DependencyInjection/ChillPersonExtension.php @@ -135,6 +135,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $this->prependRoleHierarchy($container); $this->prependHomepageWidget($container); $this->prependDoctrineDQL($container); + $this->prependCruds($container); $bundles = $container->getParameter('kernel.bundles'); //add ChillMain to assetic-enabled bundles @@ -240,4 +241,33 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac ) )); } + + protected function prependCruds(ContainerBuilder $container) + { + $container->prependExtensionConfig('chill_main', [ + 'cruds' => [ + [ + 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive::class, + 'name' => 'closing_motive', + 'base_path' => '/admin/closing-motive', + 'form_class' => \Chill\PersonBundle\Form\ClosingMotiveType::class, + 'controller' => \Chill\PersonBundle\Controller\AdminClosingMotiveController::class, + 'actions' => [ + 'index' => [ + 'template' => '@ChillPerson/ClosingMotive/index.html.twig', + 'role' => 'ROLE_ADMIN' + ], + 'new' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillPerson/ClosingMotive/new.html.twig', + ], + 'edit' => [ + 'role' => 'ROLE_ADMIN', + 'template' => '@ChillPerson/ClosingMotive/edit.html.twig', + ] + ] + ] + ] + ]); + } } diff --git a/Entity/AccompanyingPeriod/ClosingMotive.php b/Entity/AccompanyingPeriod/ClosingMotive.php index 44cbeb1aa..dfd831a00 100644 --- a/Entity/AccompanyingPeriod/ClosingMotive.php +++ b/Entity/AccompanyingPeriod/ClosingMotive.php @@ -2,7 +2,7 @@ /* * - * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, + * Copyright (C) 2014-2020, Champs Libres Cooperative SCRLFS, * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -20,6 +20,8 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; +use Doctrine\Common\Collections\Collection; + /** * ClosingMotive give an explanation why we closed the Accompanying period */ @@ -39,9 +41,32 @@ class ClosingMotive * * @var boolean */ - private $active; - - + private $active = true; + + /** + * + * @var self + */ + private $parent = null; + + /** + * child Accompanying periods + * + * @var Collection + */ + private $children; + + /** + * + * @var float + */ + private $ordering = 0.0; + + public function __construct() + { + $this->children = new \Doctrine\Common\Collections\ArrayCollection(); + } + /** * Get id * @@ -76,17 +101,104 @@ class ClosingMotive return $this->name; } - public function isActive() + public function isActive(): bool { return $this->active; } - public function setActive($active) + public function setActive(bool $active) { $this->active = $active; + + if ($this->active === FALSE) { + foreach ($this->getChildren() as $child) { + $child->setActive(FALSE); + } + } + return $this; } + public function getParent() + { + return $this->parent; + } + + public function getChildren(): Collection + { + return $this->children; + } + + public function setParent(?ClosingMotive $parent): ClosingMotive + { + $this->parent = $parent; + + if (NULL !== $parent) { + //$parent->addChildren($this); + } + + return $this; + } + + public function setChildren(Collection $children): ClosingMotive + { + $this->children = $children; + + return $this; + } + + public function addChildren(ClosingMotive $child): ClosingMotive + { + if ($this->children->contains($child)) { + return $this; + } + + $this->children->add($child); + $child->setParent($this); + + return $this; + } + + public function removeChildren(ClosingMotive $child): ClosingMotive + { + if ($this->children->removeElement($child)) { + $child->setParent(null); + } + + return $this; + } + + public function getOrdering(): float + { + return $this->ordering; + } + + public function setOrdering(float $ordering) + { + $this->ordering = $ordering; + + return $this; + } + + public function isChild(): bool + { + return $this->parent !== null; + } + + public function isParent(): bool + { + return $this->children->count() > 0; + } + + public function isLeaf(): bool + { + return $this->children->count() === 0; + } + + public function hasParent(): bool + { + return $this->parent !== null; + } } diff --git a/Form/AccompanyingPeriodType.php b/Form/AccompanyingPeriodType.php index 1c195a5fc..eb29ffcc5 100644 --- a/Form/AccompanyingPeriodType.php +++ b/Form/AccompanyingPeriodType.php @@ -14,8 +14,7 @@ use Symfony\Component\Form\Extension\Core\Type\DateType; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\MainBundle\Form\Type\UserPickerType; use Symfony\Component\Security\Core\Role\Role; - -use Chill\PersonBundle\Form\Type\ClosingMotiveType; +use Chill\PersonBundle\Form\Type\ClosingMotivePickerType; class AccompanyingPeriodType extends AbstractType { @@ -72,7 +71,7 @@ class AccompanyingPeriodType extends AbstractType ) { $form->add('closingDate', DateType::class, array('required' => true, 'widget' => 'single_text', 'format' => 'dd-MM-yyyy')); - $form->add('closingMotive', ClosingMotiveType::class); + $form->add('closingMotive', ClosingMotivePickerType::class); } }); diff --git a/Form/ClosingMotiveType.php b/Form/ClosingMotiveType.php new file mode 100644 index 000000000..10bff895f --- /dev/null +++ b/Form/ClosingMotiveType.php @@ -0,0 +1,67 @@ + + * + * 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\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Chill\PersonBundle\Form\Type\ClosingMotivePickerType; +use Chill\MainBundle\Form\Type\TranslatableStringFormType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; + +/** + * + * + */ +class ClosingMotiveType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('name', TranslatableStringFormType::class, [ + 'label' => 'Nom' + ]) + ->add('active', CheckboxType::class, [ + 'label' => 'Actif ?', + 'required' => false + ]) + ->add('ordering', NumberType::class, [ + 'label' => 'Ordre d\'apparition', + 'required' => true, + 'scale' => 5 + ]) + ->add('parent', ClosingMotivePickerType::class, [ + 'label' => 'Parent', + 'required' => false, + 'placeholder' => 'closing_motive.any parent', + 'multiple' => false, + 'only_leaf' => false + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('class', \Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive::class) + ; + } +} + \ No newline at end of file diff --git a/Form/Type/ClosingMotiveType.php b/Form/Type/ClosingMotivePickerType.php similarity index 50% rename from Form/Type/ClosingMotiveType.php rename to Form/Type/ClosingMotivePickerType.php index 00285582a..209153d46 100644 --- a/Form/Type/ClosingMotiveType.php +++ b/Form/Type/ClosingMotivePickerType.php @@ -9,13 +9,15 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; +use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension; +use Symfony\Component\OptionsResolver\Options; +use Chill\PersonBundle\Repository\ClosingMotiveRepository; /** * A type to add a closing motive * - * @author Julien Fastré */ -class ClosingMotiveType extends AbstractType +class ClosingMotivePickerType extends AbstractType { /** @@ -24,9 +26,26 @@ class ClosingMotiveType extends AbstractType */ protected $translatableStringHelper; - public function __construct(TranslatableStringHelper $translatableStringHelper) - { + /** + * + * @var ChillEntityRenderExtension + */ + protected $entityRenderExtension; + + /** + * + * @var ClosingMotiveRepository + */ + protected $repository; + + public function __construct( + TranslatableStringHelper $translatableStringHelper, + ChillEntityRenderExtension $chillEntityRenderExtension, + ClosingMotiveRepository $closingMotiveRepository + ) { $this->translatableStringHelper = $translatableStringHelper; + $this->entityRenderExtension = $chillEntityRenderExtension; + $this->repository = $closingMotiveRepository; } public function getBlockPrefix() @@ -46,11 +65,17 @@ class ClosingMotiveType extends AbstractType 'empty_data' => null, 'placeholder' => 'Choose a motive', 'choice_label' => function(ClosingMotive $cm) { - return $this->translatableStringHelper - ->localize($cm->getName()); - } + return $this->entityRenderExtension->renderString($cm); + }, + 'only_leaf' => true ) ); + + $resolver + ->setAllowedTypes('only_leaf', 'bool') + ->setNormalizer('choices', function (Options $options) { + return $this->repository->getActiveClosingMotive($options['only_leaf']); + }); } } diff --git a/Menu/AdminMenuBuilder.php b/Menu/AdminMenuBuilder.php new file mode 100644 index 000000000..e8a73d2ab --- /dev/null +++ b/Menu/AdminMenuBuilder.php @@ -0,0 +1,61 @@ + + * + * 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\Menu; + +use Chill\MainBundle\Routing\LocalMenuBuilderInterface; +use Knp\Menu\MenuItem; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Chill\PersonBundle\Security\Authorization\PersonVoter; + +/** + * + * + */ +class AdminMenuBuilder implements LocalMenuBuilderInterface +{ + /** + * + * @var AuthorizationCheckerInterface + */ + protected $authorizationChecker; + + public function __construct(AuthorizationCheckerInterface $authorizationChecker) + { + $this->authorizationChecker = $authorizationChecker; + } + + + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) { + return; + } + + $menu->addChild('Person', [ + 'route' => 'chill_person_admin' + ]) + ->setExtras([ + 'order' => 20 + ]); + } + + public static function getMenuIds(): array + { + return [ 'admin_section' ]; + } +} diff --git a/Repository/ClosingMotiveRepository.php b/Repository/ClosingMotiveRepository.php new file mode 100644 index 000000000..279935da9 --- /dev/null +++ b/Repository/ClosingMotiveRepository.php @@ -0,0 +1,52 @@ + + * + * 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\Repository; + +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query\ResultSetMappingBuilder; + +/** + * Entity repository for closing motives + * + */ +class ClosingMotiveRepository extends \Doctrine\ORM\EntityRepository +{ + public function getActiveClosingMotive(bool $onlyLeaf = true) + { + $rsm = new ResultSetMappingBuilder($this->getEntityManager()); + $rsm->addRootEntityFromClassMetadata($this->getClassName(), 'cm'); + + $sql = "SELECT ".(string) $rsm." + FROM chill_person_closingmotive AS cm + WHERE + active IS TRUE "; + + if ($onlyLeaf) { + $sql .= "AND cm.id NOT IN ( + SELECT DISTINCT parent_id FROM chill_person_closingmotive WHERE parent_id IS NOT NULL + )"; + } + + $sql .= " ORDER BY cm.ordering ASC"; + + return $this->_em + ->createNativeQuery($sql, $rsm) + ->getResult() + ; + } +} diff --git a/Repository/PersonAltNameRepository.php b/Repository/PersonAltNameRepository.php new file mode 100644 index 000000000..315cee94f --- /dev/null +++ b/Repository/PersonAltNameRepository.php @@ -0,0 +1,13 @@ + + Les motifs de clotûre donnent des indications sur la fermeture + d'une période d'accompagnement. + +accompanying_period: + dates: Période + dates_from_%opening_date%: Ouvert depuis le %opening_date% + dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date% + \ No newline at end of file diff --git a/Resources/views/AccompanyingPeriod/list.html.twig b/Resources/views/AccompanyingPeriod/list.html.twig index bf711919d..b3c5a9b0c 100644 --- a/Resources/views/AccompanyingPeriod/list.html.twig +++ b/Resources/views/AccompanyingPeriod/list.html.twig @@ -8,10 +8,9 @@ - - + {% if chill_accompanying_periods.fields.user == 'visible' %} - + {% endif %} @@ -21,14 +20,17 @@ {% for accompanying_period in accompanying_periods %} - - + {% if chill_accompanying_periods.fields.user == 'visible' %} {% endif %} + + + + {% endblock %} + + {% block table_entities_tbody %} + {% for entity in entities %} + + + + + + + {% endfor %} + {% endblock %} + {% endembed %} +{% endblock %} \ No newline at end of file diff --git a/Resources/views/ClosingMotive/new.html.twig b/Resources/views/ClosingMotive/new.html.twig new file mode 100644 index 000000000..b430411a8 --- /dev/null +++ b/Resources/views/ClosingMotive/new.html.twig @@ -0,0 +1,11 @@ +{% extends '@ChillMain/Admin/layout.html.twig' %} + +{% block title %} +{% include('@ChillMain/CRUD/_new_title.html.twig') %} +{% endblock %} + +{% block admin_content %} +{% embed '@ChillMain/CRUD/_new_content.html.twig' %} + {% block content_form_actions_save_and_show %}{% endblock %} +{% endembed %} +{% endblock %} diff --git a/Templating/Entity/ClosingMotiveRender.php b/Templating/Entity/ClosingMotiveRender.php new file mode 100644 index 000000000..8b54fca02 --- /dev/null +++ b/Templating/Entity/ClosingMotiveRender.php @@ -0,0 +1,75 @@ + '; + + /** + * + * @var TranslatableStringHelper + */ + private $translatableStringHelper; + + public function __construct(TranslatableStringHelper $translatableStringHelper) + { + $this->translatableStringHelper = $translatableStringHelper; + } + + public function renderBox($entity, array $options): string + { + return + $this->getDefaultOpeningBox('closing-motive'). + $this->renderString($entity, $options). + $this->getDefaultClosingBox() + ; + } + + /** + * + * @param ClosingMotive $entity + * @param array $options + * @return string + */ + public function renderString($entity, array $options): string + { + return $this->renderStringRecursive($entity, + '', //$this->translatableStringHelper->localize($entity->getName()), + $options); + } + + protected function renderStringRecursive(ClosingMotive $motive, $existing, array $options) + { + $newExisting = $this->translatableStringHelper->localize($motive->getName()); + + if ($motive->hasParent()) { + + if (!empty($existing)) { + $newExisting = $newExisting.self::SEPARATOR.$existing; + } + + return $this->renderStringRecursive($motive->getParent(), $newExisting, + $options); + } else { + if (!empty($existing)) { + return $newExisting.self::SEPARATOR.$existing; + } else { + return $newExisting; + } + } + } + + public function supports($entity, array $options): bool + { + return $entity instanceof ClosingMotive; + } +} diff --git a/Tests/Controller/AdminControllerTest.php b/Tests/Controller/AdminControllerTest.php new file mode 100644 index 000000000..04bbfdddc --- /dev/null +++ b/Tests/Controller/AdminControllerTest.php @@ -0,0 +1,16 @@ +request('GET', '/{_locale}/admin/person'); + } + +}
{{ 'Opening date'|trans }}{{ 'Closing date'|trans }}{{ 'accompanying_period.dates'|trans }}{{ 'Accompanying user'|trans }}{{ 'Accompanying user'|trans }}{{ 'Remark'|trans }}  
{{ accompanying_period.openingDate|localizeddate('long', 'none', app.request.locale) }}{% spaceless %} - {% if accompanying_period.isOpen %} - {{ 'Period opened'|trans }} + + {% if accompanying_period.isOpen == false %} + {{ 'accompanying_period.dates_from_%opening_date%'|trans({ '%opening_date%': accompanying_period.openingDate|localizeddate('long', 'none') } ) }} {% else %} - {{ accompanying_period.closingDate|localizeddate('long', 'none', app.request.locale) }} + {{ 'accompanying_period.dates_from_%opening_date%_to_%closing_date%'|trans({ + '%opening_date%': accompanying_period.openingDate|localizeddate('long', 'none'), + '%closing_date%': accompanying_period.closingDate|localizeddate('long', 'none')} + ) }} +
{{ accompanying_period.closingMotive|chill_entity_render_box }}
{% endif %} - {% endspaceless %}
{% if accompanying_period.user %} @@ -39,13 +41,7 @@ - {% if accompanying_period.remark is empty %} -

{{ 'No remark'|trans }}

- {% else %} -
- {{ accompanying_period.remark|nl2br }} -
- {% endif %} + {{ accompanying_period.remark|chill_print_or_message('No remark', 'blockquote') }}
    @@ -82,14 +78,15 @@
  • - - {{ 'Add an accompanying period in the past'|trans }} + + {{ 'Add an accompanying period in the past'|trans }}
  • {% if person.isOpen == false %}
  • - - {{'Begin a new accompanying period'|trans }} + + + {{'Begin a new accompanying period'|trans }}
  • {% endif %} diff --git a/Resources/views/Admin/index.html.twig b/Resources/views/Admin/index.html.twig new file mode 100644 index 000000000..9f34add85 --- /dev/null +++ b/Resources/views/Admin/index.html.twig @@ -0,0 +1,44 @@ +{# + * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, + / + * + * 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 . +#} + +{% extends "ChillMainBundle::Admin/layoutWithVerticalMenu.html.twig" %} + +{% block vertical_menu_content %} + {{ chill_menu('admin_person') }} +{% endblock %} + +{% block admin_content %} +
    +

    {{ 'Configuration of person bundle' |trans }}

    + +

    {{ 'person_admin.What would you like to configure ?'|trans }}

    + + +
    + +{% endblock %} \ No newline at end of file diff --git a/Resources/views/ClosingMotive/edit.html.twig b/Resources/views/ClosingMotive/edit.html.twig new file mode 100644 index 000000000..1f0f3de1f --- /dev/null +++ b/Resources/views/ClosingMotive/edit.html.twig @@ -0,0 +1,11 @@ +{% extends '@ChillMain/Admin/layout.html.twig' %} + +{% block title %} +{% include('@ChillMain/CRUD/_edit_title.html.twig') %} +{% endblock %} + +{% block admin_content %} +{% embed '@ChillMain/CRUD/_edit_content.html.twig' %} + {% block content_form_actions_view %}{% endblock %} +{% endembed %} +{% endblock %} \ No newline at end of file diff --git a/Resources/views/ClosingMotive/index.html.twig b/Resources/views/ClosingMotive/index.html.twig new file mode 100644 index 000000000..73cae6ce5 --- /dev/null +++ b/Resources/views/ClosingMotive/index.html.twig @@ -0,0 +1,32 @@ +{% extends '@ChillMain/Admin/layout.html.twig' %} + +{% block admin_content %} + {% embed '@ChillMain/CRUD/_index.html.twig' %} + {% block table_entities_thead_tr %} +
{{ 'Ordering'|trans }}{{ 'Label'|trans }}{{ 'Active'|trans }} 
{{ entity.ordering }}{{ entity|chill_entity_render_box }}{{ entity.active }} + +