diff --git a/VERSIONS_HOTLINE/173292 b/VERSIONS_HOTLINE/173292 new file mode 100644 index 0000000000000000000000000000000000000000..34ad0d2e2fc5d4a446df6952368bb715d7fa76e6 --- /dev/null +++ b/VERSIONS_HOTLINE/173292 @@ -0,0 +1 @@ + - correctif #173292 : Drive : Suppression d'une erreur survenant dans le tableau de bord des rendez-vous \ No newline at end of file diff --git a/application/modules/admin/controllers/DriveCheckoutController.php b/application/modules/admin/controllers/DriveCheckoutController.php index 87c0240d1adea29dc2b17cb673c8a8ae4a032c0b..3b6ca28ed6b0d18a042845fa367a2a64e323dfe2 100644 --- a/application/modules/admin/controllers/DriveCheckoutController.php +++ b/application/modules/admin/controllers/DriveCheckoutController.php @@ -113,10 +113,9 @@ class Admin_DriveCheckoutController extends ZendAfi_Controller_Action { $library = Class_Bib::find($this->_getParam('id_bib')); $date = $this->_getParam('date', $this->getCurrentDate()); - $checkouts = Class_DriveCheckout::findAllBy(['role' => 'library', - 'model' => $library, - 'left(start_at,10)' => $date, - 'order' => 'start_at']); + $checkouts = $library && (false !== ($timestamp = strtotime($date))) + ? Class_DriveCheckout::findAllByStartingAt($library, $timestamp) + : []; $holds = (new Storm_Collection($checkouts)) ->injectInto(new Storm_Collection(), diff --git a/library/Class/Bib/DriveOpening/Period.php b/library/Class/Bib/DriveOpening/Period.php index 2512dc23bf0f108d27f516e51a15d58cd11f3721..5816761d8d9a19711fbf6acd633c152360dbed7e 100644 --- a/library/Class/Bib/DriveOpening/Period.php +++ b/library/Class/Bib/DriveOpening/Period.php @@ -42,6 +42,13 @@ class Class_Bib_DriveOpening_Period { } + public static function periodFor(DateTimeInterface $start, + DateTimeInterface $end) : DatePeriod { + return new DatePeriod($start, + new DateInterval('PT' . static::CHECKOUT_TIME_SLOT . 'M'), + $end); + } + public function __construct($library, $date, $opening) { $this->_opening = $opening; @@ -124,9 +131,8 @@ class Class_Bib_DriveOpening_Period { return $this->_times; $day = $this->format('Y-m-d'); - $period = new DatePeriod(new DateTime($day . ' ' . $this->start()), - new DateInterval('PT' . static::CHECKOUT_TIME_SLOT . 'M'), - new DateTime($day . ' ' . $this->end())); + $period = static::periodFor(new DateTime($day . ' ' . $this->start()), + new DateTime($day . ' ' . $this->end())); $times = new Storm_Collection(); foreach($period as $step) @@ -141,4 +147,4 @@ class Class_Bib_DriveOpening_Period { ? 0 : $quota * $this->_times()->count(); } -} \ No newline at end of file +} diff --git a/library/Class/DriveCheckout.php b/library/Class/DriveCheckout.php index 9fdc27b4d60e230867a2234998abed10eb92ab02..f4f6697526855f9b62e8193cd6c5fe618efd4b30 100644 --- a/library/Class/DriveCheckout.php +++ b/library/Class/DriveCheckout.php @@ -24,27 +24,43 @@ class Class_DriveCheckoutLoader extends Storm_Model_Loader { if (!$library || !$user) return; - return Class_DriveCheckout::findFirstBy(['library_id' => $library->getId(), - 'user_id' => $user->getId(), - 'where' => 'date(start_at) >= curdate()', - 'order' => 'start_at']); + return ($futures = Class_DriveCheckout::query() + ->eq('library_id', $library->getId()) + ->eq('user_id', $user->getId()) + ->gt_eq_left('start_at', 10, Class_DriveCheckout::getCurrentDate()) + ->order('start_at') + ->limit(1) + ->fetchAll()) + ? $futures[0] + : null; } - public function findAllByLibraryAndDates($library, $start_date, $days_count) { - $checkouts = new Storm_Model_Collection(); - for($i=0; $i<$days_count; $i++) { - $start_at = strftime('%Y-%m-%d', strtotime($start_date . ' +' . $i . ' day')); + public function findAllByLibraryAndDates(Class_Bib $library, + string $start_date, + int $days_count) : Storm_Model_Collection { + if (false === strtotime($start_date)) + return new Storm_Model_Collection; + + $checkouts = new Storm_Model_Collection; + foreach (range(0, $days_count - 1) as $step) $checkouts - ->addAll(Class_DriveCheckout::findAllBy(['role' => 'library', - 'model' => $library, - 'left(start_at,10)' => $start_at, - 'order' => 'start_at'])); - } + ->addAll(Class_DriveCheckout::findAllByStartingAt($library, + strtotime($start_date . ' +' . $step . ' day'))); + return $checkouts; } + public function findAllByStartingAt(Class_Bib $library, int $timestamp) : array { + return Class_DriveCheckout::query() + ->eq('library_id', $library->getId()) + ->eq_left('start_at', 10, date('Y-m-d', $timestamp)) + ->order('start_at') + ->fetchAll(); + } + + public function findFor($id, $user) { return $user ? Class_DriveCheckout::findFirstBy(['id' => (int)$id, @@ -54,12 +70,14 @@ class Class_DriveCheckoutLoader extends Storm_Model_Loader { public function countBetweenForLibrary($start, $end, $library) { - return $library && $start && $end - ? Class_DriveCheckout::countBy(['library_id' => $library->getId(), - 'where' => sprintf('start_at >= "%s" and start_at < "%s"', - $this->_dateAsSql($start), - $this->_dateAsSql($end))]) - : 0; + if (!$library || !$start || !$end) + return 0; + + $params = ['library_id' => $library->getId(), + Storm_Query_Clause::greaterEqual('start_at', $this->_dateAsSql($start)), + Storm_Query_Clause::lesser('start_at', $this->_dateAsSql($end))]; + + return Class_DriveCheckout::countBy($params); } @@ -71,15 +89,15 @@ class Class_DriveCheckoutLoader extends Storm_Model_Loader { } - public function countByPreviousMonthFrom($date_time) { + public function countByPreviousMonthFrom(DateTimeInterface $date_time) : int { $date_time->modify('previous month midnight'); + $params = [ Storm_Query_Clause::greaterEqual('start_at', $this->_dateAsSql($date_time)) ]; - return Class_DriveCheckout::countBy(['where' => sprintf('start_at >= "%s"', - $this->_dateAsSql($date_time))]); + return Class_DriveCheckout::countBy($params); } - protected function _dateAsSql($date) { + protected function _dateAsSql(DateTimeInterface $date) { return $date->format('Y-m-d H:i:s'); } } @@ -93,10 +111,10 @@ class Class_DriveCheckout extends Storm_Model_Abstract { const DATETIME_FORMAT = 'Y-m-d H:i:s'; protected - $_loader_class = 'Class_DriveCheckoutLoader', + $_loader_class = Class_DriveCheckoutLoader::class, $_table_name = 'drive_checkout', - $_belongs_to = ['user' => ['model' => 'Class_Users'], - 'library' => ['model' => 'Class_Bib']], + $_belongs_to = ['user' => ['model' => Class_Users::class], + 'library' => ['model' => Class_Bib::class]], $_default_attribute_values = ['start_at' => null, 'user_id' => null, diff --git a/library/storm b/library/storm index 4f8e91183ee11d0a1be8205439e1abae14ac4f03..d2015db051956daad6aa242baa24908ce761d77f 160000 --- a/library/storm +++ b/library/storm @@ -1 +1 @@ -Subproject commit 4f8e91183ee11d0a1be8205439e1abae14ac4f03 +Subproject commit d2015db051956daad6aa242baa24908ce761d77f diff --git a/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php b/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php index c6254d4fa40e9e3478609cec6ff93822dd6b6084..5a4b932820e71abd8575e346b8506c5d9365c786 100644 --- a/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php +++ b/tests/scenarios/DriveCheckOut/DriveCheckOutBookingTest.php @@ -21,9 +21,6 @@ class DriveCheckOutBookingNotActiveTest extends AbstractControllerTestCase { - protected $_storm_default_to_volatile = true; - - public function setUp() { parent::setUp(); Class_AdminVar::set('ENABLE_DRIVE_CHECKOUT', 0); @@ -48,7 +45,6 @@ class DriveCheckOutBookingNotActiveTest extends AbstractControllerTestCase { abstract class DriveCheckOutBookingTestCase extends AbstractControllerTestCase { protected - $_storm_default_to_volatile = true, $_marcus, $_mail_transport; @@ -95,7 +91,7 @@ abstract class DriveCheckOutBookingTestCase extends AbstractControllerTestCase { protected function _setupLibraries() { $lib_hotel_dieu = $this - ->fixture('Class_Bib', + ->fixture(Class_Bib::class, ['id' => 1, 'libelle' => 'Hotel-Dieu', 'closed_on_holidays' => false, @@ -111,12 +107,12 @@ abstract class DriveCheckOutBookingTestCase extends AbstractControllerTestCase { ->beDrive(), ]]); - $this->fixture('Class_Bib', + $this->fixture(Class_Bib::class, ['id' => 2, 'enable_drive' => 1, 'libelle' => 'Albert Camus']); - $this->fixture('Class_Bib', + $this->fixture(Class_Bib::class, ['id' => 3, 'enable_drive' => 1, 'libelle' => 'Mauricette-Rafin', @@ -126,7 +122,7 @@ abstract class DriveCheckOutBookingTestCase extends AbstractControllerTestCase { ] ]); - $this->fixture('Class_Bib', + $this->fixture(Class_Bib::class, ['id' => 4, 'enable_drive' => 0, 'libelle' => 'Le Turbomoteur']); @@ -386,13 +382,12 @@ class DriveCheckOutBookingPlanOnBibWithDriveStartingNextWeekTest extends DriveCh class DriveCheckOutBookingPlanWithFuturExistingTest extends DriveCheckOutBookingTestCase { public function setUp() { parent::setUp(); - $this->onLoaderOfModel('Class_DriveCheckout') - ->whenCalled('findFuturefor')->with(Class_Bib::find(2), $this->_marcus) - ->answers($this->fixture('Class_DriveCheckout', - ['id' => 2, - 'library_id' => 2, - 'user_id' => $this->_marcus->getId(), - 'start_at' => '2020-05-12 09:00:00'])); + + $this->fixture(Class_DriveCheckout::class, + ['id' => 2, + 'library_id' => 2, + 'user_id' => $this->_marcus->getId(), + 'start_at' => '2020-05-12 09:00:00']); $this->dispatch('/opac/drive-checkout/plan'); } @@ -688,14 +683,23 @@ class DriveCheckOutBookingPlanBibHotelDieuAllowTodayTest extends DriveCheckOutBo class DriveCheckOutBookingPlanBibHotelDieuQuotaFullOn2020_05_12Test extends DriveCheckOutBookingTestCase { + protected function _occupyFull2020_05_12() { + $period = Class_Bib_DriveOpening_Period::periodFor(new DateTime('2020-05-12 09:00'), + new DateTime('2020-05-12 18:00')); + + $i = 0; + foreach($period as $step) + $this->fixture(Class_DriveCheckout::class, + ['id' => 884 + $i++, + 'library_id' => 1, + 'user_id' => 48849, + 'start_at' => '2020-05-12 ' . $step->format('H:i') . ':00']); + } + + public function setUp() { parent::setUp(); - $this->onLoaderOfModel('Class_DriveCheckout') - ->whenCalled('countBetweenForLibrary') - ->willDo(function($start, $end, $library) - { - return '2020-05-12' == $start->format('Y-m-d') ? 600 : 0; - }); + $this->_occupyFull2020_05_12(); $this->dispatch('/opac/drive-checkout/plan/id_bib/1'); } @@ -1139,4 +1143,4 @@ class DriveCheckOutBookingPlanWithOnlyOnePossibleLibrary $this->dispatch('/opac/drive-checkout/plan'); $this->assertNotRedirect(); } -} \ No newline at end of file +} diff --git a/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php b/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php index d4866705555cbb9cf8548242e6cabb4e55fede0a..045bbcab826c45704343a1daf02f5cd5f46a76ff 100644 --- a/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php +++ b/tests/scenarios/DriveCheckOut/DriveCheckoutAdminControllerTest.php @@ -23,9 +23,7 @@ require_once('application/modules/admin/controllers/DriveCheckoutController.php'); abstract class DriveCheckOutAdminControllerTestCase extends Admin_AbstractControllerTestCase { - protected - $_storm_default_to_volatile = true, - $_mail_transport; + protected MockMailTransport $_mail_transport; public function setUp() { parent::setUp(); @@ -40,22 +38,23 @@ abstract class DriveCheckOutAdminControllerTestCase extends Admin_AbstractContro $this->_mail_transport = new MockMailTransport(); Zend_Mail::setDefaultTransport($this->_mail_transport); - $lib_hotel_dieu = $this->fixture('Class_Bib', - ['id' => 1, - 'libelle' => 'Hotel-Dieu', - 'enable_drive' => true, - 'ouvertures' => [ - Class_Ouverture::chaqueLundi('00:00', '00:00', '12:00', '18:00') - ->beDrive(), - Class_Ouverture::chaqueMardi('09:00', '12:00', '14:00', '18:00') - ->beDrive()]]); - - $lib_camus = $this->fixture('Class_Bib', + $lib_hotel_dieu = $this + ->fixture(Class_Bib::class, + ['id' => 1, + 'libelle' => 'Hotel-Dieu', + 'enable_drive' => true, + 'ouvertures' => [ + Class_Ouverture::chaqueLundi('00:00', '00:00', '12:00', '18:00') + ->beDrive(), + Class_Ouverture::chaqueMardi('09:00', '12:00', '14:00', '18:00') + ->beDrive()]]); + + $lib_camus = $this->fixture(Class_Bib::class, ['id' => 2, 'libelle' => 'Albert Camus', 'enable_drive' => true]); - $lib_maurissette = $this->fixture('Class_Bib', + $lib_maurissette = $this->fixture(Class_Bib::class, ['id' => 3, 'libelle' => 'Maurissette', 'enable_drive' => false]); @@ -80,20 +79,20 @@ abstract class DriveCheckOutAdminControllerTestCase extends Admin_AbstractContro 'bib' => $lib_hotel_dieu]); - $this->fixture('Class_CodifSection', + $this->fixture(Class_CodifSection::class, ['id' => 3, 'libelle' => 'Jeunesse']); - $this->fixture('Class_CodifEmplacement', + $this->fixture(Class_CodifEmplacement::class, ['id' => 2, 'libelle' => 'BD', 'regles' => '995$e=bd']); $holds = [Class_WebService_SIGB_Reservation::newInstanceWithEmptyExemplaire() - ->setExemplaireOPAC($this->fixture('Class_Exemplaire', + ->setExemplaireOPAC($this->fixture(Class_Exemplaire::class, ['id' => 2, 'code_barres' => 'tintin123', - 'notice' => $this->fixture('Class_Notice', + 'notice' => $this->fixture(Class_Notice::class, ['id' => 123, 'titre_principal' => 'Tintin à Dole']), 'emplacement' => 2, @@ -135,38 +134,39 @@ abstract class DriveCheckOutAdminControllerTestCase extends Admin_AbstractContro 'last_login' => 0, 'bib' => $lib_hotel_dieu]) ->setFicheSigb(['type_comm' => Class_IntBib::COM_NANOOK, - 'fiche' => (new Class_WebService_SIGB_Emprunteur(4, 'Maurice')) - ->reservationsAddAll($holds)]); + 'fiche' => ((new Class_WebService_SIGB_Emprunteur(4, + 'Maurice')) + ->reservationsAddAll($holds))]); - $this->fixture('Class_DriveCheckout', + $this->fixture(Class_DriveCheckout::class, ['id' => 1, 'user' => $emilie, 'library' => $lib_hotel_dieu, 'start_at' => '2020-05-12 09:00:00', 'created_at' => '0000-00-00 00:00:00']); - $this->fixture('Class_DriveCheckout', + $this->fixture(Class_DriveCheckout::class, ['id' => 2, 'user' => $bernard, 'library' => $lib_hotel_dieu, 'start_at' => '2020-05-12 14:00:00', 'created_at' => '2020-05-02 23:46:33']); - $this->fixture('Class_DriveCheckout', + $this->fixture(Class_DriveCheckout::class, ['id' => 3, 'user' => $maurice, 'library' => $lib_camus, 'start_at' => '2020-05-14 09:00:00', 'created_at' => '0000-00-00 00:00:00']); - $this->fixture('Class_DriveCheckout', + $this->fixture(Class_DriveCheckout::class, ['id' => 4, 'user' => $emilie, 'library' => $lib_camus, 'start_at' => '2020-05-14 09:15:00', 'created_at' => '2020-05-03 19:32:55']); - $this->fixture('Class_DriveCheckout', + $this->fixture(Class_DriveCheckout::class, ['id' => 5, 'user' => $bernard, 'library' => $lib_camus, @@ -259,12 +259,21 @@ class DriveCheckoutAdminControllerIndexTest extends DriveCheckOutAdminController class DriveCheckoutAdminControllerListHotelDieuTest extends DriveCheckOutAdminControllerTestCase { + protected bool $_storm_mock_zend_adapter = true; + protected array $_storm_scopes = ['->findAllByLibraryAndDates']; + public function setUp() { parent::setUp(); $this->dispatch('/admin/drive-checkout/list/id_bib/1'); } + /** @test */ + public function shouldQueryCheckoutsOf2020_05_12() { + $this->assertSql("SELECT `drive_checkout`.* FROM `drive_checkout` WHERE (`drive_checkout`.`library_id` = 1 AND LEFT(`drive_checkout`.`start_at`, 10) = '2020-05-12') ORDER BY `drive_checkout`.`start_at` ASC"); + } + + /** @test */ public function pageTitleShouldBeRendezVousHotelDieu() { $this->assertXPathContentContains('//h1', 'Drive : rendez-vous : Hotel-Dieu, 12 mai 2020'); @@ -493,12 +502,23 @@ class DriveCheckoutAdminControllerListAllHoldsForMauriceIdFourTest extends Drive class DriveCheckoutAdminControllerExportItemsCSVCamusOnMayFourteenthTest extends DriveCheckOutAdminControllerTestCase { + protected bool $_storm_mock_zend_adapter = true; + protected array $_storm_scopes = ['->findAllByStartingAt']; + + public function setUp() { parent::setUp(); $this->dispatch('/admin/drive-checkout/items-csv/id_bib/2/date/2020-05-14'); } + /** @test */ + public function shouldQueryCheckoutsOf2020_05_14() { + $this->assertSql("SELECT `drive_checkout`.* FROM `drive_checkout` WHERE (`drive_checkout`.`library_id` = 2 AND LEFT(`drive_checkout`.`start_at`, 10) = '2020-05-14') ORDER BY `drive_checkout`.`start_at` ASC"); + } + + + /** @test */ public function filenameShouldBe2020_05_14_Albert_Camus_Rendez_Vous() { $this->assertContains(['name' => 'Content-Type', @@ -549,7 +569,12 @@ class DriveCheckoutAdminControllerDeleteCheckoutThreeTest extends DriveCheckOutA -class DriveCheckoutAdminControllerPlanNewCheckoutForMauriceTest extends DriveCheckOutAdminControllerTestCase { +class DriveCheckoutAdminControllerPlanNewCheckoutForMauriceTest + extends DriveCheckOutAdminControllerTestCase { + + protected bool $_storm_mock_zend_adapter = true; + protected array $_storm_scopes = ['->findFutureFor']; + public function setUp() { parent::setUp(); Class_DriveCheckout::find(3)->delete(); @@ -557,6 +582,13 @@ class DriveCheckoutAdminControllerPlanNewCheckoutForMauriceTest extends DriveChe } + /** @test */ + public function shouldHaveQueriedNextCheckoutOnEachLibrary() { + $this->assertSql("SELECT `drive_checkout`.* FROM `drive_checkout` WHERE (`drive_checkout`.`library_id` = 1 AND `drive_checkout`.`user_id` = 4 AND LEFT(`drive_checkout`.`start_at`, 10) >= '2023-03-27') ORDER BY `drive_checkout`.`start_at` ASC LIMIT 1"); + $this->assertSql("SELECT `drive_checkout`.* FROM `drive_checkout` WHERE (`drive_checkout`.`library_id` = 2 AND `drive_checkout`.`user_id` = 4 AND LEFT(`drive_checkout`.`start_at`, 10) >= '2023-03-27') ORDER BY `drive_checkout`.`start_at` ASC LIMIT 1"); + } + + /** @test */ public function pageTitleShouldBePlanCheckout() { $this->assertXPathContentContains('//title', 'Planifier un retrait pour maurice'); @@ -648,7 +680,9 @@ class DriveCheckoutAdminControllerDownloadCheckoutThreeTest -class DriveCheckoutAdminControllerPlanInvalidCheckoutForMauriceHotelDieuOnMayFourteenthTest extends DriveCheckOutAdminControllerTestCase { +class DriveCheckoutAdminControllerPlanInvalidCheckoutForMauriceHotelDieuOnMayFourteenthTest + extends DriveCheckOutAdminControllerTestCase { + public function setUp() { parent::setUp(); Class_DriveCheckout::find(3)->delete();