From 8925a86103941ea09f2c6a80cc20b607617cdf3f Mon Sep 17 00:00:00 2001 From: Patrick Barroca <pbarroca@afi-sa.fr> Date: Thu, 18 Jun 2020 16:31:45 +0200 Subject: [PATCH] dev #111127 : drive : add created at datetime, display in admin and csv exports --- VERSIONS_WIP/111127 | 3 +- .../controllers/SessionActivityController.php | 7 +- cosmogramme/sql/patch/patch_390.php | 6 + library/Class/DriveCheckout.php | 35 +++++- library/Class/DriveCheckout/Hold.php | 7 ++ library/Class/Entity.php | 7 +- .../DriveCheckout/HoldsWithCheckouts.php | 19 ++- .../TableDescription/DriveCheckout/List.php | 4 +- .../DriveCheckout/ListWithActions.php | 1 + .../TableDescription/PNBItemsRenderer.php | 7 +- library/Trait/GetterByAttributeName.php | 27 ++++ tests/db/UpgradeDBTest.php | 21 ++++ .../DriveCheckOutBookingTest.php | 5 +- .../DriveCheckoutAdminControllerTest.php | 115 ++++++++++-------- 14 files changed, 178 insertions(+), 86 deletions(-) create mode 100644 cosmogramme/sql/patch/patch_390.php create mode 100644 library/Trait/GetterByAttributeName.php diff --git a/VERSIONS_WIP/111127 b/VERSIONS_WIP/111127 index afe6b2c02f0..a2f406ad2b5 100644 --- a/VERSIONS_WIP/111127 +++ b/VERSIONS_WIP/111127 @@ -6,4 +6,5 @@ - prise de rendez-vous : s'il n'y a qu'un site de retrait possible l'abonné est dirigé directement sur le choix d'une date pour ce site - prise de rendez-vous : nouvelle variable "DRIVE_DISABLE_HOLD_CHECK" permettant la prise de rendez-vous sans vérification de l'existence ou de l'état des réservations de l'abonné - prise de rendez-vous : modification du libellé de suppression pour "Annuler ou replanifier", ajout de détail dans le message de confirmation de suppression - - prise de rendez-vous : ajout d'une notification par mail lors de la suppression \ No newline at end of file + - prise de rendez-vous : ajout d'une notification par mail lors de la suppression + - prise de rendez-vous : enregistrement de la date de création du rendez-vous qui est affichée en administration et présentée dans les exports csv \ No newline at end of file diff --git a/application/modules/admin/controllers/SessionActivityController.php b/application/modules/admin/controllers/SessionActivityController.php index b93a06c2328..f02b6bc5886 100644 --- a/application/modules/admin/controllers/SessionActivityController.php +++ b/application/modules/admin/controllers/SessionActivityController.php @@ -308,6 +308,8 @@ class SessionOneLetterPerDayFusionStrategy extends AbstractSessionFusionStrategy class FusionDateContext { + use Trait_GetterByAttributeName; + protected $_current_date; public function __construct($datestr=null) { @@ -339,11 +341,6 @@ class FusionDateContext { $other_date->sub($this->_current_date); return ($other_date->toValue(Zend_Date::DAY)-1); } - - - public function callGetterByAttributeName($attribute) { - return call_user_func(array($this, 'get'.Storm_Inflector::camelize($attribute))); - } } diff --git a/cosmogramme/sql/patch/patch_390.php b/cosmogramme/sql/patch/patch_390.php new file mode 100644 index 00000000000..f1fa358912d --- /dev/null +++ b/cosmogramme/sql/patch/patch_390.php @@ -0,0 +1,6 @@ +<?php +$adapter = Zend_Db_Table::getDefaultAdapter(); + +try { + $adapter->query('alter table drive_checkout add created_at datetime not null'); +} catch (Exception $e) {} diff --git a/library/Class/DriveCheckout.php b/library/Class/DriveCheckout.php index 2cb7d61ceaa..754b326a686 100644 --- a/library/Class/DriveCheckout.php +++ b/library/Class/DriveCheckout.php @@ -80,7 +80,9 @@ class Class_DriveCheckoutLoader extends Storm_Model_Loader { class Class_DriveCheckout extends Storm_Model_Abstract { - use Trait_Translator; + use Trait_Translator, Trait_TimeSource; + + const DATETIME_FORMAT = 'Y-m-d H:i:s'; protected $_loader_class = 'Class_DriveCheckoutLoader', @@ -94,6 +96,12 @@ class Class_DriveCheckout extends Storm_Model_Abstract { $_date_time_label; + public function beforeSave() { + if ($this->isNew()) + $this->setCreatedAt($this->getCurrentDateTime()); + } + + public function getLibraryLabel() { return ($library = $this->getLibrary()) ? $library->getLibelle() @@ -123,8 +131,29 @@ class Class_DriveCheckout extends Storm_Model_Abstract { } + public function getCreatedAtLabel() { + return ($created = $this->getDateTimeCreatedAt()) + ? strftime($this->_('%d %B à %Hh%M'), $created->getTimestamp()) + : $this->_('n/a'); + } + + + public function getCreatedAtForCsv() { + return ($created = $this->getDateTimeCreatedAt()) + ? $created->format(static::DATETIME_FORMAT) + : ''; + } + + + public function getDateTimeCreatedAt() { + return ($date_time = $this->getCreatedAt()) && $date_time != '0000-00-00 00:00:00' + ? DateTime::createFromFormat(static::DATETIME_FORMAT, $date_time) + : null; + } + + public function getDateTimeStartAt() { - return DateTime::createFromFormat('Y-m-d H:i:s', $this->getStartAt()); + return DateTime::createFromFormat(static::DATETIME_FORMAT, $this->getStartAt()); } @@ -191,4 +220,4 @@ class Class_DriveCheckout extends Storm_Model_Abstract { public function notifyDelete() { return (new Class_DriveCheckout_DeletionNotification($this))->send(); } -} \ No newline at end of file +} diff --git a/library/Class/DriveCheckout/Hold.php b/library/Class/DriveCheckout/Hold.php index b5690377a5d..631ca5b63d0 100644 --- a/library/Class/DriveCheckout/Hold.php +++ b/library/Class/DriveCheckout/Hold.php @@ -21,6 +21,8 @@ class Class_DriveCheckout_Hold { + use Trait_GetterByAttributeName; + protected $_checkout, $_hold, @@ -48,6 +50,11 @@ class Class_DriveCheckout_Hold { } + public function getCreatedAtForCsv() { + return $this->_checkout->getCreatedAtForCsv(); + } + + public function getCote() { return $this->_item ? $this->_item->getCote() diff --git a/library/Class/Entity.php b/library/Class/Entity.php index 10bab2a777e..998bf5f1043 100644 --- a/library/Class/Entity.php +++ b/library/Class/Entity.php @@ -21,6 +21,8 @@ class Class_Entity { + use Trait_GetterByAttributeName; + protected $_attribs = [], $_methods = []; @@ -80,11 +82,6 @@ class Class_Entity { } - public function callGetterByAttributeName($attribute) { - return call_user_func([$this, 'get' . Storm_Inflector::camelize($attribute)]); - } - - public function __toString() { return get_class($this); } diff --git a/library/Class/TableDescription/DriveCheckout/HoldsWithCheckouts.php b/library/Class/TableDescription/DriveCheckout/HoldsWithCheckouts.php index cba0bb694df..7a365926794 100644 --- a/library/Class/TableDescription/DriveCheckout/HoldsWithCheckouts.php +++ b/library/Class/TableDescription/DriveCheckout/HoldsWithCheckouts.php @@ -27,19 +27,14 @@ class Class_TableDescription_DriveCheckout_HoldsWithCheckouts extends Class_Tabl function($hold) { return strftime('%d %B',strtotime($hold->getStartAt())); }) ->addColumn($this->_('Heure'), function($hold) { return strftime('%H:%M',strtotime($hold->getStartAt())); }) - ->addColumn($this->_('Carte'), - function($hold) { return $hold->getIdabon(); }) - ->addColumn($this->_('Abonné'), - function($hold) { return $hold->getNomComplet(); }) - ->addColumn($this->_('Section'), - function($hold) { return $hold->getSectionLabel(); }) - ->addColumn($this->_('Emplacement'), - function($hold) { return $hold->getEmplacementLabel(); }) - ->addColumn($this->_('Cote'), - function($hold) { return $hold->getCote(); }) - ->addColumn($this->_('Code-barres'), - function($hold) { return $hold->getCodeBarres(); }) + ->addColumn($this->_('Carte'), 'idabon') + ->addColumn($this->_('Abonné'), 'nom_complet') + ->addColumn($this->_('Créé le'), 'created_at_for_csv') + ->addColumn($this->_('Section'), 'section_label') + ->addColumn($this->_('Emplacement'), 'emplacement_label') + ->addColumn($this->_('Cote'), 'cote') + ->addColumn($this->_('Code-barres'), 'code_barres') ->addColumn($this->_('Titre'), function($hold){ return strip_tags($hold->getTitle()); }); diff --git a/library/Class/TableDescription/DriveCheckout/List.php b/library/Class/TableDescription/DriveCheckout/List.php index 31b86158f86..b648ca6153b 100644 --- a/library/Class/TableDescription/DriveCheckout/List.php +++ b/library/Class/TableDescription/DriveCheckout/List.php @@ -36,6 +36,8 @@ class Class_TableDescription_DriveCheckout_List extends Class_TableDescription { strtotime($checkout->getStartAt())); }) ->addColumn($this->_('Carte'), 'id_abon') - ->addColumn($this->_('Abonné'), 'nom_complet'); + ->addColumn($this->_('Abonné'), 'nom_complet') + ->addColumn($this->_('Créé le'), 'created_at_for_csv') + ; } } \ No newline at end of file diff --git a/library/Class/TableDescription/DriveCheckout/ListWithActions.php b/library/Class/TableDescription/DriveCheckout/ListWithActions.php index aa28d82e8e2..a56c6efa3d8 100644 --- a/library/Class/TableDescription/DriveCheckout/ListWithActions.php +++ b/library/Class/TableDescription/DriveCheckout/ListWithActions.php @@ -32,6 +32,7 @@ class Class_TableDescription_DriveCheckout_ListWithActions extends Class_TableDe }) ->addColumn($this->_('Carte'), 'id_abon') ->addColumn($this->_('Abonné'), 'nom_complet') + ->addColumn($this->_('Créé le'), 'created_at_label') ->addRowAction(['canvas_callback' => [$this, 'listHolds']]) ->addRowAction(['canvas_callback' => [$this, 'viewUser']]) ->addRowAction(['canvas_callback' => [$this, 'deleteCheckout']]) diff --git a/library/Class/TableDescription/PNBItemsRenderer.php b/library/Class/TableDescription/PNBItemsRenderer.php index f40234de32f..72c48ce68b4 100644 --- a/library/Class/TableDescription/PNBItemsRenderer.php +++ b/library/Class/TableDescription/PNBItemsRenderer.php @@ -21,6 +21,8 @@ class Class_TableDescription_PNBItemsRenderer { + use Trait_GetterByAttributeName; + protected $_item; public function __construct($item) { @@ -28,11 +30,6 @@ class Class_TableDescription_PNBItemsRenderer { } - public function callGetterByAttributeName($attribute) { - return call_user_func([$this, 'get' . Storm_Inflector::camelize($attribute)]); - } - - public function __call($name, $params) { return call_user_func_array([$this->_item, $name], $params); } diff --git a/library/Trait/GetterByAttributeName.php b/library/Trait/GetterByAttributeName.php new file mode 100644 index 00000000000..4b3208ef0a2 --- /dev/null +++ b/library/Trait/GetterByAttributeName.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved. + * + * BOKEH 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. + * + * There are special exceptions to the terms and conditions of the AGPL as it + * is applied to this software (see README file). + * + * BOKEH 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 BOKEH; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +trait Trait_GetterByAttributeName { + public function callGetterByAttributeName($name) { + return call_user_func([$this, 'get' . Storm_Inflector::camelize($name)]); + } +} diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php index c5338f69a40..56f0ae89617 100644 --- a/tests/db/UpgradeDBTest.php +++ b/tests/db/UpgradeDBTest.php @@ -239,6 +239,12 @@ abstract class UpgradeDBTestCase extends PHPUnit_Framework_TestCase { } + protected function dropFieldFrom($table, $field) { + return $this->silentQuery(sprintf('alter table %s drop column %s', + $table, $field)); + } + + protected function dropIndexedFieldFrom($table, $field) { return $this->silentQuery('ALTER TABLE ' . $table . ' ' . 'DROP KEY ' . $field . ', ' @@ -3322,4 +3328,19 @@ class UpgradeDB_389_Test extends UpgradeDBTestCase { public function tableOuverturesShouldContainsColumnMaxPerPeriodApresMidiAsInt(){ $this->assertFieldType('ouvertures', 'max_per_period_apres_midi', 'int(11)'); } +} + + + + +class UpgradeDB_390_Test extends UpgradeDBTestCase { + public function prepare() { + $this->dropFieldFrom('drive_checkout', 'created_at'); + } + + + /** @test */ + public function tableDriveCheckoutShouldContainsCreatedAtColumn() { + $this->assertFieldType('drive_checkout', 'created_at', 'datetime'); + } } \ No newline at end of file diff --git a/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php b/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php index 3841230060f..60be3e417c4 100644 --- a/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php +++ b/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php @@ -60,6 +60,7 @@ abstract class DriveCheckOutBookingTestCase extends AbstractControllerTestCase { $timesource = new TimeSourceForTest('2020-05-05 11:30'); Class_DriveCheckout_Plan::setTimeSource($timesource); + Class_DriveCheckout::setTimeSource($timesource); $this->_mail_transport = new MockMailTransport(); Zend_Mail::setDefaultTransport($this->_mail_transport); @@ -72,6 +73,7 @@ abstract class DriveCheckOutBookingTestCase extends AbstractControllerTestCase { public function tearDown() { Class_DriveCheckout_Plan::setTimeSource(null); + Class_DriveCheckout::setTimeSource(null); Class_Systeme_ModulesAccueil::reset(); parent::tearDown(); } @@ -807,7 +809,8 @@ class DriveCheckOutBookingPlanBibHotelDieuAt2020_05_12_09_00Test public function checkoutShouldBePlanned() { $this->assertNotNull(Class_DriveCheckout::findFirstBy(['user_id' => $this->_marcus->getId(), 'library_id' => 1, - 'start_at' => '2020-05-12 09:00:00'])); + 'start_at' => '2020-05-12 09:00:00', + 'created_at' => '2020-05-05 11:30:00'])); } diff --git a/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php b/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php index 88f3e5724f9..5694ab45f36 100644 --- a/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php +++ b/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php @@ -86,6 +86,41 @@ abstract class DriveCheckOutAdminControllerTestCase extends Admin_AbstractContro 'libelle' => 'BD', 'regles' => '995$e=bd']); + $holds = [Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() + ->setExemplaireOPAC($this->fixture('Class_Exemplaire', + ['id' => 2, + 'code_barres' => 'tintin123', + 'notice' => $this->fixture('Class_Notice', + ['id' => 123, + 'titre_principal' => 'Tintin à Dole']), + 'emplacement' => 2, + 'section' => 3, + 'cote' => 'BD2'])) + ->setBibliotheque($lib_camus->getLibelle()) + ->setCodeBarre('tintin123') + ->setTitre('Tintin à Dole') + ->setEtat('On le cherche') + ->setWaitingToBePulled(), + + Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() + ->setBibliotheque($lib_camus->getLibelle()) + ->setCodeBarre('milou123') + ->setTitre('Milou à Dole') + ->setWaitingToBePulled(), + + Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() + ->setBibliotheque($lib_maurissette->getLibelle()) + ->setCodeBarre('tournesol123') + ->setEtat('Disponible') + ->setTitre('Tournesol à Maurissette') + ->setWaitingToBePulled(), + + Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() + ->setBibliotheque($lib_camus->getLibelle()) + ->setCodeBarre('dupont123') + ->setTitre('Dupont à Dole') + ]; + $maurice = $this->fixture('Class_Users', ['id' => 4, 'login' => 'maurice', @@ -95,78 +130,48 @@ abstract class DriveCheckOutAdminControllerTestCase extends Admin_AbstractContro 'bib' => $lib_hotel_dieu]) ->setFicheSigb(['type_comm' => Class_IntBib::COM_NANOOK, 'fiche' => (new Class_WebService_SIGB_Emprunteur(4, 'Maurice')) - ->reservationsAddAll( - [Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() - ->setExemplaireOPAC($this->fixture('Class_Exemplaire', - ['id' => 2, - 'code_barres' => 'tintin123', - 'notice' => $this->fixture('Class_Notice', - ['id' => 123, - 'titre_principal' => 'Tintin à Dole']), - 'emplacement' => 2, - 'section' => 3, - 'cote' => 'BD2'])) - ->setBibliotheque($lib_camus->getLibelle()) - ->setCodeBarre('tintin123') - ->setTitre('Tintin à Dole') - ->setEtat('On le cherche') - ->setWaitingToBePulled(), - - Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() - ->setBibliotheque($lib_camus->getLibelle()) - ->setCodeBarre('milou123') - ->setTitre('Milou à Dole') - ->setWaitingToBePulled(), - - Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() - ->setBibliotheque($lib_maurissette->getLibelle()) - ->setCodeBarre('tournesol123') - ->setEtat('Disponible') - ->setTitre('Tournesol à Maurissette') - ->setWaitingToBePulled(), - - Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() - ->setBibliotheque($lib_camus->getLibelle()) - ->setCodeBarre('dupont123') - ->setTitre('Dupont à Dole') - ])]); - + ->reservationsAddAll($holds)]); $this->fixture('Class_DriveCheckout', ['id' => 1, 'user' => $emilie, 'library' => $lib_hotel_dieu, - 'start_at' => '2020-05-12 09:00:00']); + 'start_at' => '2020-05-12 09:00:00', + 'created_at' => '0000-00-00 00:00:00']); $this->fixture('Class_DriveCheckout', ['id' => 2, 'user' => $bernard, 'library' => $lib_hotel_dieu, - 'start_at' => '2020-05-12 14:00:00']); + 'start_at' => '2020-05-12 14:00:00', + 'created_at' => '2020-05-02 23:46:33']); $this->fixture('Class_DriveCheckout', ['id' => 3, 'user' => $maurice, 'library' => $lib_camus, - 'start_at' => '2020-05-14 09:00:00']); + 'start_at' => '2020-05-14 09:00:00', + 'created_at' => '0000-00-00 00:00:00']); $this->fixture('Class_DriveCheckout', ['id' => 4, 'user' => $emilie, 'library' => $lib_camus, - 'start_at' => '2020-05-14 09:15:00']); + 'start_at' => '2020-05-14 09:15:00', + 'created_at' => '2020-05-03 19:32:55']); $this->fixture('Class_DriveCheckout', ['id' => 5, 'user' => $bernard, 'library' => $lib_camus, - 'start_at' => '2020-05-15 10:00:00']); - + 'start_at' => '2020-05-15 10:00:00', + 'created_at' => '0000-00-00 00:00:00']); } } + class DriveCheckoutAdminControllerAdminVarTest extends DriveCheckOutAdminControllerTestCase { /** @test */ public function withAdminVarEnableDriveFalseShouldNotDisplayMenuEntry() { @@ -266,13 +271,13 @@ class DriveCheckoutAdminControllerListHotelDieuTest extends DriveCheckOutAdminCo /** @test */ public function pageShouldContainsTableWithCheckout2020_05_12_at_9_00_For_Emilie() { - $this->assertXPath('//table//td[text()="09:00"]/following-sibling::td[text()="A121"]/following-sibling::td[text()="emilie"]'); + $this->assertXPath('//table//td[text()="09:00"]/following-sibling::td[text()="A121"]/following-sibling::td[text()="emilie"]/following-sibling::td[text()="n/a"]'); } /** @test */ public function pageShouldContainsTableWithCheckout2020_05_12_at_14_00_For_Bernard() { - $this->assertXPath('//table//td[text()="14:00"]/following-sibling::td[text()="A123"]/following-sibling::td[text()="bernard"]'); + $this->assertXPath('//table//td[text()="14:00"]/following-sibling::td[text()="A123"]/following-sibling::td[text()="bernard"]/following-sibling::td[text()="02 mai à 23h46"]'); } @@ -373,7 +378,9 @@ class DriveCheckoutAdminControllerListCamusOnMayFourteenthDaysSevenTest extends -class DriveCheckoutAdminControllerExportCSVCamusOnMayFourteenthTest extends DriveCheckOutAdminControllerTestCase { +class DriveCheckoutAdminControllerExportCSVCamusOnMayFourteenthTest + extends DriveCheckOutAdminControllerTestCase { + public function setUp() { parent::setUp(); $this->dispatch('/admin/drive-checkout/list-csv/id_bib/2/date/2020-05-14'); @@ -390,9 +397,9 @@ class DriveCheckoutAdminControllerExportCSVCamusOnMayFourteenthTest extends Driv /** @test */ public function csvShouldContainsCheckouts() { - $this->assertEquals("Jour;Heure;Carte;Abonné\n" - ."\"14 mai\";09:00;A124;maurice\n" - ."\"14 mai\";09:15;A121;emilie\n", + $this->assertEquals("Jour;Heure;Carte;Abonné;\"Créé le\"\n" + ."\"14 mai\";09:00;A124;maurice;\n" + ."\"14 mai\";09:15;A121;emilie;\"2020-05-03 19:32:55\"\n", $this->_response->getBody()); } } @@ -400,7 +407,9 @@ class DriveCheckoutAdminControllerExportCSVCamusOnMayFourteenthTest extends Driv -class DriveCheckoutAdminControllerListHoldsForCheckoutThreeTest extends DriveCheckOutAdminControllerTestCase { +class DriveCheckoutAdminControllerListHoldsForCheckoutThreeTest + extends DriveCheckOutAdminControllerTestCase { + public function setUp() { parent::setUp(); $this->dispatch('/admin/drive-checkout/list-holds/id/3'); @@ -486,10 +495,10 @@ class DriveCheckoutAdminControllerExportItemsCSVCamusOnMayFourteenthTest extends /** @test */ public function csvShouldContainsTintinMilouAndDupontItems() { - $this->assertEquals("Jour;Heure;Carte;Abonné;Section;Emplacement;Cote;Code-barres;Titre\n" - . "\"14 mai\";09:00;A124;maurice;Jeunesse;BD;BD2;tintin123;\"Tintin à Dole\"\n" - . "\"14 mai\";09:00;A124;maurice;;;;milou123;\"Milou à Dole\"\n" - . "\"14 mai\";09:00;A124;maurice;;;;dupont123;\"Dupont à Dole\"\n", + $this->assertEquals("Jour;Heure;Carte;Abonné;\"Créé le\";Section;Emplacement;Cote;Code-barres;Titre\n" + . "\"14 mai\";09:00;A124;maurice;;Jeunesse;BD;BD2;tintin123;\"Tintin à Dole\"\n" + . "\"14 mai\";09:00;A124;maurice;;;;;milou123;\"Milou à Dole\"\n" + . "\"14 mai\";09:00;A124;maurice;;;;;dupont123;\"Dupont à Dole\"\n", $this->_response->getBody()); } } -- GitLab