diff --git a/Controller/AccompanyingPeriodController.php b/Controller/AccompanyingPeriodController.php index b62a75f33..334da49b1 100644 --- a/Controller/AccompanyingPeriodController.php +++ b/Controller/AccompanyingPeriodController.php @@ -26,6 +26,10 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Form\AccompanyingPeriodType; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Doctrine\Common\Collections\Criteria; +use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; class AccompanyingPeriodController extends Controller { @@ -40,6 +44,9 @@ class AccompanyingPeriodController extends Controller public function createAction($person_id) { $person = $this->_getPerson($person_id); + + $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, + 'You are not allowed to update this person'); $accompanyingPeriod = new AccompanyingPeriod(new \DateTime()); $accompanyingPeriod->setClosingDate(new \DateTime()); @@ -48,7 +55,7 @@ class AccompanyingPeriodController extends Controller $accompanyingPeriod); $form = $this->createForm(new AccompanyingPeriodType(), - $accompanyingPeriod, array('period_action' => 'update')); + $accompanyingPeriod, array('period_action' => 'create')); $request = $this->getRequest(); @@ -65,7 +72,7 @@ class AccompanyingPeriodController extends Controller $em->flush(); $flashBag->add('success', $this->get('translator')->trans( - 'Period created!')); + 'A period has been created.')); return $this->redirect($this->generateUrl('chill_person_accompanying_period_list', array('person_id' => $person->getId()))); @@ -100,6 +107,10 @@ class AccompanyingPeriodController extends Controller } $person = $accompanyingPeriod->getPerson(); + + $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, + 'You are not allowed to update this person'); + $form = $this->createForm(new AccompanyingPeriodType(), $accompanyingPeriod, array('period_action' => 'update')); $request = $this->getRequest(); @@ -115,7 +126,7 @@ class AccompanyingPeriodController extends Controller $flashBag->add('success', $this->get('translator')->trans( - 'Period updating done')); + 'An accompanying period has been updated.')); return $this->redirect($this->generateUrl('chill_person_accompanying_period_list', array('person_id' => $person->getId()))); @@ -140,6 +151,9 @@ class AccompanyingPeriodController extends Controller public function closeAction($person_id) { $person = $this->_getPerson($person_id); + $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, + 'You are not allowed to update this person'); + if ($person->isOpen() === false) { $this->get('session')->getFlashBag() ->add('error', $this->get('translator') @@ -169,7 +183,7 @@ class AccompanyingPeriodController extends Controller if (count($errors) === 0) { $this->get('session')->getFlashBag() ->add('success', $this->get('translator') - ->trans('Period closed!', + ->trans('An accompanying period has been closed.', array('%name%' => $person->__toString()))); $this->getDoctrine()->getManager()->flush(); @@ -193,7 +207,7 @@ class AccompanyingPeriodController extends Controller $this->get('session')->getFlashBag() ->add('error', $this->get('translator') - ->trans('Pediod closing form is not valide') + ->trans('Pediod closing form is not valid') ); foreach ($form->getErrors() as $error) { @@ -233,6 +247,9 @@ class AccompanyingPeriodController extends Controller public function openAction($person_id) { $person = $this->_getPerson($person_id); + $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person, + 'You are not allowed to update this person'); + $request = $this->getRequest(); //in case the person is already open @@ -264,7 +281,7 @@ class AccompanyingPeriodController extends Controller if (count($errors) <= 0) { $this->get('session')->getFlashBag() ->add('success', $this->get('translator') - ->trans('Period %name% opened!', + ->trans('An accompanying period has been opened.', array('%name%' => $person->__toString()))); $this->getDoctrine()->getManager()->flush(); @@ -296,6 +313,54 @@ class AccompanyingPeriodController extends Controller 'accompanying_period' => $accompanyingPeriod)); } + public function reOpenAction($person_id, $period_id, Request $request) + { + $person = $this->_getPerson($person_id); + + $criteria = Criteria::create(); + $criteria->where($criteria->expr()->eq('id', $period_id)); + + /* @var $period AccompanyingPeriod */ + $period = $person->getAccompanyingPeriods() + ->matching($criteria) + ->first(); + + if ($period === NULL) { + throw $this->createNotFoundException('period not found'); + } + + $confirm = $request->query->getBoolean('confirm', false); + + if ($confirm === true && $period->canBeReOpened()) { + $period->reOpen(); + + $this->_validatePerson($person); + + $this->getDoctrine()->getManager()->flush(); + + $this->addFlash('success', $this->get('translator')->trans( + 'The period has been re-opened')); + + return $this->redirectToRoute('chill_person_accompanying_period_list', + array('person_id' => $person->getId())); + } elseif ($confirm === false && $period->canBeReOpened()) { + return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', array( + 'period' => $period, + 'person' => $person + )); + } else { + return (new Response()) + ->setStatusCode(Response::HTTP_BAD_REQUEST) + ->setContent("You cannot re-open this period"); + } + } + + /** + * + * @param int $id + * @return Person + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the person is not found + */ private function _getPerson($id) { $person = $this->getDoctrine()->getManager() ->getRepository('ChillPersonBundle:Person')->find($id); @@ -304,6 +369,9 @@ class AccompanyingPeriodController extends Controller throw $this->createNotFoundException('Person not found'); } + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, + "You are not allowed to see this person"); + return $person; } } diff --git a/Entity/AccompanyingPeriod.php b/Entity/AccompanyingPeriod.php index 396b92872..7ba651ff5 100644 --- a/Entity/AccompanyingPeriod.php +++ b/Entity/AccompanyingPeriod.php @@ -186,11 +186,36 @@ class AccompanyingPeriod return $this->closingMotive; } - public function setClosingMotive(AccompanyingPeriod\ClosingMotive $closingMotive) + public function setClosingMotive(AccompanyingPeriod\ClosingMotive $closingMotive = null) { $this->closingMotive = $closingMotive; return $this; } + + /** + * If the period can be reopened. + * + * This function test if the period is closed and if the period is the last + * for the associated person + * + * @return boolean + */ + public function canBeReOpened() + { + if ($this->isOpen() === true) { + return false; + } + + $periods = $this->getPerson()->getAccompanyingPeriodsOrdered(); + + return end($periods) === $this; + } + + public function reOpen() + { + $this->setClosingDate(null); + $this->setClosingMotive(null); + } /// VALIDATION function public function isDateConsistent(ExecutionContextInterface $context) { diff --git a/Entity/Person.php b/Entity/Person.php index ef8b4c989..9a41e2663 100644 --- a/Entity/Person.php +++ b/Entity/Person.php @@ -95,6 +95,7 @@ class Person implements HasCenterInterface { /** * @var boolean + * @deprecated */ private $proxyAccompanyingPeriodOpenState = false; //TO-DELETE ? @@ -168,11 +169,12 @@ class Person implements HasCenterInterface { } /** + * Return the opened accompanying period. * - * @return null|AccompanyingPeriod + * @return AccompanyingPeriod */ - public function getCurrentAccompanyingPeriod() { - if ($this->proxyAccompanyingPeriodOpenState === false) { + public function getOpenedAccompanyingPeriod() { + if ($this->isOpen() === false) { return null; } @@ -183,6 +185,17 @@ class Person implements HasCenterInterface { } } + /** + * Returns the opened accompanying period. + * + * @return AccompanyingPeriod + * @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead + */ + public function getCurrentAccompanyingPeriod() + { + return $this->getOpenedAccompanyingPeriod(); + } + /** * * @return \Doctrine\Common\Collections\ArrayCollection @@ -230,8 +243,20 @@ class Person implements HasCenterInterface { return $periods; } - public function isOpen() { - return $this->proxyAccompanyingPeriodOpenState; + /** + * check if the person is opened + * + * @return boolean + */ + public function isOpen() + { + foreach ($this->getAccompanyingPeriods() as $period) { + if ($period->isOpen()) { + return true; + } + } + + return false; } /** diff --git a/Form/AccompanyingPeriodType.php b/Form/AccompanyingPeriodType.php index 412ad06df..f1ba4a95d 100644 --- a/Form/AccompanyingPeriodType.php +++ b/Form/AccompanyingPeriodType.php @@ -7,6 +7,8 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\FormInterface; class AccompanyingPeriodType extends AbstractType { @@ -38,6 +40,8 @@ class AccompanyingPeriodType extends AbstractType //if the period_action is "close, should not be shown" ($options['period_action'] === 'close') OR + ($options['period_action'] === 'create') + OR ($options['period_action'] === 'update' AND !$accompanyingPeriod->isOpen()) ) { $form->add('closingDate', 'date', array('required' => true, @@ -65,7 +69,12 @@ class AccompanyingPeriodType extends AbstractType ->setRequired(array('period_action')) ->addAllowedTypes(array('period_action' => 'string')) ->addAllowedValues(array('period_action' => array( - 'update', 'open', 'close'))); + 'update', 'open', 'close', 'create'))); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['action'] = $options['period_action']; } /** diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index 927eb59e5..7566c0e98 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -70,6 +70,10 @@ chill_person_accompanying_period_open: path: /{_locale}/person/{person_id}/accompanying-period/open defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:open } +chill_person_accompanying_period_re_open: + path: /{_locale}/person/{person_id}/accompanying-period/{period_id}/re-open + defaults: { _controller: ChillPersonBundle:AccompanyingPeriod:reOpen } + chill_person_address_list: path: /{_locale}/person/{person_id}/address/list defaults: { _controller: ChillPersonBundle:PersonAddress:list } diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 18511b4d6..58fa95d82 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -63,13 +63,32 @@ Reset: 'Remise à zéro' 'Opening date': 'Date d''ouverture' 'Closing date': 'Date de fermeture' 'Period opened': 'Période ouverte' -'Close accompanying period': 'Clôre le dossier' -'Open accompanying period': 'Ouvrir le dossier' -'Create accompanying period': 'Nouvelle ouverture-fermeture à une autre date' +'Close accompanying period': 'Clôre la période' +'Open accompanying period': 'Ouvrir la période' +'Add an accompanying period in the past': Ajouter une période d'accompagnement dans le passé +The period has been re-opened: La période a été ré-ouverte +Begin a new accompanying period: Commencer une nouvelle période d'accompagnement +Create an accompanying period: Créer une période d'accompagnement +'A period has been created.': Une période d'accompagnement a été créée. +'Error! Period not created!': La période d'accompagnement n'a pas été créée. +Update accompanying period: Mettre à jour une période d'accompagnement +'An accompanying period has been updated.': Une période d'accompagnement a été mise à jour +'Error when updating the period': Erreur pendant la mise à jour de la période d'accompagnement. +'An accompanying period has been closed.': Une période d'accompagnement a été fermée. +'Error! Period not closed!': "Erreur: la période d'accompagnement n'a pas été fermée." +'An accompanying period has been opened.': Une période d'accompagnement a été ouverte. +'Period not opened': "La période d'accompagnement n'a pas été ouverte" +"Period not opened : form is invalid": "La période n'a pas été ouverte: le formulaire est invalide." 'Closing motive': 'Motif de clôture' 'Person details': 'Détails de la personne' 'Update details for %name%': 'Modifier détails de %name%' Accompanying period list: Périodes d'accompagnement +Choose a motive: Motif de fermeture +Re-open accompanying period: Ré-ouvrir la période d'accompagnement +Re-Open a period: Ré-ouvrir une période d'accompagnement +Are you sure you want to re-open this period ?: Êtes-vous sûr de vouloir ré-ouvrir cette période d'accompagnement ? +'The period has been re-opened': La période d'accompagnement a été ré-ouverte. +Pediod closing form is not valid: Le formulaire n'est pas valide # pickAPersonType Pick a person: Choisir une personne diff --git a/Resources/views/AccompanyingPeriod/form.html.twig b/Resources/views/AccompanyingPeriod/form.html.twig index aa5c63651..26a7653a5 100644 --- a/Resources/views/AccompanyingPeriod/form.html.twig +++ b/Resources/views/AccompanyingPeriod/form.html.twig @@ -2,16 +2,36 @@ {% set activeRouteKey = null %} -{% block title %}{% endblock title %} +{# define the title of the page #} +{% if form.vars.action == 'update' %} + {% set page_title = 'Update accompanying period'|trans %} +{% elseif form.vars.action == 'open' %} + {% set page_title = 'Update accompanying period'|trans %} +{% elseif form.vars.action == 'close' %} + {% set page_title = 'Update accompanying period'|trans %} +{% elseif form.vars.action == 'create' %} + {% set page_title = 'Create an accompanying period'|trans %} +{% else %} + {% set page_title = '' %} +{% endif %} + +{% block title page_title %} {% block personcontent %} + +

{{ page_title }}

{{ form_start(form) }} +

{{ 'Last opening since %last_opening%'|trans( { '%last_opening%' : accompanying_period.openingDate|localizeddate('long', 'none', app.request.locale) }) }} +

+{% if form.openingDate is defined %} + {{ form_row(form.openingDate, { 'label': 'Opening date'} ) }} +{% endif %} {% if form.closingDate is defined %} {{ form_row(form.closingDate, {'label' : 'Closing date'} ) }} {% endif %} @@ -24,10 +44,14 @@ {{ form_rest(form) }} -
+
+ + {{ form_end(form) }} diff --git a/Resources/views/AccompanyingPeriod/list.html.twig b/Resources/views/AccompanyingPeriod/list.html.twig index 9c625d731..07eea6ae0 100644 --- a/Resources/views/AccompanyingPeriod/list.html.twig +++ b/Resources/views/AccompanyingPeriod/list.html.twig @@ -30,9 +30,28 @@ {{ accompanying_period.remark }} -
- {{ 'Edit'|trans }} -
+ + {% endfor %} @@ -40,21 +59,20 @@
-

- - {{ 'Create accompanying period'|trans }} - - - {% if person.isOpen == true %} - - {{'Close accompanying period'|trans }} +

{% endblock personcontent %} \ No newline at end of file diff --git a/Resources/views/AccompanyingPeriod/re_open.html.twig b/Resources/views/AccompanyingPeriod/re_open.html.twig new file mode 100644 index 000000000..82811fcd7 --- /dev/null +++ b/Resources/views/AccompanyingPeriod/re_open.html.twig @@ -0,0 +1,27 @@ +{% extends "ChillPersonBundle::layout.html.twig" %} + +{% set activeRouteKey = 'chill_person_accompanying_period_list' %} + +{% block title 'Re-Open a period'|trans %} + +{% block personcontent %} + +

{{ 'Re-Open a period'|trans }}

+ +

{{ 'Are you sure you want to re-open this period ?'|trans }}

+ +

+ + +{% endblock personcontent %} diff --git a/Tests/Controller/AccompanyingPeriodControllerTest.php b/Tests/Controller/AccompanyingPeriodControllerTest.php index 00e55b7b4..b59a7e001 100644 --- a/Tests/Controller/AccompanyingPeriodControllerTest.php +++ b/Tests/Controller/AccompanyingPeriodControllerTest.php @@ -26,6 +26,7 @@ namespace Chill\PersonBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; +use Doctrine\Common\Collections\Criteria; /** * Test the creation or deletion of accompanying periods @@ -461,4 +462,65 @@ class AccompanyingPeriodControllerTest extends WebTestCase $this->assertGreaterThan(0, $crawlerResponse->filter('.error')->count(), "an 'error' element is shown"); } + + /** + * @group reopening + */ + public function testReOpeningPeriod() + { + // test that re-opening a period which is opened does not work + $this->client->request('GET', + sprintf( + '/fr/person/%d/accompanying-period/%d/re-open', + $this->person->getId(), + $this->person->getOpenedAccompanyingPeriod()->getId() + ) + ); + $this->assertEquals(400, $this->client->getResponse()->getStatusCode(), + "Test an error is returned on a period which cannot be reopened"); + + // close the current period + $period = $this->person->getOpenedAccompanyingPeriod(); + $period->setClosingDate(new \DateTime('2015-02-05')); + $this->person->close($period); + + $this->generatePeriods(array( + [ + 'openingDate' => '2014-01-01', + 'closingDate' => '2014-12-31', + 'closingMotive' => $this->getRandomClosingMotive() + ] + )); + + $periods = $this->person->getAccompanyingPeriodsOrdered(); + /* @var $criteria Criteria */ + $criteria = Criteria::create(); + //$criteria->where(Criteria::expr()->eq('openingDate', \DateTime::createFromFormat())) + $firstPeriod = reset($periods); + $lastPeriod = end($periods); + + // test that it is not possible to open the first period in the list + $this->client->request('GET', + sprintf('/fr/person/%d/accompanying-period/%d/re-open', $this->person->getId(), reset($periods)->getId()) + ); + $this->assertEquals(400, $this->client->getResponse()->getStatusCode(), + "Test an error is returned on the first period in the list"); + + // test that re-opening the last closed period works + $crawler = $this->client->request('GET', + sprintf('/fr/person/%d/accompanying-period/%d/re-open', $this->person->getId(), end($periods)->getId()) + ); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + + $links = $crawler->selectLink('Confirmer'); + + $this->assertEquals(1, $links->count(), "test the link 'confirmer' is present"); + + $this->client->click($links->link()); + + $this->assertTrue($this->client->getResponse()->isRedirect(), + "Test the response is a redirection => the period is re-opened"); + + } } \ No newline at end of file diff --git a/Tests/Entity/AccompanyingPeriodTest.php b/Tests/Entity/AccompanyingPeriodTest.php index 26f3c60fc..7b5e95109 100644 --- a/Tests/Entity/AccompanyingPeriodTest.php +++ b/Tests/Entity/AccompanyingPeriodTest.php @@ -23,79 +23,76 @@ namespace Chill\PersonBundle\Tests\Entity; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; class AccompanyingPeriodTest extends \PHPUnit_Framework_TestCase { + public function testClosingIsAfterOpeningConsistency() { $datetime1 = new \DateTime('now'); $datetime2 = new \DateTime('tomorrow'); - + $period = new AccompanyingPeriod($datetime1); $period->setClosingDate($datetime2); - + $r = $period->isClosingAfterOpening(); - + $this->assertTrue($r); } - - public function testClosingIsBeforeOpeningConsistency() { + + public function testClosingIsBeforeOpeningConsistency() + { $datetime1 = new \DateTime('tomorrow'); $datetime2 = new \DateTime('now'); - + $period = new AccompanyingPeriod($datetime1); $period->setClosingDate($datetime2); - + $this->assertFalse($period->isClosingAfterOpening()); } - - public function testClosingEqualOpening() { + + public function testClosingEqualOpening() + { $datetime = new \DateTime('now'); - + $period = new AccompanyingPeriod($datetime); $period->setClosingDate($datetime); - + $this->assertTrue($period->isClosingAfterOpening()); } - - public function testIsOpen() { + + public function testIsOpen() + { $period = new AccompanyingPeriod(new \DateTime()); - + $this->assertTrue($period->isOpen()); } - - public function testIsClosed() { + + public function testIsClosed() + { $period = new AccompanyingPeriod(new \DateTime()); $period->setClosingDate(new \DateTime('tomorrow')); - + $this->assertFalse($period->isOpen()); } - - /** - * This test seems only to test ordering datetime... Maybe delete ? - */ - public function testOrder() { - $d = new \DateTime(); $d->setDate(2013, 2, 1); - $g = new \DateTime(); $g->setDate(2013, 4, 1); - $f = new \DateTime(); $f->setDate(2013, 1, 1); - $e = new \DateTime(); $e->setDate(2013,3,1); + + public function testCanBeReOpened() + { + $person = new Person(\DateTime::createFromFormat('Y-m-d', '2010-01-01')); + $person->close($person->getAccompanyingPeriods()[0] + ->setClosingDate(\DateTime::createFromFormat('Y-m-d', '2010-12-31'))); - $a = array($d, $g, $f, $e); + $firstAccompanygingPeriod = $person->getAccompanyingPeriodsOrdered()[0]; - usort($a, function($a, $b) { - if ($a === $b) { - return 0; - } - - if ($a < $b) { - return -1; - } else { - return 1; - } - }); + $this->assertTrue($firstAccompanygingPeriod->canBeReOpened()); - $date = $a[0]->format('Y-m-d'); + $lastAccompanyingPeriod = (new AccompanyingPeriod(\DateTime::createFromFormat('Y-m-d', '2011-01-01'))) + ->setClosingDate(\DateTime::createFromFormat('Y-m-d', '2011-12-31')) + ; + $person->addAccompanyingPeriod($lastAccompanyingPeriod); - $this->assertEquals($date, '2013-01-01'); + $this->assertFalse($firstAccompanygingPeriod->canBeReOpened()); } -} \ No newline at end of file + +} diff --git a/Tests/Timeline/TimelineAccompanyingPeriodTest.php b/Tests/Timeline/TimelineAccompanyingPeriodTest.php index 78a182ef6..9d3e6ccd8 100644 --- a/Tests/Timeline/TimelineAccompanyingPeriodTest.php +++ b/Tests/Timeline/TimelineAccompanyingPeriodTest.php @@ -50,10 +50,10 @@ class TimelineAccompanyingPeriodTest extends \Chill\PersonBundle\Tests\Controlle "the timeline page loads sucessfully"); $this->assertGreaterThan(0, $crawler->filter('.timeline div')->count(), "the timeline page contains multiple div inside a .timeline element"); - $this->assertContains("Une période d'accompagnement a été ouverte", + $this->assertContains("Ouverture d'une période d'accompagnement", $crawler->filter('.timeline')->text(), "the text 'une période d'accompagnement a été ouverte' is present"); - $this->assertContains("Une période d'accompagnement a été fermée", + $this->assertContains("Fermeture de la période d'accompagnement", $crawler->Filter('.timeline')->text(), "the text 'Une période d'accompagnement a été fermée' is present"); }