diff --git a/FEATURES/104332 b/FEATURES/104332
new file mode 100644
index 0000000000000000000000000000000000000000..5b329a5eb2b027425da0a8c00574440c24815950
--- /dev/null
+++ b/FEATURES/104332
@@ -0,0 +1,10 @@
+        '104332' =>
+            ['Label' => $this->_('Tableau des prêts PNB : ajout des genres et sections, sélection multiple'),
+             'Desc' =>  $this->_('Tableau des prêts PNB : possibilité d\'afficher les genres et sections. Sélection multiple possible d\'album'),
+             'Image' => '',
+             'Video' => '',
+             'Category' => '',
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Configuration_du_PNB_Dilicom#Tableau_de_bord', 
+             'Test' => '',
+             'Date' => '2021-01-14'],
\ No newline at end of file
diff --git a/FEATURES/116562 b/FEATURES/116562
new file mode 100644
index 0000000000000000000000000000000000000000..b9bed85a8506c8b5ec7efb504cf2539e0c7367fa
--- /dev/null
+++ b/FEATURES/116562
@@ -0,0 +1,10 @@
+        '116562' =>
+            ['Label' => $this->_('Fournisseur de vignettes'),
+             'Desc' => $this->_('Bokeh permet de spécifier les informations permettant l\'accès au service Electre REST API version 2'),
+             'Image' => '',
+             'Video' => '',
+             'Category' => $this->_('Enrichissements'),
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Vignette#Fournisseur_de_vignettes',
+             'Test' => '',
+             'Date' => '2021-01-28'],
\ No newline at end of file
diff --git a/FEATURES/125727 b/FEATURES/125727
new file mode 100644
index 0000000000000000000000000000000000000000..2443168d39bcb49d5a9dce81f57b67bdbcfbce75
--- /dev/null
+++ b/FEATURES/125727
@@ -0,0 +1,10 @@
+        '125727' =>
+            ['Label' => $this->_('Facette nouveauté en résultat de recherche'),
+             'Desc' => $this->_('Ajout de la possibilité d\'afficher une facette "Nouveauté" oui/non basée sur la date de nouveauté la plus élevée parmis tous les exemplaires'),
+             'Image' => '',
+             'Video' => '',
+             'Category' => $this->_('Recherche'),
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Facette_%27Nouveaut%C3%A9%27',
+             'Test' => '',
+             'Date' => '2021-01-14'],
\ No newline at end of file
diff --git a/VERSIONS_WIP/104332 b/VERSIONS_WIP/104332
new file mode 100644
index 0000000000000000000000000000000000000000..c285b1a43d58b5c56fdd656f448160ac64994407
--- /dev/null
+++ b/VERSIONS_WIP/104332
@@ -0,0 +1 @@
+ - ticket #104332 : Tableau des prêts PNB : ajout des genres et sections, sélection multiple
\ No newline at end of file
diff --git a/VERSIONS_WIP/116562 b/VERSIONS_WIP/116562
new file mode 100644
index 0000000000000000000000000000000000000000..6c5659d5b8883ce754ff297bff4416b7ceab306f
--- /dev/null
+++ b/VERSIONS_WIP/116562
@@ -0,0 +1 @@
+ - ticket #116562 : Enrichissements : Ajout de la possibilité de définir les accès aux fournisseur de notices Electre Rest APIv2 pour récupération des vignettes
\ No newline at end of file
diff --git a/VERSIONS_WIP/124826 b/VERSIONS_WIP/124826
new file mode 100644
index 0000000000000000000000000000000000000000..339d10ff93d00b8c1c7fa971d04128921fed7851
--- /dev/null
+++ b/VERSIONS_WIP/124826
@@ -0,0 +1 @@
+ - ticket #124826 : Activités : Ajout de l'age maximum des enfants, ajout d'un envoi courriel de rappel par session d'activité, ajout de l'heure de début et de fin
\ No newline at end of file
diff --git a/VERSIONS_WIP/125727 b/VERSIONS_WIP/125727
new file mode 100644
index 0000000000000000000000000000000000000000..d3e051728ca258acc59923c01351521fcb5211ef
--- /dev/null
+++ b/VERSIONS_WIP/125727
@@ -0,0 +1 @@
+ - ticket #125727 : Résultat de recherche : Ajout de la possibilité d'afficher une facette "Nouveauté" oui/non basée sur la date de nouveauté la plus élevée parmis tous les exemplaires
\ No newline at end of file
diff --git a/application/modules/admin/controllers/SessionActivityController.php b/application/modules/admin/controllers/SessionActivityController.php
index 83253ce498c9622377717a8fa730ca81c6ab7809..2866d07ff0a869951c9896152eaf783eb952647f 100644
--- a/application/modules/admin/controllers/SessionActivityController.php
+++ b/application/modules/admin/controllers/SessionActivityController.php
@@ -53,21 +53,13 @@ class Admin_SessionActivityController extends ZendAfi_Controller_Action {
       ->addColumn('', 'value')
       ;
 
-    $content = $this->view
-      ->renderCsv($description,
-                  [new Class_Entity(['Label' => $this->_('Activité'),
-                                     'Value' => $session->getLibelleActivity()]),
-                  new Class_Entity(['Label' => $this->_('Session'),
-                                    'Value' => $this->view->humanDate($session->getDateDebut(),
-                                                                      'd MMMM YYYY')]),
-                  new Class_Entity(['Label' => $this->_('Durée'),
-                                    'Value' => $session->getDuree() . 'h']),
-                  new Class_Entity(['Label' => $this->_('Effectif'),
-                                    'Value' => $session->getAttendeesMin()
-                                    . '-' . $session->getAttendeesMax()])]);
+    $content = $this->view->renderCsv($description, $session->getMetaDatas());
+
+    $description = (new Class_TableDescription_SessionActivityInscriptionsExport(''))
+      ->setAgeChildMax($session->getAgeChildMax());
 
     $content .= "\n"
-      . $this->view->renderCsv((new Class_TableDescription_SessionActivityInscriptionsExport('')),
+      . $this->view->renderCsv($description,
                                $session->getInscriptionsSortedByName());
 
     $this->_helper->csv(sprintf('session_%d.csv', $session->getId()),
diff --git a/cosmogramme/sql/patch/patch_396.php b/cosmogramme/sql/patch/patch_396.php
new file mode 100644
index 0000000000000000000000000000000000000000..d127800ac629e9d5648192da4edc85c4519c193c
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_396.php
@@ -0,0 +1,19 @@
+<?php
+
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+
+try {
+  $adapter->query('ALTER TABLE `session_activity` '
+                  . 'ADD COLUMN `age_child_max` int(11) unsigned not null default 0,'
+                  . 'ADD COLUMN `all_day` tinyint(1) unsigned not null default 1,'
+                  . 'MODIFY `date_debut` datetime,'
+                  . 'MODIFY `date_fin` datetime'
+  );
+} catch(Exception $e) {}
+
+try {
+  $adapter->query('ALTER TABLE `session_activity_inscriptions` '
+                  . 'ADD COLUMN `notified_at` datetime,'
+                  . 'ADD KEY `notified_at` (`notified_at`)'
+  );
+} catch(Exception $e) {}
diff --git a/cosmogramme/tests/php/classes/KohaPeriodiquesTest.php b/cosmogramme/tests/php/classes/KohaPeriodiquesTest.php
index aa9e530cf645174aef5287228255c20b5f92f3de..6a0917953eeb48d8dadd20591485c828708d7ac5 100644
--- a/cosmogramme/tests/php/classes/KohaPeriodiquesTest.php
+++ b/cosmogramme/tests/php/classes/KohaPeriodiquesTest.php
@@ -659,7 +659,7 @@ class KohaPeriodiquesMatriculeAngesTest extends KohaPeriodiquesWithArticlesTestC
 
     $title->updateFacetsFromExemplaires();
 
-    $this->assertEquals('D800 Lfre Tper_title B1 YCHYJR',
+    $this->assertEquals('D800 Lfre Tper_title B1 YCHYJR HNRNR0001',
                         $title->getFacettes());
   }
 
diff --git a/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php b/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
index eb8932f3b92d7de859959eaab17011ccd5a0d44f..171fcb29a5cc0a205e29c67aed00fc07aba6af9c 100644
--- a/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/KohaRecordIntegrationTest.php
@@ -233,7 +233,7 @@ class KohaRecordIntegrationFacetPresseTest extends KohaRecordIntegrationTestCase
 
   /** @test */
   public function facettesShouldContainsT2AndHANNE0001AndHMOIS0001AndJOUR0001() {
-    $this->assertEquals('HANNE0001 HMOIS0001 HJOUR0001 Lfre T2 B1 YMEDSTMAR',
+    $this->assertEquals('HANNE0001 HMOIS0001 HJOUR0001 Lfre T2 B1 YMEDSTMAR HNRNR0001',
                         Class_Notice::find(1)
                         ->updateFacetsFromExemplaires()
                         ->getFacettes());
@@ -242,7 +242,7 @@ class KohaRecordIntegrationFacetPresseTest extends KohaRecordIntegrationTestCase
 
     /** @test */
   public function facettesForRecord25ShouldContainsT2AndHANNE0001AndHMOIS0001() {
-    $this->assertEquals('HANNE0001 HMOIS0007 HJOUR0006 Lfre T2 B1 YMEDSTMAR',
+    $this->assertEquals('HANNE0001 HMOIS0007 HJOUR0006 Lfre T2 B1 YMEDSTMAR HNRNR0001',
                         Class_Notice::find(25)
                         ->updateFacetsFromExemplaires()
                         ->getFacettes());
diff --git a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
index 7eb4afa2c5345eb4ed2c330b1c1e2c167f8f11d5..9dc927120c59e4c3df7bed2afc483e2e60ab4eae 100644
--- a/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/NoticeIntegrationTest.php
@@ -1239,7 +1239,7 @@ class NoticeIntegrationArchivesAlsaceTest extends NoticeIntegrationTestCase {
 
   /** @test */
   public function facettesWithExemplairesShouldContainsM1AndM2AndD1() {
-    $this->assertEquals('D94438 A1 M1 M2 Lfre T1 B1 YBibliothèque des Dominicains',
+    $this->assertEquals('D94438 A1 M1 M2 Lfre T1 B1 YBibliothèque des Dominicains HNRNR0001',
                         Class_Notice::find(1)->updateFacetsFromExemplaires()->getFacettes());
   }
 }
diff --git a/library/Class/AdminVar.php b/library/Class/AdminVar.php
index fa82bfa15922dcb1adea37aa7a784b114b1756a6..43188e543592dfd52643a493649226968c4f4ec2 100644
--- a/library/Class/AdminVar.php
+++ b/library/Class/AdminVar.php
@@ -140,6 +140,7 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
        'drive-checkout' => $this->_getDriveCheckoutVars(),
        'journal' => $this->_getJournalVars(),
        'activity' => $this->_getActivityVars(),
+       'pellicule' => $this->_getPelliculeVars(),
        ];
   }
 
@@ -210,7 +211,9 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
 
             'DILICOM_PNB_HOLD_AVAILABLE_MAIL' => Class_AdminVar_Meta::newEditor($this->_('Email de notification de disponibilité de reservation numérique'), ['value'=>'Bonjour {user_name},<p>Le document <a href="{record_url}">{record_title}</a> vous est réservé pour emprunt jusqu\'au {hold_expiration_date}.</p>']),
             'DILICOM_PNB_RECORD_MAX_HOLD_COUNT' => Class_AdminVar_Meta::newDefault($this->_('Nombre maximum de réservations par document numérique'), ['value' => 3]),
-            'DILICOM_PNB_PATRON_MAX_HOLD_COUNT' => Class_AdminVar_Meta::newDefault($this->_('Nombre maximum de réservations par utilisateur'), ['value' => 2])];
+            'DILICOM_PNB_PATRON_MAX_HOLD_COUNT' => Class_AdminVar_Meta::newDefault($this->_('Nombre maximum de réservations par utilisateur'), ['value' => 2]),
+            'DILICOM_PNB_BOARD_DISPLAY_SECTION' => Class_AdminVar_Meta::newOnOff($this->_('Affichage des sections et genres dans le tableau d\'administration des prêts PNB'))]
+      ;
   }
 
 
@@ -571,6 +574,10 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
       ];
   }
 
+  protected function _getPelliculeVars() {
+    return ['PELLICULE_SETTINGS' => (new Class_AdminVar_PelliculeSettings)->meta()];
+  }
+
 
   protected function _getJournalVars() {
     $stormModels = new Class_AdminVar_JournalStormModels();
@@ -590,7 +597,11 @@ class Class_AdminVarLoader extends Storm_Model_Loader {
 
   protected function _getActivityVars() {
     return ['ACTIVITY' => Class_AdminVar_Meta::newOnOff($this->_('Activer ou désactiver le module d\'activité')),
-            'ACTIVITY_SESSION_QUOTAS' => Class_AdminVar_Meta::newOnOff($this->_('Activer ou désactiver la gestion des quotas pour les activités'))];
+            'ACTIVITY_SESSION_QUOTAS' => Class_AdminVar_Meta::newOnOff($this->_('Activer ou désactiver la gestion des quotas pour les activités')),
+            'ACTIVITY_NOTIFICATION_DELAY' => Class_AdminVar_Meta::newDefault($this->_('Délai d\'envoie des rappel pour les activités (en jours)'), ['value' => '0']),
+            'ACTIVITY_NOTIFICATION_SUBJECT' => Class_AdminVar_Meta::newDefault($this->_('Sujet de l\'email de rappel pour les activités'), ['value'=> 'Rappel : {session.libelle_activity}']),
+            'ACTIVITY_NOTIFICATION_BODY' => Class_AdminVar_Meta::newEditor($this->_('Contenu de l\'email de rappel pour les activités'), ['value' => '<p>Bonjour {stagiaire.nom_complet},</p><p>L\'activité {session.libelle_activity} à laquelle vous êtes inscrit, commencera le {session.date_debut_texte}.</p><p>Cordialement</p>'])
+    ];
   }
 
 
diff --git a/library/Class/AdminVar/PelliculeSettings.php b/library/Class/AdminVar/PelliculeSettings.php
new file mode 100644
index 0000000000000000000000000000000000000000..73a6d3f3bb7f1c31d9862aa42e4784d343cc6650
--- /dev/null
+++ b/library/Class/AdminVar/PelliculeSettings.php
@@ -0,0 +1,90 @@
+<?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
+ */
+
+
+class Class_AdminVar_PelliculeSettings {
+  use Trait_Translator;
+  const
+    PROVIDER_KEY = 'Provider',
+    LOGIN_KEY = 'Login',
+    PASSWORD_KEY = 'Password',
+
+    ELECTRE_REST_2 = 'electre_rest_2';
+
+  protected $_settings;
+
+  public function headers() {
+    if ((!$settings = json_decode(Class_AdminVar::get('PELLICULE_SETTINGS'), true))
+        || !isset($settings[static::PROVIDER_KEY])
+        || !is_array($settings[static::PROVIDER_KEY]))
+      return [];
+
+    $this->_settings = $settings;
+
+    return array_filter(array_map([$this, '_header'],
+                                  $settings[static::PROVIDER_KEY],
+                                  array_keys($settings[static::PROVIDER_KEY])));
+  }
+
+
+  public function meta() {
+    $options = ['fields' => [
+                             ['name' => static::PROVIDER_KEY,
+                              'label' => $this->_('Fournisseur'),
+                              'type' => 'select',
+                              'options' => [static::ELECTRE_REST_2 => $this->_('Electre v2 REST')],
+                             ],
+                             ['name' => static::LOGIN_KEY,
+                              'label' => $this->_('Identifiant')],
+                             ['name' => static::PASSWORD_KEY,
+                              'label' => $this->_('Mot de passe')
+                             ]],
+
+                'fixed' => true];
+
+    $label = $this->_('Identifiant auprès d\'un fournisseur de vignettes');
+
+    return Class_AdminVar_Meta::newMultiInput($label, ['options' => $options, 'value' => '']);
+  }
+
+
+  protected function _header($provider, $position) {
+    if ($provider
+        && ($credentials = $this->_credentialsFor($provider, $position)))
+      return ['Authorization'=> 'Pellicule ' . base64_encode(json_encode($credentials))];
+  }
+
+
+  protected function _credentialsFor($provider, $position) {
+    if (($id = $this->_getAtPosition(static::LOGIN_KEY, $position))
+        && ($secret = $this->_getAtPosition(static::PASSWORD_KEY, $position)))
+      return ['provider' => $provider,
+              'client_id' => $id,
+              'client_secret' => $secret];
+  }
+
+
+  protected function _getAtPosition($key, $position) {
+    return isset($this->_settings[$key][$position])
+      ? $this->_settings[$key][$position]
+      : null;
+  }
+}
diff --git a/library/Class/Album/Item.php b/library/Class/Album/Item.php
index a8df06267be322a0f1d28d303e3903ddea480224..405267127439d458778814eab0235d3af2da3dd9 100644
--- a/library/Class/Album/Item.php
+++ b/library/Class/Album/Item.php
@@ -127,4 +127,18 @@ class Class_Album_Item extends Storm_Model_Abstract {
     $this->getUsageConstraints()->setLoanQuantity($quantity);
     return $this;
   }
+
+
+  public function getAlbumSectionIds() {
+    if ($album = $this->getAlbum())
+      return explode(';',$album->getSections());
+    return [];
+  }
+
+
+  public function getAlbumGenreIds() {
+    if ($album = $this->getAlbum())
+      return explode(';',$album->getGenre());
+    return [];
+  }
 }
diff --git a/library/Class/Batch.php b/library/Class/Batch.php
index ff78f8f303a901ba68983f6825847325b349fdfc..7c4aa6b984df3dfd1f771e5e7c04e1dec38f0fc7 100644
--- a/library/Class/Batch.php
+++ b/library/Class/Batch.php
@@ -44,6 +44,7 @@ class Class_BatchLoader extends Storm_Model_Loader {
                         Class_Batch_ExternalAgenda::TYPE => new Class_Batch_ExternalAgenda(),
                         Class_Batch_FederationReviewHarvest::TYPE => new Class_Batch_FederationReviewHarvest(),
                         Class_Batch_SendRendezVousNotification::TYPE => new Class_Batch_SendRendezVousNotification(),
+                        Class_Batch_ActivitiesNotifications::TYPE => new Class_Batch_ActivitiesNotifications(),
                        ]);
   }
 
diff --git a/library/Class/Batch/ActivitiesNotifications.php b/library/Class/Batch/ActivitiesNotifications.php
new file mode 100644
index 0000000000000000000000000000000000000000..0aa9e7ec467aa804497720ee165e8164e8690bad
--- /dev/null
+++ b/library/Class/Batch/ActivitiesNotifications.php
@@ -0,0 +1,45 @@
+<?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
+ */
+
+
+class Class_Batch_ActivitiesNotifications extends Class_Batch_Abstract {
+
+  const TYPE = 'ACTIVITIES_NOTIFICATIONS';
+
+  public function run() {
+    $sessions = Class_SessionActivity::findAllNotifiable();
+    foreach($sessions as $session) {
+      (new Class_SessionActivity_Notification($session))
+        ->setLogger($this->getLogger())
+        ->notify();
+    }
+  }
+
+
+  public function isEnabled() {
+    return Class_AdminVar::isActivityEnabled();
+  }
+
+
+  public function getLabel() {
+    return $this->_('Notifier les prochaines activités');
+  }
+}
diff --git a/library/Class/Catalogue.php b/library/Class/Catalogue.php
index c24c70ddb03ee2cb89cbc65119ab72f82a1c44a1..d3ee2453d3f0dda5cda05ec3eec0f8e27e89b0fd 100644
--- a/library/Class/Catalogue.php
+++ b/library/Class/Catalogue.php
@@ -1142,7 +1142,7 @@ class Class_Catalogue extends Storm_Model_Abstract {
 
 
   public function getThesaurusForId($thesaurus) {
-    $startWith = substr($thesaurus->getIdThesaurus(), 0, Class_Notice_Facette::NB_CHAR_THESAURUS);
+    $startWith = substr($thesaurus->getIdThesaurus(), 0, Class_CodifThesaurus::ID_KEY_LENGTH);
 
     $ids='';
     $thesaurus_ids = explode(';',$this->getThesaurus());
diff --git a/library/Class/CodifThesaurus.php b/library/Class/CodifThesaurus.php
index 96b7cf158636b228fe908a303cffa07d42efc0a9..309d09f80ea7701e0454467272026c4ce34d0773 100644
--- a/library/Class/CodifThesaurus.php
+++ b/library/Class/CodifThesaurus.php
@@ -69,6 +69,23 @@ class CodifThesaurusLoader extends Storm_Model_Loader {
   }
 
 
+  public function ensureRecordNovelty() {
+    $this->recordNoveltyFor(true);
+    $this->recordNoveltyFor(false);
+  }
+
+
+  /** @param $flag boolean */
+  public function recordNoveltyFor($flag) {
+    $attributes = ((bool) $flag)
+      ? ['Id' => 1, 'Libelle' => $this->_('Oui')]
+      : ['Id' => 2, 'Libelle' => $this->_('Non')];
+
+    return $this->ensureForModelUnderRoot(new Class_Entity($attributes),
+                                          $this->_fixed['RecordNovelty']);
+  }
+
+
   public function ensureForModelUnderRoot($model, $definition) {
     return ($root = Class_CodifThesaurus::findRootOfId($definition->getId(),
                                                        $definition->getCode(),
@@ -213,7 +230,7 @@ class CodifThesaurusLoader extends Storm_Model_Loader {
 
 
   public function findParent($id_thesaurus) {
-    $parent_str=substr($id_thesaurus,0,strlen($id_thesaurus)-(4-strlen($id_thesaurus)%4));
+    $parent_str = substr($id_thesaurus, 0, strlen($id_thesaurus) - (4 - strlen($id_thesaurus) % 4));
     if (!$parent_str)
       return null;
 
@@ -298,7 +315,7 @@ class CodifThesaurusLoader extends Storm_Model_Loader {
 
 
   public function getIndices($pere, $all = false ) {
-    if(Class_CodifThesaurus::CODE_ROOT != $pere) {
+    if (Class_CodifThesaurus::CODE_ROOT != $pere) {
       $pere = $this->getCodeSur4Chiffres($pere);
       $length = strlen($pere);
 
@@ -306,17 +323,32 @@ class CodifThesaurusLoader extends Storm_Model_Loader {
                                               'order'=> 'id_thesaurus']);
     }
 
-    $where = '';
-
     // while search result do not handle item novelty correctly
-    $to_exclude = [$this->fixedCodeOf('LibraryNovelty'),
-                   $this->fixedCodeOf('AnnexeNovelty')];
+    $to_exclude = [$this->fixedIdOf('LibraryNovelty'),
+                   $this->fixedIdOf('AnnexeNovelty')];
     if (!$all)
-      $to_exclude[] = $this->fixedCodeOf('Domain');
+      $to_exclude[] = $this->fixedIdOf('Domain');
+
+    return $this->_findAllFromRootExcluding($to_exclude);
+  }
+
+
+  public function findAllForDomainCriteria() {
+    // novelty is a criteria in itself
+    // and no domains in domains
+
+    return $this
+      ->_findAllFromRootExcluding([$this->fixedIdOf('RecordNovelty'),
+                                   $this->fixedIdOf('LibraryNovelty'),
+                                   $this->fixedIdOf('AnnexeNovelty'),
+                                   $this->fixedIdOf('Domain')]);
+  }
 
-    $where = sprintf('code not in ("%s") and ', implode('", "', $to_exclude));
 
-    $where .= 'LENGTH(id_thesaurus) in (1,4)';
+  protected function _findAllFromRootExcluding($to_exclude=[]) {
+    $where = sprintf('id_thesaurus not in ("%s") and LENGTH(id_thesaurus) in (1,%d)',
+                     implode('", "', $to_exclude),
+                     Class_CodifThesaurus::ID_KEY_LENGTH);
 
     return Class_CodifThesaurus::findAllBy(['where' => $where,
                                             'order' => 'id_thesaurus']);
diff --git a/library/Class/CodifThesaurusFixed.php b/library/Class/CodifThesaurusFixed.php
index f71397a05ef6d01dc31f775e8a41b5ef449c1297..a8e25790274c75022b56f11740e7dcc5d3971852 100644
--- a/library/Class/CodifThesaurusFixed.php
+++ b/library/Class/CodifThesaurusFixed.php
@@ -27,6 +27,7 @@ class Class_CodifThesaurusFixed extends Class_Entity {
      'CustomField' => ['CFCF', 'Custom fields', 'Champs personnalisés'],
      'LibraryNovelty' => ['NNNN', 'Nouveauté par bibliothèque', 'Nouveauté par bibliothèque'],
      'AnnexeNovelty' => ['NANA', 'Nouveauté par annexe', 'Nouveauté par annexe'],
+     'RecordNovelty' => ['NRNR', 'Nouveauté', 'Nouveauté'],
      'DigitalResources' => ['DRDR', 'Digital resources', 'Ressources numériques'],
      'StreetName' => ['STRE', 'StreetName', 'Nom de rue'],
      'LocationType' => ['LOCA', 'LocationType', 'Type de lieu'],
diff --git a/library/Class/MultiSelection/Abstract.php b/library/Class/MultiSelection/Abstract.php
index 6902ec1359d5d37bd34c06c35a0129a8baaa6b34..120705ace2b72809ec63409afbce94c437f67fdb 100644
--- a/library/Class/MultiSelection/Abstract.php
+++ b/library/Class/MultiSelection/Abstract.php
@@ -59,10 +59,7 @@ abstract class Class_MultiSelection_Abstract {
   public function isFullWith($values, $limit) {
     if(count($values) > $limit)
       return true;
-
-    $temp_storage = clone($this->_getStorage());
-    $temp_storage->addValues($values);
-    return count($temp_storage->getValues()) > $limit;
+    return (count($values) + $this->countAlreadySelected()) > $limit;
   }
 
 
@@ -78,6 +75,11 @@ abstract class Class_MultiSelection_Abstract {
   }
 
 
+  public function countAlreadySelected() {
+    return $this->_getStorage()->count();
+  }
+
+
   public function contains($id) {
     return $this->_getStorage()->contains($id);
   }
diff --git a/library/Class/MultiSelection/AlbumItem.php b/library/Class/MultiSelection/AlbumItem.php
new file mode 100644
index 0000000000000000000000000000000000000000..a9fc7f38705d10c329ac40c8f45068cfb3c0bf25
--- /dev/null
+++ b/library/Class/MultiSelection/AlbumItem.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, 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
+ */
+
+
+class Class_MultiSelection_AlbumItem extends Class_MultiSelection_Album {
+
+  public function acceptActionsVisitor($visitor) {
+    $visitor
+      ->visitControllerName('album')
+
+      ->visitLeafCondition(function($model)
+                           {
+                             return $this->isSelected($model->getAlbum()->getId());
+                           })
+
+      ->visitAddLeaf(function($model)
+                     {
+                       return $this->_('Ajouter %s à la sélection', $model->getTitre());
+                     })
+
+      ->visitRemoveLeaf(function($model)
+                        {
+                          return $this->_('Supprimer %s de la sélection', $model->getTitre());
+                        });
+
+    return $this;
+  }
+}
\ No newline at end of file
diff --git a/library/Class/MultiSelection/SessionStorage.php b/library/Class/MultiSelection/SessionStorage.php
index ae982ba321698722041b1c1134ae967476d503b8..3738bfceee05ae80de8b36563b060ef56b514341 100644
--- a/library/Class/MultiSelection/SessionStorage.php
+++ b/library/Class/MultiSelection/SessionStorage.php
@@ -46,6 +46,11 @@ class Class_MultiSelection_SessionStorage {
   }
 
 
+  public function count() {
+    return count($this->getValues());
+  }
+
+
   public function addValues($values) {
     $call_back = function ($selected_models, $values) {
       $selected_models = array_merge($selected_models, $values);
diff --git a/library/Class/Notice.php b/library/Class/Notice.php
index 526a81eca2314d7f9d2f7935fc91beaa1c6f1545..eab5fa9fbece3a1173927f6ee1d51c722fae3237 100644
--- a/library/Class/Notice.php
+++ b/library/Class/Notice.php
@@ -153,15 +153,39 @@ class NoticeLoader extends Storm_Model_Loader {
 
 
   public function indexNoveltyFacets() {
-    $page = 1;
-    while($records = Class_Notice::findAllBy(['where' => 'match(facettes) against("+(HNNNN* HNANA*)" in boolean mode)',
-                                              'limitPage' => [$page, 100]])) {
-      foreach($records as $record)
-        $record->updateNoveltyFacets();
-
-      $this->_cleanMemory();
-      $page++;
+    $facets = ['HNNNN*', 'HNANA*'];
+    if ($novelty_thesaurus = Class_CodifThesaurus::recordNoveltyFor(true))
+      $facets[] = $novelty_thesaurus->getFacetCode();
+    $facets = implode(' ', $facets);
+
+    $record_id = 0;
+    while ($records = $this->_findNoveltyRecordsFrom($facets, $record_id))
+      $record_id = $this->_updateNovelty($records);
+  }
+
+
+  protected function _findNoveltyRecordsFrom($facets, $record_id) {
+    if (null === $record_id)
+      return [];
+
+    $where = sprintf('match(facettes) against("+(%s)" in boolean mode) and id_notice > %d',
+                     $facets, $record_id);
+
+    return Class_Notice::findAllBy(['where' => $where,
+                                    'order' => 'id_notice',
+                                    'limit' => 100]);
+  }
+
+
+  protected function _updateNovelty($records) {
+    $record_id = null;
+    foreach($records as $record) {
+      $record->updateNoveltyFacets();
+      $record_id = $record->getId();
     }
+    $this->_cleanMemory();
+
+    return $record_id;
   }
 }
 
@@ -1790,25 +1814,6 @@ class Class_Notice extends Storm_Model_Abstract {
   }
 
 
-
-  public function getFacetCodesWithoutItemFacets() {
-    $filtered = [Class_Bib::CODE_FACETTE,
-                 Class_CodifEmplacement::CODE_FACETTE,
-                 Class_CodifSection::CODE_FACETTE,
-                 Class_CodifAnnexe::CODE_FACETTE,
-                 Class_CodifTypeDoc::CODE_FACETTE,
-                 Class_Codification::CODE_DATE_NOUVEAUTE,
-                 Class_Codification::CODE_AVAILABILITY];
-
-    return array_filter($this->getFacetCodes(),
-                        function($code) use ($filtered)
-                        {
-                          return !in_array(substr($code, 0, 1),
-                                           $filtered);
-                        });
-  }
-
-
   protected function _fetchItemsToInjectInFacets() {
     if ($this->isPeriodiqueArticle() && ($linked_record = $this->getLinkedSerialRecord()))
       return $linked_record->getExemplaires();
@@ -1828,19 +1833,29 @@ class Class_Notice extends Storm_Model_Abstract {
 
 
   public function updateFacetsFromExemplaires() {
-    $facettes = $this->getFacetCodesWithoutItemFacets();
-    $facettes []= Class_CodifTypeDoc::CODE_FACETTE. $this->getTypeDoc();
+    $facets = Class_Notice_Facette::fromStringWithoutItemFacets($this->getFacettes());
+    $facets = array_map(function($facet) { return $facet->getCle(); },
+                        $facets);
 
+    $facets[] = Class_CodifTypeDoc::CODE_FACETTE . $this->getTypeDoc();
+
+    $is_novelty = false;
     $date_nouveaute = '';
     foreach($this->_fetchItemsToInjectInFacets() as $exemplaire) {
-      $facettes = array_merge($facettes, $exemplaire->getFacets());
+      $facets = array_merge($facets, $exemplaire->getFacets());
+
       $date_nouveaute = ($exemplaire->getDateNouveaute() > $date_nouveaute)
         ? $exemplaire->getDateNouveaute()
         : $date_nouveaute;
+
+      $is_novelty = $is_novelty || $exemplaire->isNouveaute();
     }
 
+    if ($novelty_thesaurus = Class_CodifThesaurus::recordNoveltyFor($is_novelty))
+      $facets[] = $novelty_thesaurus->getFacetCode();
+
     return $this
-      ->setFacettes(array_unique($facettes))
+      ->setFacettes(array_unique($facets))
       ->setDateCreation($date_nouveaute ? ($date_nouveaute . ' 00:00:00') : null);
   }
 
@@ -1858,12 +1873,17 @@ class Class_Notice extends Storm_Model_Abstract {
     $this->deleteFacettes(implode(' ', $existings));
 
     $facets = [];
+    $is_novelty = false;
     foreach($this->getExemplaires() as $item)
       if ($item->isNouveaute()) {
         $facets[] = Class_CodifThesaurus::findForItemLibraryNovelty($item);
         $facets[] = Class_CodifThesaurus::findForItemAnnexeNovelty($item);
+        $is_novelty = true;
       }
 
+    if ($novelty_thesaurus = Class_CodifThesaurus::recordNoveltyFor($is_novelty))
+      $facets[] = $novelty_thesaurus->getFacetCode();
+
     $this->updateFacette(implode(' ', $facets))->save();
     return $this;
   }
diff --git a/library/Class/Notice/Facette.php b/library/Class/Notice/Facette.php
index 7c8f320b8bffb4e03b15cf2205fb04c784c85c2e..96c33793baf6f484fc6c7378c47595aa8c6f69f8 100644
--- a/library/Class/Notice/Facette.php
+++ b/library/Class/Notice/Facette.php
@@ -19,13 +19,13 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class Class_Notice_Facette {
-  const NB_CHAR_THESAURUS = 4;
-
   protected static $_instances = [];
 
   protected
     $_cle,
-    $_rubrique;
+    $_rubrique,
+    $_group_code,
+    $_value;
 
 
   public static function fromStringFiltered($facet_string, $callback) {
@@ -35,6 +35,12 @@ class Class_Notice_Facette {
   }
 
 
+  public static function fromStringWithoutItemFacets($facet_string) {
+    return static::fromStringFiltered($facet_string,
+                                      function($facet) { return !$facet->isItem(); });
+  }
+
+
   public static function parseFacettesFromNoticeField($str_facettes) {
     $items = array_filter(explode(' ', trim($str_facettes)));
     $facettes = [];
@@ -88,12 +94,16 @@ class Class_Notice_Facette {
 
 
   public function getValue() {
-    return substr($this->_cle, 1);
+    return $this->_value = $this->_value
+      ? $this->_value
+      : substr($this->_cle, 1);
   }
 
 
   protected function getGroupCodeFromKey() {
-    return static::extractCodeRubrique($this->_cle);
+    return $this->_group_code = $this->_group_code
+      ? $this->_group_code
+      : static::extractCodeRubrique($this->_cle);
   }
 
 
@@ -122,55 +132,98 @@ class Class_Notice_Facette {
   public function isNovelty() {
     return in_array(substr($this->getGroupCodeFromKey(), 1),
                     [Class_CodifThesaurus::fixedIdOf('LibraryNovelty'),
-                     Class_CodifThesaurus::fixedIdOf('AnnexeNovelty')]);
+                     Class_CodifThesaurus::fixedIdOf('AnnexeNovelty'),
+                     Class_CodifThesaurus::fixedIdOf('RecordNovelty')]);
   }
 
 
   public function isAvailability() {
-    return Class_Codification::CODE_AVAILABILITY == $this->getGroupCodeFromKey();
+    return $this->isGroupCode(Class_Codification::CODE_AVAILABILITY);
   }
 
 
   public function isAuthor() {
-    return Class_CodifAuteur::CODE_FACETTE == $this->getGroupCodeFromKey();
+    return $this->isGroupCode(Class_CodifAuteur::CODE_FACETTE);
   }
 
 
   public function isMatter() {
-    return Class_CodifMatiere::CODE_FACETTE == $this->getGroupCodeFromKey();
+    return $this->isGroupCode(Class_CodifMatiere::CODE_FACETTE);
   }
 
 
   public function isDynamicFacet() {
-    return Class_CodifThesaurus::CODE_FACETTE === substr($this->_cle, 0, 1)
+    return $this->isThesaurus()
       && strlen($this->getValue()) === (Class_CodifThesaurus::ID_KEY_LENGTH * 2);
   }
 
 
   public function isDynamicFacetRoot() {
-    return Class_CodifThesaurus::CODE_FACETTE === substr($this->_cle, 0, 1)
+    return $this->isThesaurus()
       && strlen($this->getValue()) === Class_CodifThesaurus::ID_KEY_LENGTH;
   }
 
 
   public function isDocTypeFacet() {
-    return Class_CodifTypeDoc::CODE_FACETTE == $this->getGroupCodeFromKey();
+    return $this->isGroupCode(Class_CodifTypeDoc::CODE_FACETTE);
   }
 
 
   public function isAnnexeFacet() {
-    return Class_CodifAnnexe::CODE_FACETTE == $this->getGroupCodeFromKey();
+    return $this->isGroupCode(Class_CodifAnnexe::CODE_FACETTE);
   }
 
 
   public function isLibraryFacet() {
-    return Class_Bib::CODE_FACETTE == $this->getGroupCodeFromKey();
+    return $this->isGroupCode(Class_Bib::CODE_FACETTE);
   }
 
 
   public function isDomainFacet() {
-    $code = $this->getGroupCodeFromKey();
-    return Class_Catalogue::CODE_FACETTE == $code
-      || Class_CodifThesaurus::CODE_FACETTE == $this->_cle[0];
+    return $this->isGroupCode(Class_Catalogue::CODE_FACETTE) || $this->isThesaurus();
+  }
+
+
+  public function isLocationFacet() {
+    return $this->isGroupCode(Class_CodifEmplacement::CODE_FACETTE);
+  }
+
+
+  public function isSectionFacet() {
+    return $this->isGroupCode(Class_CodifSection::CODE_FACETTE);
+  }
+
+
+  public function isThesaurus() {
+    return Class_CodifThesaurus::CODE_FACETTE === substr($this->_cle, 0, 1);
+  }
+
+
+  public function isRecordNoveltyFacet() {
+    return $this->isDynamicFacetUnder(Class_CodifThesaurus::fixedIdOf('RecordNovelty'));
+  }
+
+
+  public function isDynamicFacetUnder($root_code) {
+    return $this->isDynamicFacet() && substr($this->getGroupCodeFromKey(), 1) === $root_code;
+  }
+
+
+  public function isItem() {
+    return
+      $this->isLibraryFacet()
+      || $this->isLocationFacet()
+      || $this->isSectionFacet()
+      || $this->isAnnexeFacet()
+      || $this->isDocTypeFacet()
+      || $this->isGroupCode(Class_Codification::CODE_DATE_NOUVEAUTE)
+      || $this->isGroupCode(Class_Codification::CODE_AVAILABILITY)
+      || $this->isRecordNoveltyFacet()
+      ;
+  }
+
+
+  public function isGroupCode($code) {
+    return $this->getGroupCodeFromKey() === $code;
   }
 }
\ No newline at end of file
diff --git a/library/Class/SessionActivity.php b/library/Class/SessionActivity.php
index 580a89a333f45f610fedaed88693426519e39ead..ee6b3c1be04a0e0e31dba365b3ea60ab74413df3 100644
--- a/library/Class/SessionActivity.php
+++ b/library/Class/SessionActivity.php
@@ -80,6 +80,15 @@ class SessionActivityLoader extends Storm_Model_Loader {
       ? Class_SessionActivity::findFirstBy(['article_id' => $article->getId()])
       : null;
   }
+
+
+  public function findAllNotifiable() {
+    if (!$delay = (int)Class_AdminVar::get('ACTIVITY_NOTIFICATION_DELAY'))
+      return [];
+
+    $date_clause = 'date_debut >= NOW() and DATEDIFF(date_debut, NOW()) <= ' . $delay;
+    return Class_SessionActivity::findAllBy(['where' => $date_clause]);
+  }
 }
 
 
@@ -115,6 +124,8 @@ class Class_SessionActivity extends Storm_Model_Abstract {
                                           'effectif_max' => 10,
                                           'effectif_child_min' => 0,
                                           'effectif_child_max' => 0,
+                                          'age_child_max' => 0,
+                                          'all_day' => 1,
                                           'effectif_inscription_max' => 0,
                                           'effectif_inscription_child_max' => 0,
                                           'cout' => 0,
@@ -272,35 +283,35 @@ class Class_SessionActivity extends Storm_Model_Abstract {
   }
 
 
+  public function getMetaDatas() {
+    return [new SessionActivity_MetaData($this->_('Activité'), $this->getLibelleActivity()),
+            new SessionActivity_MetaData($this->_('Session'), $this->getDateDebutTexte()),
+            new SessionActivity_MetaData($this->_('Durée'), $this->getDuree() . 'h'),
+            new SessionActivity_MetaData($this->_('Effectif'),
+                                         $this->getAttendeesMin()
+                                         . '-' . $this->getAttendeesMax())];
+  }
+
+
   public function validate() {
     $this->checkAttribute("effectif_max",
                           $this->getEffectifMin() <= $this->getEffectifMax(),
                           $this->_("L'effectif maximum doit être supérieur ou égal à l'effectif minimum"));
 
-    $this->checkAttribute("effectif_max",
-                          $this->getEffectifInscriptionMax() <= $this->getEffectifMax(),
-                          $this->_("L'effectif maximum doit être supérieur ou égal à l'effectif maximum par inscription"));
-
-    $this->checkAttribute("effectif_child_max",
-                          $this->getEffectifChildMin() <= $this->getEffectifChildMax(),
-                          $this->_("L'effectif enfants maximum doit être supérieur ou égal à l'effectif enfants minimum"));
-
-    $this->checkAttribute("effectif_child_max",
-                          $this->getEffectifInscriptionChildMax() <= $this->getEffectifChildMax(),
-                          $this->_("L'effectif enfants maximum doit être supérieur ou égal à l'effectif enfants maximum par inscription"));
-
     $this->checkAttribute("effectif_max",
                           $this->numberOfAttendees() <= $this->getAttendeesMax(),
                           $this->_("Le nombre d'inscrit.e.s ne peut dépasser l'effectif maximum"));
 
+    $this->_checkAttributesWithQuotas();
+
     foreach($this->getStagiaires() as $stagiaire) {
       $this->checkAttribute("effectif_max",
                             $stagiaire->hasRightSuivreActivity(),
                             $this->_("Le stagiaire %s n'a pas les droits suffisants pour suivre une activité", $stagiaire->getLogin()));
     }
 
-    $start_date = DateTime::createFromFormat('!Y-m-d', $this->getDateDebut());
-    $this->checkAttribute('date_debut', $start_date,
+    $start_date = $this->_getFormatDate($this->getDateDebut());
+    $this->checkAttribute('activity_range', $start_date,
                           $this->_('La date de début est requise'));
 
     $limit_date = DateTime::createFromFormat('!Y-m-d', $this->getDateLimiteInscription());
@@ -312,12 +323,43 @@ class Class_SessionActivity extends Storm_Model_Abstract {
                             $limit_date <= $start_date,
                             $this->_("La date limite d'inscription doit être inférieure ou égale à la date de début"));
 
-    if ($this->hasDateFin()
-        && ($end_date = DateTime::createFromFormat('!Y-m-d', $this->getDateFin()))
-        && $start_date)
-      $this->checkAttribute('date_fin',
-                            $end_date >= $start_date,
-                            $this->_("La date de fin doit être supérieure ou égale à la date de début"));
+    if (!$this->hasDateFin())
+      return;
+
+    $this->checkAttribute('activity_range',
+                          $start_date && ($end_date = $this->_getFormatDate($this->getDateFin())) && $end_date >= $start_date,
+                          $this->_("La date de fin doit être supérieure ou égale à la date de début"));
+  }
+
+
+  protected function _checkAttributesWithQuotas() {
+    if (!Class_AdminVar::isActivitySessionQuotasEnabled())
+      return;
+
+    $this->checkAttribute("effectif_max",
+                          $this->getEffectifInscriptionMax() <= $this->getEffectifMax(),
+                          $this->_("L'effectif maximum doit être supérieur ou égal à l'effectif maximum par inscription"));
+
+    $this->checkAttribute("effectif_child_max",
+                          $this->getEffectifChildMin() <= $this->getEffectifChildMax(),
+                          $this->_("L'effectif enfants maximum doit être supérieur ou égal à l'effectif enfants minimum"));
+
+    $this->checkAttribute("effectif_child_max",
+                          $this->getEffectifInscriptionChildMax() <= $this->getEffectifChildMax(),
+                          $this->_("L'effectif enfants maximum doit être supérieur ou égal à l'effectif enfants maximum par inscription"));
+
+    $this->checkAttribute('age_child_max',
+                          $this->getEffectifChildMax() <= 0 || $this->getAgeChildMax() > 0,
+                          $this->_("L'âge maximum pour les enfants doit être renseigné"));
+
+    $this->checkAttribute("effectif_inscription_max",
+                          $this->getEffectifMax() <= 0 || $this->getEffectifInscriptionMax() > 0,
+                          $this->_("Adultes maximum par inscription doit être renseigné"));
+
+    $this->checkAttribute("effectif_inscription_child_max",
+                          $this->getEffectifChildMax() <= 0
+                          || $this->getEffectifInscriptionChildMax() > 0,
+                          $this->_("Enfants maximum par inscription doit être renseigné"));
   }
 
 
@@ -405,6 +447,14 @@ class Class_SessionActivity extends Storm_Model_Abstract {
   }
 
 
+  protected function _getFormatDate($date) {
+    if (!$date || strlen($date) < 10)
+      return false;
+
+    return DateTime::createFromFormat('!Y-m-d', substr($date, 0, 10));
+  }
+
+
   protected function _getHumanDate($date) {
     return strftime(static::HUMAN_DATE_FORMAT, strtotime($date));
   }
@@ -503,6 +553,7 @@ class Class_SessionActivity extends Storm_Model_Abstract {
 
 
 
+
 class Class_SessionActivity_Article
   extends Storm_Model_Association_HasOne {
   use Trait_Translator;
@@ -564,11 +615,15 @@ class Class_SessionActivity_Article
   protected function _synchronize($model, $dependent, $force_content=false) {
     $dependent
       ->setTitre($model->getLibelleActivity())
-      ->setEventsDebut($this->_replaceDateInto($dependent->getEventsDebut(), $model->getDateDebut()))
+      ->setEventsDebut($this->_replaceDateInto($dependent->getEventsDebut(),
+                                               $model->getDateDebut(),
+                                               $model->getAllDay()))
       ->setEventsFin($this->_replaceDateInto($dependent->getEventsFin(),
                                              $model->hasDateFin()
                                              ? $model->getDateFin()
-                                             : $model->getDateDebut()))
+                                             : $model->getDateDebut(),
+                                             $model->getAllDay()))
+      ->setAllDay($model->getAllDay())
       ->setLieu($model->getLieu());
 
     if (!$dependent->getContenu() || $force_content)
@@ -579,15 +634,19 @@ class Class_SessionActivity_Article
   }
 
 
-  protected function _replaceDateInto($old_datetime, $new_date) {
-    return $new_date
-      . (10 < strlen($old_datetime) ? substr($old_datetime, 10) : '');
+  protected function _replaceDateInto($old_datetime, $new_date, $is_all_day) {
+    return $is_all_day
+      ? $new_date . (10 < strlen($old_datetime) ? substr($old_datetime, 10) : '')
+      : $new_date;
   }
 
 
   protected function _contentFrom($model) {
-    return
-      $model->getActivityDescription()
+    $content = ($content = $model->getContenu())
+      ? $content
+      : $model->getActivityDescription();
+
+    return $content
       . $this->_getInformations($model)
       . $this->_getRegisterLink($model);
   }
@@ -661,4 +720,30 @@ class Class_SessionActivity_Article
                                                  'id' => $model->getId()]) . '">'
       . $this->_('S\'inscrire') . '</a></p>';
   }
+}
+
+
+
+
+class SessionActivity_MetaData {
+  use Trait_GetterByAttributeName;
+
+  protected
+    $_label,
+    $_value;
+
+  public function __construct($label, $value) {
+    $this->_label = $label;
+    $this->_value = $value;
+  }
+
+
+  public function getLabel() {
+    return $this->_label;
+  }
+
+
+  public function getValue() {
+    return $this->_value;
+  }
 }
\ No newline at end of file
diff --git a/library/Class/SessionActivity/Notification.php b/library/Class/SessionActivity/Notification.php
new file mode 100644
index 0000000000000000000000000000000000000000..18ee64db23d5ba4cb3abdb35f268d713047b8709
--- /dev/null
+++ b/library/Class/SessionActivity/Notification.php
@@ -0,0 +1,87 @@
+<?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
+ */
+
+
+class Class_SessionActivity_Notification {
+  use Trait_Logger, Trait_Translator, Trait_TimeSource;
+
+  protected
+    $_session_activity,
+    $_sent_count = 0,
+    $_errors = [];
+
+  public function __construct($session) {
+    $this->_session_activity = $session;
+  }
+
+
+  public function notify() {
+    foreach($this->_session_activity->getSessionActivityInscriptions() as $inscription)
+      $this->_notifyOne($inscription);
+
+    $this->getLogger()->log($this->_('%s : %s rappel(s) envoyé(s)',
+                                     $this->_session_activity->getLibelleActivity(),
+                                     $this->_sent_count));
+
+    foreach($this->_errors as $message)
+      $this->getLogger()->error($message);
+
+    return $this;
+  }
+
+
+  protected function _notifyOne($inscription) {
+    if ($inscription->hasNotifiedAt())
+      return $this;
+
+    $stagiaire = $inscription->getStagiaire();
+    $mailer = new Class_MailHtml();
+    if (!$mailer->isMailValid($stagiaire->getMail()))
+      return $this;
+
+    if (true === $result = $mailer->mail($stagiaire->getMail(),
+                                         $this->_fusion($stagiaire, 'ACTIVITY_NOTIFICATION_SUBJECT'),
+                                         $this->_fusion($stagiaire, 'ACTIVITY_NOTIFICATION_BODY'))) {
+      $this->_sent_count++;
+      $inscription->setNotifiedAt($this->getCurrentDateTime())
+                  ->saveWithoutValidation();
+    }
+
+    return $this->_error($result);
+  }
+
+
+  protected function _fusion($stagiaire, $var_key) {
+    return (new Class_ModeleFusion())
+      ->setContenu(Class_AdminVar::getValueOrDefault($var_key))
+      ->setDataSource(['session' => $this->_session_activity,
+                       'stagiaire' => $stagiaire])
+      ->getContenuFusionne();
+  }
+
+
+  protected function _error($message) {
+    if ($message && !in_array($message, $this->_errors))
+      $this->_errors[] = $message;
+
+    return $this;
+  }
+}
diff --git a/library/Class/SessionActivityInscription.php b/library/Class/SessionActivityInscription.php
index 1f3c9d6d1a1996142c05d68bf2fbc3d88175126f..a819e359361591b52400303f8f822728f99e646d 100644
--- a/library/Class/SessionActivityInscription.php
+++ b/library/Class/SessionActivityInscription.php
@@ -76,7 +76,8 @@ class Class_SessionActivityInscription extends Storm_Model_Abstract {
     $_default_attribute_values = ['presence' => false,
                                   'adults' => 0,
                                   'children' => 0,
-                                  'stagiaire_id' => null];
+                                  'stagiaire_id' => null,
+                                  'notified_at' => null];
 
 
   public function validate() {
diff --git a/library/Class/TableDescription.php b/library/Class/TableDescription.php
index a523d8e647fb5ed4889249203dc69e6b53378055..082a4e18beab906f1211a10093a96e99c2d30ee4 100644
--- a/library/Class/TableDescription.php
+++ b/library/Class/TableDescription.php
@@ -271,6 +271,11 @@ class Class_TableDescription_Columns {
   }
 
 
+  public function isEmpty() {
+    return $this->_columns->isEmpty();
+  }
+
+
   /** @return Class_TableDescription_ColumnAbstract subclass */
   public function add($column) {
     $this->_columns->append($column);
diff --git a/library/Class/TableDescription/PNBItems.php b/library/Class/TableDescription/PNBItems.php
index 33944e92a1d5c3f4adf5246d83df6f5fc450bd6e..c630484111aa1ad0f16b4171930748793baf2481 100644
--- a/library/Class/TableDescription/PNBItems.php
+++ b/library/Class/TableDescription/PNBItems.php
@@ -33,14 +33,32 @@ class Class_TableDescription_PNBItems extends Class_TableDescription {
                   })
       ->addColumn($this->_('Durée de prêt en jours'), 'duration')
       ->addColumn($this->_('Nombre de jours restant sur la licence'), 'license_expiration')
-      ->addColumn($this->_('Date de commande'), 'order_date')
-      ->addRowAction(['url' => ['module' => 'admin',
-                                'controller' => 'album',
-                                'action' => 'edit_album',
-                                'id' => '%s'],
-                      'id' => function($model) { return $model->getAlbumId(); },
-                      'anchorOptions' => ['data-popup' => 'true'],
-                      'label' => $this->_('Voir l\'album'),
-                      'icon' => 'view']);
+      ->addColumn($this->_('Date de commande'), 'order_date');
+
+    if (Class_AdminVar::get('DILICOM_PNB_BOARD_DISPLAY_SECTION'))
+      $this->addColumn($this->_('Genre'), 'genre')
+           ->addColumn($this->_('Section'), 'section');
+
+    $this->addRowAction(['url' => ['module' => 'admin',
+                                   'controller' => 'album',
+                                   'action' => 'edit_album',
+                                   'id' => '%s'],
+                         'id' => function($model) { return $model->getAlbumId(); },
+                         'anchorOptions' => ['data-popup' => 'true'],
+                         'label' => $this->_('Voir l\'album'),
+                         'icon' => 'view']);
+    return $this->_addMultiSelectionAction();
+  }
+
+
+  protected function _addMultiSelectionAction() {
+    $multi_selection = new Class_MultiSelection_AlbumItem();
+
+    foreach( (new ZendAfi_Controller_Plugin_MultiSelection_LeafActions($multi_selection))
+            ->getActions() as $action) {
+      $action['id'] = function($model) { return $model->getAlbumId(); };
+      $this->addRowAction($action);
+    }
+    return $this;
   }
 }
diff --git a/library/Class/TableDescription/PNBItemsRenderer.php b/library/Class/TableDescription/PNBItemsRenderer.php
index 69e0e25adab68a89496f0bfc605498b16a9ded7b..d66e6c085415d71e46ca2985379b6e9006186925 100644
--- a/library/Class/TableDescription/PNBItemsRenderer.php
+++ b/library/Class/TableDescription/PNBItemsRenderer.php
@@ -52,6 +52,20 @@ class Class_TableDescription_PNBItemsRenderer {
   }
 
 
+  public function getSection() {
+    if ($sections = $this->_item->getAlbumSectionIds())
+      return implode(', ',Class_CodifSection::labelsOfIds($sections)->getArrayCopy());
+    return '';
+  }
+
+
+  public function getGenre() {
+    if ($genre = $this->_item->getAlbumGenreIds())
+      return implode(', ',Class_CodifGenre::labelsOfIds($genre)->getArrayCopy());
+    return '';
+  }
+
+
   public function quantityOrInfinite($value) {
     return $this->isInfinite($value)
       ? '∞'
diff --git a/library/Class/TableDescription/SessionActivityInscriptionsExport.php b/library/Class/TableDescription/SessionActivityInscriptionsExport.php
index b22f3db822fe12e7b6bd73effa754ba99177ff4b..a21f9463407fb1995516533827dded3c78922f0a 100644
--- a/library/Class/TableDescription/SessionActivityInscriptionsExport.php
+++ b/library/Class/TableDescription/SessionActivityInscriptionsExport.php
@@ -22,19 +22,38 @@
 
 class Class_TableDescription_SessionActivityInscriptionsExport extends Class_TableDescription {
 
-  public function init() {
-    parent::init();
+  protected $_age_child_max;
 
+  public function setAgeChildMax($age_child_max) {
+    $this->_age_child_max = $age_child_max;
+    return $this;
+  }
+
+
+  public function columnsCollect($callback) {
+    if ($this->_columns->isEmpty())
+      $this->_initColumns();
+
+    return parent::columnsCollect($callback);
+  }
+
+
+  protected function _initColumns() {
     $this->addColumn($this->_('Nom'), 'nom')
          ->addColumn($this->_('Prénom'), 'prenom')
          ->addColumn($this->_('Identifiant'), 'login')
          ->addColumn($this->_('Mail'), 'mail')
          ->addColumn($this->_('Bibliothèque'), 'libelle_bib');
 
-    if (!Class_AdminVar::isActivitySessionQuotasEnabled())
-      return;
+    if (Class_AdminVar::isActivitySessionQuotasEnabled())
+      $this->addColumn($this->_('Adultes'), 'adults')
+           ->addColumn($this->_getChildrenLabel(), 'children');
+  }
+
 
-    $this->addColumn($this->_('Adultes'), 'adults')
-         ->addColumn($this->_('Enfants'), 'children');
+  protected function _getChildrenLabel() {
+    return $this->_age_child_max
+      ? $this->_('Enfants (âge max %s)', $this->_age_child_max)
+      : $this->_('Enfants');
   }
 }
diff --git a/library/Class/User/SessionActivity.php b/library/Class/User/SessionActivity.php
index 51398eed8fad30462f275b362ee0b46bdacf1c4e..7d67b0725a0a15cf10c038ee39a4919876dc9637 100644
--- a/library/Class/User/SessionActivity.php
+++ b/library/Class/User/SessionActivity.php
@@ -76,6 +76,11 @@ class Class_User_SessionActivity {
   public function getNextChildrenMax() {
     return $this->_session_activity->getNextChildrenMaxFor($this->_inscription);
   }
+
+
+  public function getAgeChildMax() {
+    return $this->_session_activity->getAgeChildMax();
+  }
 }
 
 
diff --git a/library/Class/WebService/AllServices.php b/library/Class/WebService/AllServices.php
index a64a48b9c07d55ebb9281e3aaa1140d2fa859e1f..e7d4f9934114f5c01304f6c458b6400421c75f5d 100644
--- a/library/Class/WebService/AllServices.php
+++ b/library/Class/WebService/AllServices.php
@@ -84,41 +84,45 @@ class Class_WebService_AllServices {
 
 
   public static function runServiceAfiBiographie($args) {
-    return self::runServiceAfi(static::SVC_GET_BIOGRAPHY, $args);
+    return static::runServiceAfi(static::SVC_GET_BIOGRAPHY, $args);
   }
 
 
   public static function runServiceAfiVideo($args) {
-    return self::runServiceAfi(static::SVC_GET_VIDEO, $args);
+    return static::runServiceAfi(static::SVC_GET_VIDEO, $args);
   }
 
 
   public static function runServiceAfiInterviews($args) {
-    return self::runServiceAfi(static::SVC_GET_INTERVIEW, $args);
+    return static::runServiceAfi(static::SVC_GET_INTERVIEW, $args);
   }
 
 
   public static function runServiceAfiUploadVignette($args) {
-    return self::runServiceAfi(static::SVC_UPLOAD_THUMBNAIL, $args);
+    return static::runServiceAfi(static::SVC_UPLOAD_THUMBNAIL, $args);
   }
 
 
   public static function runServiceAfiUploadBiographie($args) {
-    return self::runServiceAfi(static::SVC_SET_BIOGRAPHY, $args);
+    return static::runServiceAfi(static::SVC_SET_BIOGRAPHY, $args);
   }
 
+
   public static function runServiceAfiUploadTrailer($args) {
-    return self::runServiceAfi(static::SVC_SET_TRAILER, $args);
+    return static::runServiceAfi(static::SVC_SET_TRAILER, $args);
   }
 
 
   public static function runServiceGetUrlVignette($args) {
-    return self::runServiceAfi(static::SVC_GET_THUMBNAIL, $args);
+    if ($headers = (new Class_AdminVar_PelliculeSettings)->headers())
+      $args['headers'] = $headers;
+
+    return static::runServiceAfi(static::SVC_GET_THUMBNAIL, $args);
   }
 
 
   public static function setHttpClient($client) {
-    self::$_http_client = $client;
+    static::$_http_client = $client;
   }
 
 
@@ -132,7 +136,7 @@ class Class_WebService_AllServices {
                                                                 'image' => $url,
                                                                 'numero' => $notice->getTomeAlpha(),
                                                                 'clef_chapeau' => $notice->getClefChapeau()]));
-    if (self::RETOUR_SERVICE_OK != $result['statut_recherche'])
+    if (static::RETOUR_SERVICE_OK != $result['statut_recherche'])
       return $result['erreur'];
 
     $notice
@@ -150,7 +154,7 @@ class Class_WebService_AllServices {
                           'auteur' => $auteur,
                           'clef_oeuvre' => Class_Notice_WorkKey::legacy()->keyString($notice)]);
     $result = static::runServiceAfiUploadBiographie($args);
-    if (self::RETOUR_SERVICE_OK != $result['statut_recherche'])
+    if (static::RETOUR_SERVICE_OK != $result['statut_recherche'])
       return $result['message'];
   }
 
@@ -171,7 +175,7 @@ class Class_WebService_AllServices {
                                                      'clef_oeuvre' => Class_Notice_WorkKey::legacy()->keyString($record),
                                                      'language' => $lang,
                                                      'biography_disabled' => ($status == static::BIO_DISABLED) ? 1 : 0]);
-    if (self::RETOUR_SERVICE_OK != $result['statut_recherche'])
+    if (static::RETOUR_SERVICE_OK != $result['statut_recherche'])
       return $result['message'];
   }
 
@@ -188,31 +192,41 @@ class Class_WebService_AllServices {
     if (!isset($result['statut_recherche']))
       return $this->_('Le service n\'a pas répondu');
 
-    if (self::RETOUR_SERVICE_OK != $result['statut_recherche'])
+    if (static::RETOUR_SERVICE_OK != $result['statut_recherche'])
       return $result['message'];
   }
 
 
 
   public static function httpGet($url, $args) {
-    if (!isset(self::$_http_client))
-      self::$_http_client = new Class_WebService_SimpleWebClient();
-    return self::$_http_client->open_url($url.'?'.http_build_query($args));
+    if (!isset(static::$_http_client))
+      static::$_http_client = new Class_WebService_SimpleWebClient();
+
+    $options = isset($args['headers'])
+      ? ['headers' => $args['headers']]
+      : [];
+    unset($args['headers']);
+
+    $call_params = [$url . '?' . http_build_query($args)];
+    if ($options)
+      $call_params[] = $options;
+
+    return call_user_func_array([static::$_http_client, 'open_url'], $call_params);
   }
 
 
-  public static function runServiceAfi($service,$args)  {
+  public static function runServiceAfi($service, $args)  {
     if (!$url_service = Class_CosmoVar::get('url_services'))
       return false;
 
     if (!$args)
       $args = array();
 
-    $args['src'] = self::createSecurityKey();
-    $args['api'] = self::SVC_API;
+    $args['src'] = static::createSecurityKey();
+    $args['api'] = static::SVC_API;
     $args['action'] = $service;
 
-    $response =  json_decode(self::httpGet($url_service, $args), true);
+    $response = json_decode(static::httpGet($url_service, $args), true);
 
     if ($ig = Zend_Controller_Front::getInstance()
         ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
diff --git a/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php b/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php
index 784db9d518a97aa8b9073a91a67c454a0c8ae03c..0626c416951b6d7ba5da3b6e2354ca8123bb236a 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/SessionActivity.php
@@ -25,12 +25,24 @@ class ZendAfi_Controller_Plugin_Manager_SessionActivity
 
   public function _getPost($key = null, $default = null) {
     $post = parent::_getPost($key, $default);
-    foreach(['date_debut', 'date_fin', 'date_limite_inscription'] as $field)
-      $post[$field] = $this->_readPostDate($this->_request->getPost($field));
+    foreach(['date_debut', 'date_fin'] as $field)
+      $post[$field] = $this->_readPostActivityDate($this->_request->getPost($field));
+
+    $post['date_limite_inscription'] = $this->_readPostDate($this->_request->getPost('date_limite_inscription'));
     return $post;
   }
 
 
+  protected function _readPostActivityDate($date) {
+    $date_explode = explode(' ', $date);
+
+    if (1 === count($date_explode))
+      return Class_Date::frToIso($date);
+
+    return Class_Date::frToIso($date_explode[0]) . ' ' . $date_explode[1];
+  }
+
+
   protected function _readPostDate($date) {
     return Class_Date::frToIso($date);
   }
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php b/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
index f9bc12403253e6832a0f20673843108de9a0c5f3..9a57a23ad1e639b85f367a67064b095bd7e96f5e 100644
--- a/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
@@ -40,6 +40,30 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
   }
 
 
+  public function addModelToSelectionAjaxAction() {
+    $values = $this->_getValuesFromParams();
+    $limit = (int) Class_AdminVar::getValueOrDefault('LIMIT_MULTIPLE_SELECTION');
+
+    if($this->_getMultiSelection()->isFullWith($values, $limit)) {
+      return $this->_helper->json(['selection' => $this->renderWidget(),
+                                   'response' => $this->_('Il n\'est pas possible de sélectionner plus de %d éléments',
+                                                          $limit)]);
+    }
+
+    $this->_multi_selection = $this->_getMultiSelection()->addValues($values);
+    return $this->_helper->json(['selection' => $this->renderWidget(),
+                                 'response' => 'ok']);
+  }
+
+
+  public function removeModelFromSelectionAjaxAction() {
+    $this->_getMultiSelection()->removeValues($this->_getValuesFromParams());
+
+    return $this->_helper->json(['selection' => $this->renderWidget(),
+                                 'response' => 'ok']);
+  }
+
+
   public function removeModelFromSelectionAction() {
     $this->_getMultiSelection()->removeValues($this->_getValuesFromParams());
     return $this->_redirectToReferer();
@@ -48,7 +72,7 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
 
   public function clearModelsSelectionAction() {
     $this->_multi_selection->clear();
-    $this->_redirect($this->_view->absoluteUrl(['action' => 'index']));
+    return $this->_redirectToReferer();
   }
 
 
@@ -161,13 +185,19 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
 
   public function render() {
     if (('index' == $this->_request->getActionName())
-        || ('edit-multiple' == $this->_request->getActionName()))
-      return $this->_view->Plugin_MultiSelection_Widget($this->_multi_selection);
+        || ('edit-multiple' == $this->_request->getActionName())
+        || ('dilicom' == $this->_request->getActionName()))
+      return $this->renderWidget();
 
     return '';
   }
 
 
+  public function renderWidget() {
+    return $this->_view->Plugin_MultiSelection_Widget($this->_getMultiSelection());
+  }
+
+
   public function visitGetForm($callback) {
     $this->_get_form = $callback;
     return $this;
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php b/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php
index 77cebd917db043f7430aaa7d61ebe4013f4ea762..f39a5f6c77855a4553ab00a93d9be2170660f8ef 100644
--- a/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/LeafActions.php
@@ -26,6 +26,8 @@ class ZendAfi_Controller_Plugin_MultiSelection_LeafActions extends ZendAfi_Contr
             ['url' => ['controller' => $this->_controller_name,
                        'action' => 'remove-model-from-selection',
                        'select_id' => '%s'],
+             'anchorOptions' => ['onclick' => 'addMultiSelection(\'selected-%s\');return false;',
+                                 'class' => 'selected-%s'],
              'icon'  => 'cancel',
              'label' => $this->_remove_leaf,
              'condition' => $this->_leaf_condition],
@@ -35,6 +37,10 @@ class ZendAfi_Controller_Plugin_MultiSelection_LeafActions extends ZendAfi_Contr
                        'select_id' => '%s'],
              'icon'  => 'basket',
              'label' => $this->_add_leaf,
+
+             'anchorOptions' => ['onclick' => 'addMultiSelection(\'selected-%s\');return false;',
+                                 'class' => 'selected-%s'],
+
              'condition' => function($model)
               {
                 return !call_user_func($this->_leaf_condition, $model);
diff --git a/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php b/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php
index 6318a64c4085cd1e29e9373365574c19e4992021..318da985aa4ca385f7f2b6956c74e806f3a9ac7b 100644
--- a/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php
+++ b/library/ZendAfi/Form/Admin/AdminVar/MultiInput.php
@@ -21,7 +21,9 @@
 
 
 class ZendAfi_Form_Admin_AdminVar_MultiInput extends ZendAfi_Form_Admin_AdminVar {
-  protected $_fields = [];
+  protected
+    $_fields = [],
+    $_fixed = false;
 
 
   public function setFields($fields) {
@@ -30,11 +32,18 @@ class ZendAfi_Form_Admin_AdminVar_MultiInput extends ZendAfi_Form_Admin_AdminVar
   }
 
 
+  public function setFixed($flag) {
+    $this->_fixed = $flag;
+    return $this;
+  }
+
+
   public function addVariableEditElement() {
     $this->addElement('multiInput',
                       'valeur',
                       ['label' => $this->_('Liste de valeurs'),
-                       'fields' => $this->_fields]);
+                       'fields' => $this->_fields,
+                       'fixed' => $this->_fixed]);
   }
 
 
diff --git a/library/ZendAfi/Form/Admin/SessionActivity.php b/library/ZendAfi/Form/Admin/SessionActivity.php
index 13947999e7367eab3dd3104489f8f6bee7595a21..6195d41f33e9dfc72e8e85cf9240a7d54d23bdb1 100644
--- a/library/ZendAfi/Form/Admin/SessionActivity.php
+++ b/library/ZendAfi/Form/Admin/SessionActivity.php
@@ -21,6 +21,15 @@
 
 
 class ZendAfi_Form_Admin_SessionActivity extends ZendAfi_Form {
+
+  public static function newWith($datas = [], $custom_form = null) {
+    $form = parent::newWith($datas, $custom_form);
+    $form->_addDates($datas['all_day'], $datas['date_debut'], $datas['date_fin']);
+
+    return $form;
+  }
+
+
   public function init() {
     parent::init();
 
@@ -40,13 +49,20 @@ class ZendAfi_Form_Admin_SessionActivity extends ZendAfi_Form {
     $this
       ->setAttrib('id', 'sessionForm')
       ->setMethod('post')
-      ->addElement('datePicker', 'date_debut', ['label' => $this->_('Date début'),
-                                                'size' => 10,
-                                                'required' => true,
-                                                'allowEmpty' => false])
+      ->addElement('dateRangePicker',
+                   'activity_range',
+                   ['label' => $this->_("Date de l'activitée"),
+                    'required' => true,
+                    'start' => ['name' => 'date_debut',
+                                'allowEmpty' => false,
+                                'dateOnly' => false,
+                                'toggleAllDay' => 'all_day'],
+                    'end' => ['name' => 'date_fin',
+                              'dateOnly' => false,
+                              'toggleAllDay' => 'all_day']])
 
-      ->addElement('datePicker', 'date_fin', ['label' => $this->_('Date fin'),
-                                              'size' => 10])
+      ->addElement('checkbox', 'all_day',
+                   ['label' => $this->_("L'activitée dure toute la journée")])
 
       ->addElement('datePicker', 'date_limite_inscription',
                    ['label' => $this->_('Date limite d\'inscription'),
@@ -104,9 +120,21 @@ class ZendAfi_Form_Admin_SessionActivity extends ZendAfi_Form {
   }
 
 
+  protected function _addDates($all_day, $start, $end) {
+    $activity_range = $this->getElement('activity_range');
+    $activity_range->setStartValue($start)->setEndValue($end);
+
+    if (!$all_day)
+      return $this;
+
+    $activity_range->setDateOnly(true);
+    return $this;
+  }
+
+
   protected function _addChildrenEffectifs() {
-    $elements = ['date_debut',
-                 'date_fin',
+    $elements = ['activity_range',
+                 'all_day',
                  'date_limite_inscription',
                  'effectif_min',
                  'effectif_max',
@@ -128,6 +156,7 @@ class ZendAfi_Form_Admin_SessionActivity extends ZendAfi_Form {
       ->_addNumber('effectif_max', $this->_('Adultes maximum'))
       ->_addNumber('effectif_child_min', $this->_('Enfants minimum'))
       ->_addNumber('effectif_child_max', $this->_('Enfants maximum'))
+      ->_addNumber('age_child_max', $this->_('Âge maximum des enfants'))
       ->_addNumber('effectif_inscription_max',  $this->_('Adultes maximum par inscription'))
       ->_addNumber('effectif_inscription_child_max', $this->_('Enfants maximum par inscription'));
 
@@ -145,6 +174,7 @@ class ZendAfi_Form_Admin_SessionActivity extends ZendAfi_Form {
                                 'effectif_max',
                                 'effectif_child_min',
                                 'effectif_child_max',
+                                'age_child_max',
                                 'effectif_inscription_max',
                                 'effectif_inscription_child_max'],
                                'quotas',
diff --git a/library/ZendAfi/Form/Configuration/Domain.php b/library/ZendAfi/Form/Configuration/Domain.php
index 805b247c442f592b930a2184620cca0a50bc0431..99eaa12bdb3086885d065b99fa62bd2b23d331a3 100644
--- a/library/ZendAfi/Form/Configuration/Domain.php
+++ b/library/ZendAfi/Form/Configuration/Domain.php
@@ -160,7 +160,7 @@ class ZendAfi_Form_Configuration_Domain extends ZendAfi_Form {
       ;
 
     $thesaurus_elements = [];
-    foreach(Class_CodifThesaurus::getIndices('root') as $thesaurus) {
+    foreach(Class_CodifThesaurus::findAllForDomainCriteria() as $thesaurus) {
       $name = $thesaurus->getIdThesaurus();
       $this->addElement('listeSuggestion',
                         $name,
diff --git a/library/ZendAfi/Form/SessionActivityInscription.php b/library/ZendAfi/Form/SessionActivityInscription.php
index 8bc92bbe8fd6ebccb475f6d2fd69dcb0f6b01356..6b15526019405a9c7aa188464d9bec596ae2b54e 100644
--- a/library/ZendAfi/Form/SessionActivityInscription.php
+++ b/library/ZendAfi/Form/SessionActivityInscription.php
@@ -32,8 +32,7 @@ class ZendAfi_Form_SessionActivityInscription extends ZendAfi_Form {
                     'min' => 0,
                     'max' => 0])
       ->addElement('number', 'children',
-                   ['label' => $this->_('Nombre d\'enfants'),
-                    'value' => 0,
+                   ['value' => 0,
                     'min' => 0,
                     'max' => 0])
 
@@ -58,6 +57,17 @@ class ZendAfi_Form_SessionActivityInscription extends ZendAfi_Form {
       : $this->getElement('children')
              ->setAttrib('max', $session->getNextChildrenMax());
 
+    $this->_updateChildrenlabel($session);
+
     return parent::populate($datas);
   }
+
+
+  protected function _updateChildrenlabel($session) {
+    if (!$element_children = $this->getElement('children'))
+      return;
+
+    $element_children->setLabel($this->_('Nombre d\'enfants (âge max %s)',
+                                         $session->getAgeChildMax()));
+  }
 }
diff --git a/library/ZendAfi/View/Helper/Abonne/AvailableActivities.php b/library/ZendAfi/View/Helper/Abonne/AvailableActivities.php
index 178f49cfc6ccc3d23d6867b7a375706ffa973ac3..f4ba61ac119fc1f62ad1607e1cfa5252721dbb2b 100644
--- a/library/ZendAfi/View/Helper/Abonne/AvailableActivities.php
+++ b/library/ZendAfi/View/Helper/Abonne/AvailableActivities.php
@@ -80,9 +80,9 @@ class ZendAfi_View_Helper_Abonne_AvailableActivities extends ZendAfi_View_Helper
                                    $model->getLabel())]];
 
     if (($user = Class_Users::getIdentity()) && $user->hasRightRegisterActivity())
-      $items[] = ['url' => $this->_admin_link . '/id/%s',
+      $items[] = ['url' => $this->_admin_link . '/id/%s/search_session_activity_subscription_status/all',
                   'icon' => 'users',
-                  'label' => $this->_('Gérer les inscriptions'),
+                  'label' => $this->_('Nouvelle inscription'),
                   'anchorOptions' => ['target' => '_blank',
                                       'class' => 'menu_admin_front_anchor']];
 
diff --git a/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php b/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php
index a2e18f5390059cc37c19784b8f9e339e572f1611..78db27f53d698252edeac70d1232d70e728a092a 100644
--- a/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php
+++ b/library/ZendAfi/View/Helper/Plugin/MultiSelection/Widget.php
@@ -35,11 +35,34 @@ class ZendAfi_View_Helper_Plugin_MultiSelection_Widget extends ZendAfi_View_Help
     $_category_label;
 
   public function Plugin_MultiSelection_Widget($multi_selection) {
+    $this->_current_skin = Class_Admin_Skin::current();
+
+    $icon_add = $this->_current_skin->renderActionIconOn('basket',
+                                                         $this->view,
+                                                         ['alt' => $this->_('Ajouter à la sélection'),
+                                                          'title' => $this->_('Ajouter'),
+                                                          'class' => 'ico']);
+    $icon_remove = $this->_current_skin->renderActionIconOn('cancel',
+                                                            $this->view,
+                                                            ['alt' => $this->_('Supprimer de la sélection'),
+                                                             'title' => $this->_('Supprimer'),
+                                                             'class' => 'ico']);
+
+
+    Class_ScriptLoader::getInstance()
+      ->addInlineScript("
+      var icon_basket = '".$icon_add."';
+      var icon_delete = '".$icon_remove."';")
+
+      ->addAdminScript('multi_selection/multi_selection.js');
+
     if(!$multi_selection)
       return '';
 
     if(0 == count($multi_selection->getModels()))
-      return '';
+      return $this->_tag('div',
+                         $this->_tag('div', '',
+                                     ['class' => 'selected_items_widget show']));
 
     $multi_selection->acceptWidgetVisitor($this);
     return $this->_render();
diff --git a/library/ZendAfi/View/Helper/RenderModelAction.php b/library/ZendAfi/View/Helper/RenderModelAction.php
index 8b35b1094267d11a48dfc2205416af64eef16d9f..1e4b08798ba992f3c168699a002673178c68ecb8 100644
--- a/library/ZendAfi/View/Helper/RenderModelAction.php
+++ b/library/ZendAfi/View/Helper/RenderModelAction.php
@@ -62,7 +62,7 @@ class ZendAfi_View_Helper_RenderModelAction extends ZendAfi_View_Helper_BaseHelp
     $url = $this->_initUrl($model);
     $icon = $this->_initIcon($model);
 
-    $anchorOptions = $this->_initAnchorOptions($model->getId(), $url);
+    $anchorOptions = $this->_initAnchorOptions($this->_getModelId($model), $url);
 
     $title = $this->_initTitle($model);
 
@@ -131,6 +131,7 @@ class ZendAfi_View_Helper_RenderModelAction extends ZendAfi_View_Helper_BaseHelp
         && (false === strpos($url, $_SERVER['REQUEST_URI'])))
       return $attribs;
 
+
     isset($attribs['class'])
       ? ($attribs['class'] .= ' selected')
       : ($attribs['class'] = 'selected');
diff --git a/library/ZendAfi/View/Helper/TagSessionActivityInscription.php b/library/ZendAfi/View/Helper/TagSessionActivityInscription.php
index c77091dc6d084e09e46da79d71e72df908675044..91948df558eaa46db6cea07c3da7fd79b75aee22 100644
--- a/library/ZendAfi/View/Helper/TagSessionActivityInscription.php
+++ b/library/ZendAfi/View/Helper/TagSessionActivityInscription.php
@@ -85,10 +85,11 @@ class ZendAfi_View_Helper_TagSessionActivityInscription extends ZendAfi_View_Hel
     return  ' ' . $this->_tagAnchor($this->_url(['module' => 'admin',
                                                  'controller' => 'session-activity',
                                                  'action' => 'inscriptions',
-                                                 'id' => $this->_session_activity->getId()
+                                                 'id' => $this->_session_activity->getId(),
+                                                 'search_session_activity_subscription_status' => 'all'
                                                  ],
                                                 null, true),
-                                    $this->_('Gérer les inscriptions'),
+                                    $this->_('Nouvelle inscription'),
                                     ['target' => '_blank',
                                      'class' => 'menu_admin_front_anchor']);
   }
diff --git a/library/templates/Intonation/Library/Link/AdminSessionActivityRegistrations.php b/library/templates/Intonation/Library/Link/AdminSessionActivityRegistrations.php
index 32526cd97cde6437c2ebc79bf21b61066673a1a1..9465eaf81da8283f56bf1addcbd86af2a0712beb 100644
--- a/library/templates/Intonation/Library/Link/AdminSessionActivityRegistrations.php
+++ b/library/templates/Intonation/Library/Link/AdminSessionActivityRegistrations.php
@@ -34,10 +34,11 @@ class Intonation_Library_Link_AdminSessionActivityRegistrations extends Intonati
       ->setUrl($view->url(['module' => 'admin',
                            'controller' => 'session-activity',
                            'action' => 'inscriptions',
-                           'id' => $session->getId()],
+                           'id' => $session->getId(),
+                           'search_session_activity_subscription_status' => 'all'],
                           null, true))
-      ->setText($t->_('Gérer les inscriptions'))
-      ->setTitle($t->_('Gérer les inscriptions de l\'activité %s',
+      ->setText($t->_('Nouvelle inscription'))
+      ->setTitle($t->_('Nouvelle inscription à l\'activité %s',
                        $session->getLibelleActivity()))
       ->setImage(Class_Template::current()->getIco($view, 'team', 'library'))
       ->setAttribs(['target' => '_blank'])
diff --git a/public/admin/js/multi_selection/multi_selection.js b/public/admin/js/multi_selection/multi_selection.js
new file mode 100644
index 0000000000000000000000000000000000000000..586046a6b56d63322fbc255042fbe6caad163d69
--- /dev/null
+++ b/public/admin/js/multi_selection/multi_selection.js
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2012, 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 
+ */
+
+
+function addMultiSelection(id) {
+  var url = $('a.'+id+':first').attr('href');
+  $.getJSON(url.replace('-selection','-selection-ajax',url), function(data)  {
+    $('.selected_items_widget:first').html(data['selection']);
+    if (data['response'] != 'ok' ) {
+      $('.selected_items_widget:first').html(data['selection']+"<div class=\"popup_notification\">"+data['response']+"</div>");
+      $('.popup_notification:first').dialog();
+      return ;
+    }
+    var is_basket = url.indexOf('add-model')>-1;
+    var icon = (is_basket ? icon_delete : icon_basket);
+
+    var url_href = (is_basket
+                    ? url.replace(/add-model-to/g,'remove-model-from',url)
+                    : url.replace(/remove-model-from/g,'add-model-to',url));
+
+    $('a.'+id).each (function() {
+      $(this).attr('href', url_href);
+      $(this).html(icon);
+    });
+
+  });
+  return false;
+}
diff --git a/public/admin/js/multi_selection/test.js b/public/admin/js/multi_selection/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..4ccafbea056667d84a6a24b93e10eecfd1981b8c
--- /dev/null
+++ b/public/admin/js/multi_selection/test.js
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2012, 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 
+ */
+
+var ajax_calls = [];
+
+$.getJSON = function(url, callback, c) {
+  ajax_calls.push(url);
+  return callback({'selection':'my selection', 'response':'ok'});
+};
+
+
+test('call url with ajax', function() {
+  $('#first').click();
+  equal(ajax_calls[0], '/admin/cms/add-model-to-selection-ajax/page/1/id_cat/7', 'URL has been called ' + ajax_calls[0]);
+});
+
+
+test('change basket to remove all links and images', function() {
+  var first= $("#first");
+  $('#first').click();
+  $('#first').click();
+  var second= $("#first");
+  equal(first.attr("href"), '/admin/cms/remove-model-from-selection/page/1/id_cat/7' );
+  equal(second.attr("href"), '/admin/cms/remove-model-from-selection/page/1/id_cat/7' );
+  var img= $("#first> img");
+  equal(img.attr("src"), "/public/admin/skins/bokeh74/icons/actions/cancel_24.png");
+
+  var img_two= $("#second> img");
+  equal(img_two.attr("src"), "/public/admin/skins/bokeh74/icons/actions/cancel_24.png");
+
+});
+
+
+
+test('scenario with 3 clicks', function() {
+  $('#first').click();
+  var myanchor= $("#first");
+  equal(myanchor.attr("href"), '/admin/cms/add-model-to-selection/page/1/id_cat/7' );
+  $('#first').click();
+  equal(myanchor.attr("href"), '/admin/cms/remove-model-from-selection/page/1/id_cat/7' );
+  $('#first').click();
+  equal(myanchor.attr("href"), '/admin/cms/add-model-to-selection/page/1/id_cat/7' );
+});
+
+
diff --git a/public/admin/js/multi_selection/tests.html b/public/admin/js/multi_selection/tests.html
new file mode 100644
index 0000000000000000000000000000000000000000..0d43492e1c14c53f5ac75238409f353a68adec27
--- /dev/null
+++ b/public/admin/js/multi_selection/tests.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!--
+/**
+ * Copyright (c) 2012, 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 
+ */
+-->
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>QUnit tests</title>
+  <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-git.css">
+</head>
+<body>
+  <div id="qunit"></div>
+  <div id="qunit-fixture"></div>
+  <script type="text/javascript" src="../jquery-3.2.1.min.js"></script>
+  
+  <script src="multi_selection.js"></script>
+  <script src="http://code.jquery.com/qunit/qunit-1.13.0.js"></script>
+  <script src="test.js"></script>
+  <script> var icon_basket = '<img src="/public/admin/skins/bokeh74/icons/actions/panier_24.png" alt="Ajouter &agrave; la s&eacute;lection" title="Ajouter" class="ico">';
+      var icon_delete = '<img src="/public/admin/skins/bokeh74/icons/actions/cancel_24.png" alt="Supprimer de la s&eacute;lection" title="Supprimer" class="ico">';</script>
+  <a class="select-111" id="first" href="/admin/cms/add-model-to-selection/page/1/id_cat/7" onclick="addMultiSelection('select-111');return false;"><img src="" alt="Ajouter"> </a>
+  <a class="select-111" id="second" href="/admin/cms/add-model-to-selection/page/1/id_cat/7" onclick="addMultiSelection('select-111');return false;"><img src="" alt="Ajouter"> </a>
+</body>
+</html>
diff --git a/scripts/reindex_local_items_facets.php b/scripts/reindex_local_items_facets.php
new file mode 100644
index 0000000000000000000000000000000000000000..e1f660ec9c263aa5cb183712495e21ee2749dc17
--- /dev/null
+++ b/scripts/reindex_local_items_facets.php
@@ -0,0 +1,59 @@
+<?php
+error_reporting(E_ALL^E_DEPRECATED);
+require __DIR__ . '/../console.php';
+
+echo "\n\nWelcome to the iReindex Items Facets 2000 X tool by @patbator, proudly RTed by @lla \n\n";
+
+echo "\n\nWill update items facets of " . Class_Notice::count() . " records in database\n\n";
+
+class Script_ItemsFacets_Updater {
+  use Trait_MemoryCleaner;
+
+  protected
+    $_adapter,
+    $_last_id = 0,
+    $_page = 1;
+
+  public function __construct() {
+    $this->_adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+  }
+
+
+  public function run() {
+    while ($records = Class_Notice::findAllBy(['where' => 'id_notice > ' . (int)$this->_last_id,
+                                               'limit' => '1000']))
+      $this->_runPage($records);
+
+    return $this;
+  }
+
+
+  protected function _runPage($records) {
+    echo "\npage: ". $this->_page ."\n";
+    $this->_page++;
+
+    foreach($records as $record)
+      $this->_runOne($record);
+
+    $this->_last_id = $record->getId();
+    $this->_cleanMemory();
+    return $this;
+  }
+
+
+  protected function _runOne($record) {
+    $old = $record->getFacettes();
+    $record->updateFacetsFromExemplaires();
+    if ($old != $record->getFacettes())
+      $record->save();
+
+    echo '.';
+
+    return $this;
+  }
+}
+
+$updater = new Script_ItemsFacets_Updater;
+$updater->run();
+
+echo "\n\nDONE !!!!\n\n";
diff --git a/tests/application/modules/admin/controllers/ActivityControllerTest.php b/tests/application/modules/admin/controllers/ActivityControllerTest.php
index 8d36bbf1b1cff3e1068dcaa98a086ca94b93032d..f6ba0cde41ad0f459cffffb33900804556f4370b 100644
--- a/tests/application/modules/admin/controllers/ActivityControllerTest.php
+++ b/tests/application/modules/admin/controllers/ActivityControllerTest.php
@@ -787,7 +787,9 @@ class Admin_ActivityControllerEditSessionLearningJavaFevrierTest extends  Admin_
 
 
 
-class Admin_ActivityControllerEditSessionLearningJavaMars27Test extends  Admin_ActivityControllerTestCase  {
+class Admin_ActivityControllerEditSessionLearningJavaMars27Test
+  extends Admin_ActivityControllerTestCase {
+
   public function setUp() {
     parent::setUp();
     $this->dispatch('/admin/session-activity/edit/id/32', true);
@@ -808,11 +810,17 @@ class Admin_ActivityControllerEditSessionLearningJavaMars27Test extends  Admin_A
 
 
   /** @test */
-  function inputDateFinShouldContains28_03_2012() {
+  function inputDateFinShouldContains29_03_2012() {
     $this->assertXPath('//form[@id="sessionForm"]//input[@name="date_fin"][@value="29/03/2012"]');
   }
 
 
+  /** @test */
+  function inputCheckboxAlldayShouldBePresent() {
+    $this->assertXPath('//form[@id="sessionForm"]//input[@name="all_day"][@value="1"]');
+  }
+
+
   /** @test */
   function inputDateLimiteInscriptionShouldContains05_03_2012() {
     $this->assertXPath('//form[@id="sessionForm"]//input[@name="date_limite_inscription"][@value="05/03/2012"]');
@@ -860,7 +868,9 @@ class Admin_ActivityControllerEditSessionLearningJavaMars27Test extends  Admin_A
 
 
 
-class Admin_ActivityControllerDeleteSessionLearningJavaMars27Test extends  Admin_ActivityControllerTestCase  {
+class Admin_ActivityControllerDeleteSessionLearningJavaMars27Test
+  extends Admin_ActivityControllerTestCase {
+
   public function setUp() {
     parent::setUp();
     $this->dispatch('/admin/session-activity/delete/year/2012/id/32', true);
@@ -884,7 +894,9 @@ class Admin_ActivityControllerDeleteSessionLearningJavaMars27Test extends  Admin
 
 
 
-class Admin_ActivityControllerDeleteActivityLearningJavaTest extends  Admin_ActivityControllerTestCase  {
+class Admin_ActivityControllerDeleteActivityLearningJavaTest
+  extends Admin_ActivityControllerTestCase  {
+
   public function setUp() {
     parent::setUp();
     $this->dispatch('/admin/activity/delete/id/3/year/2012');
@@ -1141,7 +1153,9 @@ class Admin_ActivityControllerPostSessionLearnJavaTest extends Admin_ActivityCon
 
 
 
-class Admin_ActivityControllerPostSessionLearnJavaWithInvalidDataTest extends  Admin_ActivityControllerTestCase  {
+class Admin_ActivityControllerPostSessionLearnJavaWithInvalidDataTest
+  extends Admin_ActivityControllerTestCase {
+
   public function setUp() {
     parent::setUp();
     $this->postDispatch('/admin/session-activity/edit/id/32',
@@ -1174,12 +1188,68 @@ class Admin_ActivityControllerPostSessionLearnJavaWithInvalidDataTest extends  A
   public function errorsShouldContainsDateLimiteInscriptionAfterDateDebut() {
     $this->assertXPathContentContains('//ul[@class="errors"]//li', "La date de début est requise");
   }
+
+
+  /** @test */
+  public function pageShouldNotContainsInscriptionMaxAdultesRequired() {
+    $this->assertNotXPathContentContains('//ul[@class="errors"]//li',
+                                         "Adultes maximum par inscription doit être renseigné");
+  }
+
+
+  /** @test */
+  public function pageShouldNotContainsInscriptionMaxEnfantsRequired() {
+    $this->assertNotXPathContentContains('//ul[@class="errors"]//li',
+                                         "Enfants maximum par inscription doit être renseigné");
+  }
+}
+
+
+
+
+class Admin_ActivityControllerPostSessionLearnJavaWithAllDayFalseTest
+  extends Admin_ActivityControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)
+      ->setDateDebut('2012-03-27 08:00')
+      ->setDateFin('2012-03-27 18:00')
+      ->setAllDay(0);
+    $this->dispatch('/admin/session-activity/edit/id/32');
+  }
+
+
+  /** @test */
+  function inputDateDebutShouldContains27_03_2012_0800() {
+    $this->assertXPath('//form[@id="sessionForm"]//input[@name="date_debut"][@value="27/03/2012 08:00"]');
+  }
+
+
+  /** @test */
+  function inputDateFinShouldContains27_03_2012_1800() {
+    $this->assertXPath('//form[@id="sessionForm"]//input[@name="date_fin"][@value="27/03/2012 18:00"]');
+  }
+
+
+  /** @test */
+  function inputCheckboxAlldayShouldBePresent() {
+    $this->assertXPath('//form[@id="sessionForm"]//input[@name="all_day"][@value="0"]');
+  }
+
+
+  /** @test */
+  public function javascriptRangePickerShouldBePresent() {
+    $this->assertXPathContentContains('//script', '$("#all_day").click(function()');
+  }
 }
 
 
 
 
-class Admin_ActivityControllerPostSessionLearnJavaWithInvalidDateFinTest extends  Admin_ActivityControllerTestCase  {
+class Admin_ActivityControllerPostSessionLearnJavaWithInvalidDateFinTest
+  extends Admin_ActivityControllerTestCase {
+
   public function setUp() {
     parent::setUp();
     $this->postDispatch('/admin/session-activity/edit/id/32',
@@ -1304,7 +1374,8 @@ class Admin_ActivityControllerPostAddSessionToActivityLearningPythonTest
 
   /** @test */
   function getActivityShouldReturnActivityLearnPython() {
-    $this->assertEquals($this->_learn_python->getLibelle(), $this->session->getActivity()->getLibelle());
+    $this->assertEquals($this->_learn_python->getLibelle(),
+                        $this->session->getActivity()->getLibelle());
   }
 
 
@@ -1384,7 +1455,7 @@ class Admin_ActivityControllerPostAddSessionToActivityLearningPythonTest
 
 
   public function linkedArticleContentChecks() {
-    return [['<p>Discover Python language</p>'],
+    return [['On charme les serpents'],
             ['<dd class="registration_limitdate">10 février 2010</dd>'],
             ['<dd class="session_hours">9h - 18h</dd>'],
             ['<dd class="session_length">3 h</dd>'],
@@ -1406,7 +1477,9 @@ class Admin_ActivityControllerPostAddSessionToActivityLearningPythonTest
 
 
 
-class Admin_ActivityControllerPostAddSessionToActivityLearningPythonWithWrongIntervenantsTest extends  Admin_ActivityControllerTestCase  {
+class Admin_ActivityControllerPostAddSessionToActivityLearningPythonWithWrongIntervenantsTest
+  extends  Admin_ActivityControllerTestCase {
+
   /** @test */
   public function sessionSaveShoulNotHaveBeenCalled() {
     $this->postDispatch('/admin/session-activity/add/activity_id/12',
@@ -1953,26 +2026,47 @@ class ActivityControllerRefusTestCase extends ActivityControllerImpressionsTestC
 
 
 
-class Admin_ActivityControllerLinkedArticleWithoutArticleActionTest
+abstract class Admin_ActivityControllerLinkedArticleTestCase
   extends Admin_ActivityControllerTestCase {
 
+  protected $_linked_article;
+
   public function setUp() {
     parent::setUp();
 
     // fixtures generate article on save,
     // we want to ensure real life case with no existing linked article
-    Class_SessionActivity::find(32)->getArticle()->delete();
+    $this->_updateModels();
+    $this->_session_java_mars->getArticle()->delete();
     Class_SessionActivity::clearCache();
     Class_Article::clearCache();
 
-    $this->dispatch('/admin/session-activity/linked-article/id/32', true);
-    $this->article = Class_SessionActivity::find(32)->getArticle();
+    $this->dispatch('/admin/session-activity/linked-article/id/32');
+    $this->_linked_article = Class_SessionActivity::find(32)->getArticle();
+  }
+
+
+  protected function _updateModels() {
+  }
+}
+
+
+
+
+class Admin_ActivityControllerLinkedArticleWithoutArticleActionTest
+  extends Admin_ActivityControllerLinkedArticleTestCase {
+
+  protected function _updateModels() {
+    $this->_session_java_mars
+      ->setContenu('')
+      ->save();
   }
 
 
   /** @test */
   public function linkToEditShouldBePresent() {
-    $this->assertXpath('//a[contains(@href, "/admin/cms/edit/id/' . $this->article->getId() . '")][@data-popup="true"]');
+    $this->assertXpath('//a[contains(@href, "/admin/cms/edit/id/'
+                       . $this->_linked_article->getId() . '")][@data-popup="true"]');
   }
 
 
@@ -1980,6 +2074,70 @@ class Admin_ActivityControllerLinkedArticleWithoutArticleActionTest
   public function linkToRegenerateShouldBePresent() {
     $this->assertXpath('//a[contains(@href, "/admin/session-activity/linked-article-regenerate/id/32")]');
   }
+
+
+  /** @test */
+  public function linkedArticleShouldHaveAlldayChecked() {
+    $this->assertEquals(1, $this->_linked_article->getAllDay());
+  }
+
+
+  /** @test */
+  public function linkedArticleEventsDebutShouldBe2012_03_27() {
+    $this->assertEquals('2012-03-27', $this->_linked_article->getEventsDebut());
+  }
+
+
+  /** @test */
+  public function linkedArticleEventsFinShouldBe2012_03_29() {
+    $this->assertEquals('2012-03-29', $this->_linked_article->getEventsFin());
+  }
+
+
+  /** @test */
+  public function linkedArticleContenuShouldContainsActivityDescription() {
+    $this->assertContains('Here you will learn some old and boring stuff',
+                          $this->_linked_article->getContenu());
+  }
+}
+
+
+
+
+class Admin_ActivityControllerLinkedArticleWithoutArticleAndAllDayActionTest
+  extends Admin_ActivityControllerLinkedArticleTestCase {
+
+  protected function _updateModels() {
+    $this->_session_java_mars
+      ->setAllDay(0)
+      ->setDateDebut('2012-03-27 08:00')
+      ->setDateFin('2012-03-29 18:00')
+      ->save();
+  }
+
+
+  /** @test */
+  public function linkedArticleShouldHaveAlldayUnchecked() {
+    $this->assertEquals(0, $this->_linked_article->getAllDay());
+  }
+
+
+  /** @test */
+  public function linkedArticleEventsDebutShouldBe2012_03_27_0800() {
+    $this->assertEquals('2012-03-27 08:00', $this->_linked_article->getEventsDebut());
+  }
+
+
+  /** @test */
+  public function linkedArticleEventsFinShouldBe2012_03_29_1800() {
+    $this->assertEquals('2012-03-29 18:00', $this->_linked_article->getEventsFin());
+  }
+
+
+  /** @test */
+  public function linkedArticleContenuShouldContainsSessionActivityContenu() {
+    $this->assertContains('Intro à la syntaxe', $this->_linked_article->getContenu());
+  }
 }
 
 
diff --git a/tests/application/modules/admin/controllers/CatalogueControllerTest.php b/tests/application/modules/admin/controllers/CatalogueControllerTest.php
index 4275650e7e8098756e4ede6a8a045895836e7328..11f6cbf10560ed1f9ba7dd8d02fcf12d947f8785 100644
--- a/tests/application/modules/admin/controllers/CatalogueControllerTest.php
+++ b/tests/application/modules/admin/controllers/CatalogueControllerTest.php
@@ -698,12 +698,12 @@ class CatalogueControllerEditCatalogueTest extends AdminCatalogueControllerTestC
 
     $this->onLoaderOfModel('Class_CodifThesaurus')
          ->whenCalled('findAllBy')
-         ->with(['where' => 'code not in ("Nouveauté par bibliothèque", "Nouveauté par annexe", "Catalogue") and LENGTH(id_thesaurus) in (1,4)',
+         ->with(['where' => 'id_thesaurus not in ("NRNR", "NNNN", "NANA", "CCCC") and LENGTH(id_thesaurus) in (1,4)',
                  'order' => 'id_thesaurus'])
-         ->answers([Class_CodifThesaurus::find(1),
-                    Class_CodifThesaurus::find(2)]);
+         ->answers([Class_CodifThesaurus::find(1), Class_CodifThesaurus::find(2)])
+      ;
 
-    $this->dispatch('/admin/catalogue/edit/id_catalogue/6', true);
+    $this->dispatch('/admin/catalogue/edit/id_catalogue/6');
   }
 
 
diff --git a/tests/application/modules/admin/controllers/SessionActivityControllerTest.php b/tests/application/modules/admin/controllers/SessionActivityControllerTest.php
index 99e371d2ffa82f595c96be04b07ccbecd49b2bd9..4da7ec70e6aa01a8bf55f114cebb4534a2cb5270 100644
--- a/tests/application/modules/admin/controllers/SessionActivityControllerTest.php
+++ b/tests/application/modules/admin/controllers/SessionActivityControllerTest.php
@@ -107,6 +107,7 @@ abstract class Admin_SessionActivityControllerTestCase extends Admin_AbstractCon
                     'effectif_max' => 25,
                     'effectif_child_min' => 1,
                     'effectif_child_max' => 5,
+                    'age_child_max' => 5,
                     'effectif_inscription_max' => 2,
                     'effectif_inscription_child_max' => 3,
                     'duree'=> 8,
@@ -221,193 +222,11 @@ class Admin_SessionActivityControllerInscriptionsSessionMarsJavaTest
 
 
 
-class Admin_SessionActivityControllerInscriptionsSessionMarsJavaWithQuotasTest
-  extends Admin_SessionActivityControllerInscriptionsSessionMarsJavaTestCase {
-
-  protected function _fixtureQuotas() {
-    parent::_fixtureQuotas();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', '1');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsCounters() {
-    $this->assertXPathContentContains('//a[contains(@href, "/session-activity/inscriptions/id/32")]',
-                                      '3 / 6-30',
-                                      $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function pageShouldContainsParticipantsColumn() {
-    $this->assertXPathContentContains('//th', 'Participants');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsAttendees() {
-    $this->assertXPathContentContains('//td', '1 adulte, 2 enfants');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsLinkToEditAttendees() {
-    $this->assertXPath('//a[contains(@href, "/admin/session-activity-inscription/edit/id/165")]');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsLinkToSubscribePistache() {
-    $this->assertXPath('//a[contains(@href, "/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10")][not(@onclick)][@data-popup]');
-  }
-}
-
-
-
-
-class Admin_SessionActivityControllerInscriptionsSessionMarsJavaWithQuotasAndLegacyRegistrationTest
-  extends Admin_SessionActivityControllerInscriptionsSessionMarsJavaTestCase {
-
-  protected function _fixtureQuotas() {
-    parent::_fixtureQuotas();
-
-    $legacy_registration = $this->fixture('Class_SessionActivityInscription',
-                                          ['id' => 38839,
-                                           'session_activity_id' => 32,
-                                           'stagiaire_id' => 999838]);
-    Class_SessionActivity::find(32)
-      ->addSessionActivityInscription($legacy_registration);
-
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', '1');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsCountersWithLegacyRegistration() {
-    $this->assertXPathContentContains('//a[contains(@href, "/session-activity/inscriptions/id/32")]',
-                                      '4 / 6-30');
-  }
-}
-
-
-
-
 class Admin_SessionActivityControllerEditQuotasTest extends Admin_SessionActivityControllerTestCase {
 
-  public function quotasElements() {
-    return [['effectif_min'],
-            ['effectif_child_min'],
-            ['effectif_max'],
-            ['effectif_child_max'],
-            ['effectif_inscription_max'],
-            ['effectif_inscription_child_max']];
-  }
-
-
-  /**
-   * @test
-   * @dataProvider quotasElements
-   */
-  public function withQuotasFormShouldContainsElement($element) {
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', '1');
-    $this->dispatch('/admin/session-activity/edit/id/32');
-    $this->assertXPath('//fieldset[@id="fieldset-quotas"]//input[@name="' . $element . '"][@required="required"][@type="number"][@min="0"]');
-  }
-
-
   /** @test */
   public function withoutQuotasFormShouldNotContainChildrenElement() {
     $this->dispatch('/admin/session-activity/edit/id/32');
     $this->assertNotXPath('//input[@name="effectif_child_min"][@required="required"][@type="number"]');
   }
 }
-
-
-
-
-class Admin_SessionActivityControllerInvalidPostQuotasTest
-  extends Admin_SessionActivityControllerTestCase {
-
-  protected $_post_values = ['effectif_child_min' => 'chaine',
-                             'effectif_child_max' => 'chaine',
-                             'effectif_min' => 'chaine',
-                             'effectif_max' => 'chaine',
-                             'effectif_inscription_max' => 'chaine',
-                             'effectif_inscription_child_max' => 'chaine'];
-
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', '1');
-  }
-
-
-  public function quotasElements() {
-    return array_map(function($name) { return [$name]; },
-                     array_keys($this->_post_values));
-  }
-
-
-  /**
-   * @test
-   * @dataProvider quotasElements
-   */
-  public function pageShouldContainErrorNotIntegerFor($element) {
-    $this->postDispatch('admin/session-activity/edit/id/32', $this->_post_values);
-    $this->assertXPathContentContains('//ul[preceding-sibling::input[@name="' . $element . '"]][@class="errors"]',
-                                      "'chaine' n'est pas un nombre entier");
-  }
-
-
-  /** @test */
-  public function minGreaterThanMaxShouldDisplayError() {
-    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_child_min' => 30,
-                                                              'effectif_child_max' => 10]);
-    $this->assertXPathContentContains('//ul[@class="errors"]',
-                                      'L\'effectif enfants maximum doit être supérieur ou égal à l\'effectif enfants minimum');
-  }
-
-
-  /** @test */
-  public function inscriptionMaxGreaterThanMaxShouldDisplayError() {
-    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_inscription_max' => 30,
-                                                              'effectif_max' => 10]);
-    $this->assertXPathContentContains('//ul[@class="errors"]',
-                                      'L\'effectif maximum doit être supérieur ou égal à l\'effectif maximum par inscription');
-  }
-
-
-  /** @test */
-  public function inscriptionChildMaxGreaterThanMaxShouldDisplayError() {
-    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_inscription_child_max' => 10,
-                                                              'effectif_child_max' => 5]);
-    $this->assertXPathContentContains('//ul[@class="errors"]',
-                                      'L\'effectif enfants maximum doit être supérieur ou égal à l\'effectif enfants maximum par inscription');
-  }
-}
-
-
-
-
-class Admin_SessionActivityControllerExportInscriptionsWithQuotasTest
-  extends Admin_SessionActivityControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->dispatch('/admin/session-activity/exportinscriptions/id/32');
-  }
-
-
-  /** @test */
-  public function exportShouldContainsEffectif6_30() {
-    $this->assertContains('Effectif;6-30', $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function exportShouldContainsAdultsAndChildrenNumber() {
-    $this->assertContains('Barroca;Patrick;Pat;user@server.org;;1;2', $this->_response->getBody());
-  }
-}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/SessionActivityInscriptionControllerTest.php b/tests/application/modules/admin/controllers/SessionActivityInscriptionControllerTest.php
index a1bca6a6de62e12b193accb9d6332ad1a6a39fa1..7180c847cd8d4736517f36d9492386975e2a27a3 100644
--- a/tests/application/modules/admin/controllers/SessionActivityInscriptionControllerTest.php
+++ b/tests/application/modules/admin/controllers/SessionActivityInscriptionControllerTest.php
@@ -101,6 +101,7 @@ abstract class Admin_SessionActivityInscriptionControllerTestCase
                     'effectif_max' => 25,
                     'effectif_child_min' => 1,
                     'effectif_child_max' => 5,
+                    'age_child_max' => 5,
                     'effectif_inscription_max' => 2,
                     'effectif_inscription_child_max' => 2,
                     'duree'=> 8,
@@ -294,282 +295,4 @@ class Admin_SessionActivityInscriptionControllerUnsubscribePatSessionMarsJavaTes
   public function removableShouldBeNotified() {
     $this->assertFlashMessengerContentContains('L\'utilisateur a bien été désinscrit');
   }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerSubscribeWithQuotasTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->dispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsSessionNavigation() {
-    $this->assertXPathContentContains('//div[contains(@class, "activities")]',
-                                      'Learn Java');
-  }
-
-
-  /** @test */
-  public function inputAdultsShouldBePresent() {
-    $this->assertXPath('//input[@name="adults"]');
-  }
-
-
-  /** @test */
-  public function inputChildrenShouldBePresent() {
-    $this->assertXPath('//input[@name="children"]');
-  }
-
-
-  /** @test */
-  public function backButtonUrlShouldContainsSessionActivity32() {
-    $this->assertXPath('//button[contains(@data-url, "/admin/session-activity/inscriptions/id/32")]');
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerSubscribeWithQuotasPopupTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->dispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10/render/popup');
-  }
-
-
-  /** @test */
-  public function popupShouldNotContainsSessionNavigation() {
-    $this->assertNotXPathContentContains('//div[contains(@class, "activities")]',
-                                         'Learn Java');
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerPostWithQuotasTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->postDispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10',
-                        ['adults' => 1,
-                         'children' => 2]);
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivityInscriptions() {
-    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
-  }
-
-
-  /** @test */
-  public function pistacheShouldBeSubscribed() {
-    $this
-      ->assertNotNull(Class_SessionActivityInscription::findFirstBy(['stagiaire_id' => 10,
-                                                                     'session_activity_id' => 32]));
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerSubscribePostWithQuotasMaxOverloadTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->postDispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10',
-                        ['adults' => 3,
-                         'children' => 3]);
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivityInscriptions() {
-    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
-  }
-
-
-  /** @test */
-  public function pistacheShouldBeSubscribed() {
-    $this
-      ->assertNotNull(Class_SessionActivityInscription::findFirstBy(['stagiaire_id' => 10,
-                                                                     'session_activity_id' => 32,
-                                                                     'adults' => 3,
-                                                                     'children' => 3]));
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerEditWithQuotasTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->dispatch('/admin/session-activity-inscription/edit/id/165');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsSessionNavigation() {
-    $this->assertXPathContentContains('//div[contains(@class, "activities")]',
-                                      'Learn Java');
-  }
-
-
-  /** @test */
-  public function inputAdultsShouldBePresent() {
-    $this->assertXPath('//input[@name="adults"][@value="1"]');
-  }
-
-
-  /** @test */
-  public function inputChildrenShouldBePresent() {
-    $this->assertXPath('//input[@name="children"][@value="2"]');
-  }
-
-
-  /** @test */
-  public function backButtonUrlShouldContainsSessionActivity32() {
-    $this->assertXPath('//button[contains(@data-url, "/admin/session-activity/inscriptions/id/32")]');
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerEditPostWithQuotasTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->postDispatch('/admin/session-activity-inscription/edit/id/165',
-                        ['adults' => 2,
-                         'children' => 2]);
-
-    Class_SessionActivityInscription::clearCache();
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivityInscriptions() {
-    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
-  }
-
-
-  /** @test */
-  public function patRegistrationAdultsShouldBecome2() {
-    $this->assertEquals(2, Class_SessionActivityInscription::find(165)->getAdults());
-  }
-
-
-  /** @test */
-  public function patRegistrationChildrenShouldBecome2() {
-    $this->assertEquals(2, Class_SessionActivityInscription::find(165)->getChildren());
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerEditPostWithQuotasOverloadTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->postDispatch('/admin/session-activity-inscription/edit/id/165',
-                        ['adults' => 3,
-                         'children' => 3]);
-
-    Class_SessionActivityInscription::clearCache();
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivityInscriptions() {
-    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
-  }
-
-
-  /** @test */
-  public function patRegistrationAdultsShouldBecome3() {
-    $this->assertEquals(3, Class_SessionActivityInscription::find(165)->getAdults());
-  }
-
-
-  /** @test */
-  public function patRegistrationChildrenShouldBecome3() {
-    $this->assertEquals(3, Class_SessionActivityInscription::find(165)->getChildren());
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerEditWithQuotasFullChildrenTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_SessionActivity::find(32)->setEffectifChildMax(2);
-    $this->dispatch('/admin/session-activity-inscription/edit/id/165');
-  }
-
-
-  /** @test */
-  public function inputAdultsShouldBePresent() {
-    $this->assertXPath('//input[@name="adults"][@value="1"]');
-  }
-
-
-  /** @test */
-  public function inputChildrenShouldBePresent() {
-    $this->assertXPath('//input[@name="children"][@value="2"]');
-  }
-}
-
-
-
-
-class Admin_SessionActivityInscriptionControllerSubscribeWithQuotasFullChildrenTest
-  extends Admin_SessionActivityInscriptionControllerTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    Class_SessionActivity::find(32)->resetAttendees();
-    Class_SessionActivity::find(32)->setEffectifChildMax(2);
-    $this->dispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10');
-  }
-
-
-  /** @test */
-  public function pageShouldContainNoMoreChildrenRoomMessage() {
-    $this->assertXPathContentContains('//div', 'Aucune place enfant disponible');
-  }
 }
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php b/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php
index 10268c58cc3ec08421e1e50cc0255ceabc4dd39f..2a220bdbd9728ac1a88e2408abfdbf4b4169bf6d 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerActivitiesTest.php
@@ -483,7 +483,8 @@ class AbonneControllerActivitiesListWithRightRegisterTest
 
   /** @test */
   public function pageShouldContainsLinkToAdminSessionActivityInscriptions() {
-    $this->assertXPath('//a[contains(@href, "/admin/session-activity/inscriptions/id/31")]');
+    $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/31/search_session_activity_subscription_status/all")][@target="_blank"]',
+                                      'Nouvelle inscription');
   }
 }
 
@@ -614,174 +615,6 @@ class AbonneControllerActivitiesRegisteredActionTest
 
 
 
-class AbonneControllerActivitiesRegisteredWithQuotasActionTest
-  extends AbstractAbonneControllerActivitiesTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->dispatch('/abonne/activities-registered');
-  }
-
-
-  /** @test */
-  public function adultsCountShouldBePresent() {
-    $this->assertXPathContentContains('//td', '1 adulte');
-  }
-
-
-  /** @test */
-  public function childrenCountShouldBePresent() {
-    $this->assertXPathContentContains('//td', '1 enfant');
-  }
-
-
-  /** @test */
-  public function rowShouldContainEditButton() {
-    $this->assertXPath('//td//a[contains(@href, "/abonne/edit-session/id/1")]');
-  }
-}
-
-
-
-
-abstract class AbonneControllerActivitiesEditSessionWithQuotasTestCase
-  extends AbstractAbonneControllerActivitiesTestCase {
-
-  protected $_inscription,
-    $_other_inscription;
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->_session_python_juillet
-      ->resetAttendees()
-      ->setEffectifMax(5)
-      ->setEffectifChildMax(15)
-      ->setEffectifInscriptionMax(3)
-      ->setEffectifInscriptionChildMax(12);
-
-    $this->_other_inscription = $this->_register(1, 1);
-    $this->_inscription = $this->_register(1, 1);
-    $this->_inscription->setStagiaire($this->_amadou)
-                       ->assertSave();
-  }
-
-
-  protected function _register($adults, $children) {
-    $inscription = Class_SessionActivityInscription::newInstance(['session_activity_id' => 121,
-                                                                  'adults' => $adults,
-                                                                  'children' => $children]);
-    $inscription->assertSave();
-
-    $this->_session_python_juillet->addSessionActivityInscription($inscription);
-    return $inscription;
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesEditSessionWithQuotasActionTest
-  extends AbonneControllerActivitiesEditSessionWithQuotasTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->dispatch('/abonne/edit-session/id/' . $this->_inscription->getId());
-  }
-
-
-  /** @test */
-  public function inputAdultsShouldHaveValueOneAndMaxThree() {
-    $this->assertXPath('//input[@name="adults"][@value="1"][@max="3"]');
-  }
-
-
-  /** @test */
-  public function inputChildrenShouldHaveValueOneAndMaxTwelve() {
-    $this->assertXPath('//input[@name="children"][@value="1"][@max="12"]');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesEditSessionWithQuotasPostTest
-  extends AbonneControllerActivitiesEditSessionWithQuotasTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $url = $_SERVER['HTTP_REFERER'] = '/abonne/edit-session/id/' . $this->_inscription->getId();
-
-    $this->postDispatch($url, ['adults' => 2, 'children' => 3]);
-    $this->_inscription = Class_SessionActivityInscription::find($this->_inscription->getId());
-  }
-
-
-  /** @test */
-  public function inscriptionAdultsShouldBeTwo() {
-    $this->assertEquals(2, $this->_inscription->getAdults());
-  }
-
-
-  /** @test */
-  public function inscriptionChildrenShouldBeThree() {
-    $this->assertEquals(3, $this->_inscription->getChildren());
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivities() {
-    $this->assertRedirectContains('/abonne/activities-registered');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesEditSessionWithQuotasErrorsPostTest
-  extends AbonneControllerActivitiesEditSessionWithQuotasTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->postDispatch('/abonne/edit-session/id/'
-                        . $this->_inscription->getId(), ['adults' => 5, 'children' => 15]);
-  }
-
-
-  /** @test */
-  public function editAdultsShouldHaveErrorThreeMorePlace() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', '(3 restantes)');
-  }
-
-
-  /** @test */
-  public function editChildrenShouldHaveErrorTwelvePlace() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', '(12 restantes)');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesEditSessionBadUserWithQuotasTest
-  extends AbonneControllerActivitiesEditSessionWithQuotasTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->dispatch('/abonne/edit-session/id/' . $this->_other_inscription->getId());
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivities() {
-    $this->assertRedirectContains('/activities');
-  }
-}
-
-
-
-
 class AbonneControllerActivitiesDoneActionTest
   extends AbstractAbonneControllerActivitiesTestCase {
 
@@ -1024,312 +857,6 @@ class AbonneControllerActivitiesAmadouInscritSessionFebruaryJavaOpenTest extends
 
 
 
-class AbonneControllerActivitiesRegisterSessionWithQuotasTest
-  extends AbstractAbonneControllerActivitiesTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->_session_java_fevrier
-      ->resetAttendees()
-      ->setEffectifMax(5)
-      ->setEffectifChildMax(15)
-      ->setEffectifInscriptionMax(5)
-      ->setEffectifInscriptionChildMax(15);
-
-    $inscription = Class_SessionActivityInscription::newInstance(['session_activity_id' => 31,
-                                                                  'adults' => 1,
-                                                                  'children' => 1]);
-    $inscription->assertSave();
-
-    $this->_session_java_fevrier->addSessionActivityInscription($inscription);
-
-    $this->dispatch('/opac/abonne/inscrire-session/id/31');
-  }
-
-
-  public function quotasElements() {
-    return [['adults', 4], ['children', 14]];
-  }
-
-
-  /**
-   * @test
-   * @dataProvider quotasElements
-   */
-  public function pageShouldContainsElementCount($element, $count) {
-    $this->assertXPath('//input[@type="number"][@min="0"][@max="'
-                       . $count . '"][@name="'
-                       . $element . '"]');
-  }
-}
-
-
-
-
-abstract class AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase
-  extends AbstractAbonneControllerActivitiesTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    Class_SessionActivity::find(31)->resetAttendees();
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-  }
-
-
-  protected function _postAdultsAndChildren($adults, $children) {
-    $this->postDispatch('/opac/abonne/inscrire-session/id/31',
-                        ['adults' => $adults, 'children' => $children]);
-  }
-
-
-  protected function _alreadyRegisteredAdultsAndChildren($adults, $children) {
-    $inscription = Class_SessionActivityInscription::newInstance(['session_activity_id' => 31,
-                                                                  'adults' => $adults,
-                                                                  'children' => $children]);
-    $inscription->assertSave();
-
-    $this->_session_java_fevrier->addSessionActivityInscription($inscription);
-    return $this;
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesRegisterSessionWithQuotasPostTest
-  extends AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_session_java_fevrier
-      ->setEffectifMax(10)
-      ->setEffectifChildMax(20)
-      ->setEffectifInscriptionMax(5)
-      ->setEffectifInscriptionChildMax(15);
-
-    $this->_postAdultsAndChildren(1, 3);
-  }
-
-
-  /** @test */
-  public function inscriptionShouldBeCreated() {
-    $inscription = Class_SessionActivityInscription::findFirstBy(['session_activity_id' => 31]);
-    $this->assertNotNull($inscription);
-    return $inscription;
-  }
-
-
-  /**
-   * @test
-   * @depends inscriptionShouldBeCreated
-   */
-  public function inscriptionAdultsShouldBeOne($inscription) {
-    $this->assertEquals(1, $inscription->getAdults());
-  }
-
-
-  /**
-   * @test
-   * @depends inscriptionShouldBeCreated
-   */
-  public function inscriptionChildrenShouldBeThree($inscription) {
-    $this->assertEquals(3, $inscription->getChildren());
-  }
-
-
-  /** @test */
-  public function shouldRedirectToActivities() {
-    $this->assertRedirectTo('/activities');
-  }
-
-
-  /** @test */
-  public function shouldNotifyRegistered() {
-    $this->assertFlashMessengerContentContains('Vous êtes inscrit');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesRegisterSessionWithQuotasZerosPostTest
-  extends AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_session_java_fevrier
-      ->setEffectifMax(10)
-      ->setEffectifChildMax(20)
-      ->setEffectifInscriptionMax(5)
-      ->setEffectifInscriptionChildMax(15);
-
-    $this->_postAdultsAndChildren(0, 0);
-  }
-
-
-  /** @test */
-  public function shouldDisplayAtLeastOnePersonError() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', 'Au moins un adulte ou un enfant doit s\'inscrire');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesRegisterSessionWithMaxQuotasErrorsPostTest
-  extends AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_session_java_fevrier
-      ->setEffectifInscriptionMax(1)
-      ->setEffectifInscriptionChildMax(2)
-      ->setEffectifMax(10)
-      ->setEffectifChildMax(20);
-
-    $this->_postAdultsAndChildren(2, 3);
-  }
-
-
-  /** @test */
-  public function shouldDisplayTooManyAdults() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'adultes est supérieur au nombre de places adultes disponibles (1 restante)');
-  }
-
-
-  /** @test */
-  public function shouldDisplayTooManyChildren() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'enfants est supérieur au nombre de places enfants disponibles (2 restantes)');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesRegisterSessionWithNoMoreRoomAtAllErrorsPostTest
-  extends AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_session_java_fevrier
-      ->setEffectifInscriptionMax(1)
-      ->setEffectifInscriptionChildMax(2)
-      ->setEffectifMax(1)
-      ->setEffectifChildMax(2);
-
-    $this->_alreadyRegisteredAdultsAndChildren(1, 2)
-         ->_postAdultsAndChildren(1, 2);
-  }
-
-
-  /** @test */
-  public function shouldDisplayNoMoreRoomMessage() {
-    $this->assertFlashMessengerContentContains('Inscription impossible, plus de place disponible.');
-  }
-
-
-  /** @test */
-  public function sessionShouldStillHaveOnlyOneRegistration() {
-    $this->assertEquals(1, Class_SessionActivityInscription::countBy(['session_activity_id' => 31]));
-  }
-
-
-  /** @test */
-  public function shouldRedirect() {
-    $this->assertRedirectTo('/activities');
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesRegisterSessionWithNoMoreAdultsPostTest
-  extends AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_session_java_fevrier
-      ->setEffectifInscriptionMax(1)
-      ->setEffectifInscriptionChildMax(2)
-      ->setEffectifMax(1)
-      ->setEffectifChildMax(2);
-
-    $this->_alreadyRegisteredAdultsAndChildren(1, 1)
-         ->_postAdultsAndChildren(1, 2);
-  }
-
-
-  /** @test */
-  public function pageShouldContainsNoMoreAdultsRoom() {
-    $this->assertXPathContentContains('//span', 'Aucune place adulte disponible');
-  }
-
-
-  /** @test */
-  public function inputAdultsShouldNotDisplay() {
-    $this->assertNotXPath('//input[@name="adults"]');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsTooManyChildrenError() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'enfants est supérieur au nombre de places enfants disponibles (1 restante)');
-  }
-
-
-  /** @test */
-  public function sessionShouldStillHaveOnlyOneRegistration() {
-    $this->assertEquals(1, Class_SessionActivityInscription::countBy(['session_activity_id' => 31]));
-  }
-}
-
-
-
-
-class AbonneControllerActivitiesRegisterSessionWithNoMoreChildrenPostTest
-  extends AbonneControllerActivitiesRegisterSessionWithQuotasPostTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_session_java_fevrier
-      ->setEffectifInscriptionMax(2)
-      ->setEffectifInscriptionChildMax(3)
-      ->setEffectifMax(2)
-      ->setEffectifChildMax(3);
-
-    $this->_alreadyRegisteredAdultsAndChildren(1, 3)
-         ->_postAdultsAndChildren(2, 2);
-  }
-
-
-  /** @test */
-  public function pageShouldContainsNoMoreChildrenRoom() {
-    $this->assertXPathContentContains('//span', 'Aucune place enfant disponible');
-  }
-
-
-  /** @test */
-  public function inputChildrenShouldNotDisplay() {
-    $this->assertNotXPath('//input[@name="children"]');
-  }
-
-
-  /** @test */
-  public function pageShouldContainsTooManyAdultsError() {
-    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'adultes est supérieur au nombre de places adultes disponibles (1 restante)');
-  }
-
-
-  /** @test */
-  public function sessionShouldStillHaveOnlyOneRegistration() {
-    $this->assertEquals(1, Class_SessionActivityInscription::countBy(['session_activity_id' => 31]));
-  }
-}
-
-
-
-
 class AbonneControllerActivitiesAmadouWithoutMailInscritSessionFebruaryJavaOpenTest extends AbstractAbonneControllerActivitiesTestCase {
   protected
     $_mails;
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerPelliculeTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerPelliculeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7a5b703b7dc95d6efd3c17c118c4be68b41745e
--- /dev/null
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerPelliculeTest.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, 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
+ */
+
+
+class NoticeAjaxControllerPelliculeActiveTest extends AbstractControllerTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_http_client;
+
+  public function setUp() {
+    parent::setUp();
+    Class_CosmoVar::set('url_services', 'http://cache.org');
+    Class_AdminVar::set('PELLICULE_SETTINGS',
+                        json_encode(['Provider' => [Class_AdminVar_PelliculeSettings::ELECTRE_REST_2],
+                                     'Login' => ['mylogin'],
+                                     'Password' => ['mys3cr3t']]));
+
+    Class_Exemplaire::clearCache();
+    Class_Notice::clearCache();
+
+    $this->fixture('Class_Notice',
+                   ['id' => 1,
+                    'titre' => 'Pomme et Ananas',
+                    'titre_principal' => 'Journey in Satchidananda',
+                    'auteur_principal' => 'Jacques Audiard',
+                    'isbn' => '978225316403610',
+                    'ean' => '978225316403610',
+                    'clef_alpha' => 'POMMEETANANAS',
+                    'facettes' => 'A1 Q9',
+                    'date_maj' => '01/01/2015',
+                    'type_doc' => Class_TypeDoc::LIVRE,
+                    'url_vignette' => '',
+                    'url_image' => '',
+                    'exemplaires' => [$this->fixture('Class_Exemplaire',
+                                                     ['id' => 2,
+                                                      'id_origine' => '9939_SSIRTE'])]
+                   ]);
+
+    $this->_http_client = $this->mock()
+                               ->whenCalled('open_url')
+                               ->answers(null);
+
+    Class_WebService_AllServices::setHttpClient($this->_http_client);
+    $this->dispatch('/noticeajax/notice/id/1');
+  }
+
+
+  /** @test */
+  public function requestShouldContainsAuthorizationHeader() {
+    $headers = [['Authorization' => "Pellicule eyJwcm92aWRlciI6ImVsZWN0cmVfcmVzdF8yIiwiY2xpZW50X2lkIjoibXlsb2dpbiIsImNsaWVudF9zZWNyZXQiOiJteXMzY3IzdCJ9"]];
+
+    $call_params = $this->_http_client->getAttributesForLastCallOn('open_url');
+    $this->assertEquals($headers, $call_params[1]['headers']);
+  }
+}
diff --git a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
index b635191cad7c62466af342eb8f0e959986d4bc02..c765327b5792dc527caa91cde1e296bf0ae9ddd6 100644
--- a/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
+++ b/tests/application/modules/opac/controllers/NoticeAjaxControllerTest.php
@@ -834,8 +834,9 @@ class NoticeAjaxControllerExemplairesWithOtherAnnexTestCase extends AbstractCont
 
   /** @test */
   public function facetsShouldBeUpdatedWithAnnex21() {
-    $this->dispatch('noticeajax/exemplaires/id/123', true);
-    $this->assertEquals('D78092 A3029 A9751 A117014 A117015 M37414 G464 HNNNN0002 HNANA0002 T0 B6 SA9 Eemplacement de test Y21 V6', Class_notice::find(123)->getFacettes());
+    $this->dispatch('noticeajax/exemplaires/id/123');
+    $this->assertEquals('D78092 A3029 A9751 A117014 A117015 M37414 G464 HNNNN0002 HNANA0002 T0 B6 SA9 Eemplacement de test Y21 V6 HNRNR0001',
+                        Class_notice::find(123)->getFacettes());
   }
 }
 
diff --git a/tests/db/UpgradeDBTest.php b/tests/db/UpgradeDBTest.php
index e9d58b412ab53fd5f127f4babbb4e4659f535344..02776e4c46188ffa2b79bb079c0e944723696a46 100644
--- a/tests/db/UpgradeDBTest.php
+++ b/tests/db/UpgradeDBTest.php
@@ -3470,3 +3470,53 @@ class UpgradeDB_395_Test extends UpgradeDBTestCase {
     $this->assertFieldType('session_activity_inscriptions', 'children', 'int(11) unsigned');
   }
 }
+
+
+
+
+class UpgradeDB_396_Test extends UpgradeDBTestCase {
+
+  public function prepare() {
+    $this->dropFieldFrom('session_activity', 'age_child_max')
+         ->dropFieldFrom('session_activity', 'all_day')
+         ->dropFieldFrom('session_activity_inscriptions', 'notified_at');
+    $this->silentQuery('ALTER TABLE `session_activity` MODIFY `date_debut` date');
+    $this->silentQuery('ALTER TABLE `session_activity` MODIFY `date_fin` date');
+  }
+
+
+  /** @test */
+  public function sessionActivityShouldContainsAgeChildMaxColumn() {
+    $this->assertFieldType('session_activity', 'age_child_max', 'int(11) unsigned');
+  }
+
+
+  /** @test */
+  public function sessionActivityShouldContainsAlldayColumn() {
+    $this->assertFieldType('session_activity', 'all_day', 'tinyint(1) unsigned');
+  }
+
+
+  /** @test */
+  public function sessionActivityAllDayDefaultShouldBeOne() {
+    $this->assertFieldDefault('session_activity', 'all_day', 1);
+  }
+
+
+  /** @test */
+  public function sessionActivityColumDateDebutShouldBeDatetime() {
+    $this->assertFieldType('session_activity', 'date_debut', 'datetime');
+  }
+
+
+  /** @test */
+  public function sessionActivityColumDateFinShouldBeDatetime() {
+    $this->assertFieldType('session_activity', 'date_fin', 'datetime');
+  }
+
+
+  /** @test */
+  public function sessionActivityInscriptionColumNotifiedAtShouldBeDatetime() {
+    $this->assertFieldType('session_activity_inscriptions', 'notified_at', 'datetime');
+  }
+}
\ No newline at end of file
diff --git a/tests/js/MultiSelectionTest.php b/tests/js/MultiSelectionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cff8067164ae26bc9707056f9912e4bd3f9263e7
--- /dev/null
+++ b/tests/js/MultiSelectionTest.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, 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
+ */
+
+
+class MultiSelectionTest extends PHPUnit_Framework_TestCase {
+
+  /** @test */
+  public function multiSelectJstestShouldSuccess() {
+    exec('phantomjs ' . ROOT_PATH . 'tests_js/lib/qunit-phantomjs-runner/runner.js ' . ROOT_PATH . 'public/admin/js/multi_selection/tests.html', $output, $result);
+    $this->assertEquals(0, $result, implode("\n", $output));
+  }
+}
\ No newline at end of file
diff --git a/tests/library/Class/CodifThesaurusTest.php b/tests/library/Class/CodifThesaurusTest.php
index 8957f4d7e1d614747fdb26c063f3228ceaf82e35..88166946bbbac6de5a56eb8d15fc1a41ca5bc7db 100644
--- a/tests/library/Class/CodifThesaurusTest.php
+++ b/tests/library/Class/CodifThesaurusTest.php
@@ -171,8 +171,8 @@ class CodifThesaurusGetIndicesRootTest extends ModelTestCase {
 
   /** @test */
   public function shouldExcludeNoveltyAndDomains() {
-    Class_CodifThesaurus::getIndices('root', false);
-    $expected_params = ['where' => 'code not in ("Nouveauté par bibliothèque", "Nouveauté par annexe", "Catalogue") and LENGTH(id_thesaurus) in (1,4)',
+    Class_CodifThesaurus::getIndices('root');
+    $expected_params = ['where' => 'id_thesaurus not in ("NNNN", "NANA", "CCCC") and LENGTH(id_thesaurus) in (1,4)',
                         'order' => 'id_thesaurus'];
 
     $this->assertEquals($expected_params,
@@ -183,7 +183,7 @@ class CodifThesaurusGetIndicesRootTest extends ModelTestCase {
   /** @test */
   public function shouldExcludeNoveltyButNotDomain() {
     Class_CodifThesaurus::getIndices('root', true);
-    $expected_params = ['where' => 'code not in ("Nouveauté par bibliothèque", "Nouveauté par annexe") and LENGTH(id_thesaurus) in (1,4)',
+    $expected_params = ['where' => 'id_thesaurus not in ("NNNN", "NANA") and LENGTH(id_thesaurus) in (1,4)',
                         'order' => 'id_thesaurus'];
     $this->assertEquals($expected_params, Class_CodifThesaurus::getFirstAttributeForLastCallOn('findAllBy'));
   }
diff --git a/tests/library/Class/NoticeNoveltyTest.php b/tests/library/Class/NoticeNoveltyTest.php
index b13299a566fa9bbe6cbb8989c4ed643a9df48c27..92683e8fe15c8874f734db0a6dd92e9af377f2bf 100644
--- a/tests/library/Class/NoticeNoveltyTest.php
+++ b/tests/library/Class/NoticeNoveltyTest.php
@@ -38,33 +38,42 @@ class NoticeNoveltyTest extends ModelTestCase{
                     'libelle' => 'My Annexe']);
 
 
-    $this
+    $record_12 = $this
       ->fixture('Class_Notice',
                 ['id' => 12,
                  'facettes' => 'T1 B4',
-                 'date_creation' => '2017-02-28'])
-      ->addExemplaire($this->fixture('Class_Exemplaire',
-                                     ['id' => 90000,
-                                      'id_bib' => 4,
-                                      'annexe' => 2,
-                                      'date_nouveaute' => '2017-02-28']))
-      ->assertSave();
-
-    $this
+                 'date_creation' => '2017-02-28']);
+    $record_12->addExemplaire($this->fixture('Class_Exemplaire',
+                                             ['id' => 90000,
+                                              'id_bib' => 4,
+                                              'annexe' => 2,
+                                              'date_nouveaute' => '2017-02-28']))
+              ->assertSave();
+
+    $record_15 = $this
       ->fixture('Class_Notice',
                 ['id' => 15,
                  'facettes' => 'T1 B4 HNNNN0001',
-                 'date_creation' => ''])
-      ->addExemplaire($this->fixture('Class_Exemplaire',
-                                     ['id' => 99999,
-                                      'id_bib' => 4,
-                                      'annexe' => 2,
-                                      'date_nouveaute' => '2015-02-28']))
-      ->assertSave();
+                 'date_creation' => '']);
+    $record_15->addExemplaire($this->fixture('Class_Exemplaire',
+                                             ['id' => 99999,
+                                              'id_bib' => 4,
+                                              'annexe' => 2,
+                                              'date_nouveaute' => '2015-02-28']))
+              ->assertSave();
 
 
-    $this->onLoaderOfModel('Class_Notice');
     Class_Notice::setMemoryCleaner(function() {});
+    $this->onLoaderOfModel('Class_Notice')
+         ->whenCalled('findAllBy')
+         ->willDo(function($params) use($record_12, $record_15)
+                  {
+                    if ($params['where'] == 'match(facettes) against("+(HNNNN* HNANA* HNRNR0001)" in boolean mode) and id_notice > 0')
+                      return [$record_12, $record_15];
+
+                    return [];
+                  });
+
     Class_Notice::indexNoveltyFacets();
   }
 
@@ -72,21 +81,22 @@ class NoticeNoveltyTest extends ModelTestCase{
   /** @test */
   public function record12ShouldHaveNoveltyFacet() {
     Class_Notice::find(12)->updateNoveltyFacets();
-    $this->assertEquals('T1 B4 HNNNN0001 HNANA0001',
+    $this->assertEquals('T1 B4 HNNNN0001 HNANA0001 HNRNR0001',
                         Class_Notice::find(12)->getFacettes());
   }
 
 
   /** @test */
   public function record15ShouldNotHaveNoveltyFacet() {
-    $this->assertEquals('T1 B4', Class_Notice::find(15)->getFacettes());
+    $this->assertEquals('T1 B4 HNRNR0002', Class_Notice::find(15)->getFacettes());
   }
 
 
   /** @test */
   public function indexShouldNotFetchAllRecords() {
-    $this->assertEquals(['where' => 'match(facettes) against("+(HNNNN* HNANA*)" in boolean mode)',
-                         'limitPage' => [2, 100]],
+    $this->assertEquals(['where' => 'match(facettes) against("+(HNNNN* HNANA* HNRNR0001)" in boolean mode) and id_notice > 15',
+                         'order' => 'id_notice',
+                         'limit' => 100],
                         Class_Notice::getFirstAttributeForLastCallOn('findAllBy'));
   }
 }
diff --git a/tests/library/Class/NoticeTest.php b/tests/library/Class/NoticeTest.php
index c4aaec6e53e004df6b0b535ec3e54ba8f5b79858..27b66076346771be2f696676235ccbaea499f4bf 100644
--- a/tests/library/Class/NoticeTest.php
+++ b/tests/library/Class/NoticeTest.php
@@ -1010,7 +1010,8 @@ class NoticeUpdateFacetsFromItemsTest extends ModelTestCase {
 
   /** @test */
   public function recordFacetsShouldContainsNoveltyForRoubaixAndNoveltyForLille() {
-    $this->assertEquals('T0 B10 YROUB HNNNN0001 HNANA0001 B11 HNNNN0002', Class_Notice::find(5)->getFacettes());
+    $this->assertEquals('T0 B10 YROUB HNNNN0001 HNANA0001 B11 HNNNN0002 HNRNR0001',
+                        Class_Notice::find(5)->getFacettes());
   }
 }
 
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/ActivitiesWidgetTest.php b/tests/library/ZendAfi/View/Helper/Accueil/ActivitiesWidgetTest.php
index f9eb501bc8f5905c201a763e434f935bad451c7a..e8f1ba9fcce78b867803dc814bbbefb0d15c99de 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/ActivitiesWidgetTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/ActivitiesWidgetTest.php
@@ -411,28 +411,6 @@ class ZendAfi_View_Helper_Accueil_SortAndDisplayActivitiesWidgetTest
   }
 
 
-  /** @test */
-  public function activitieShouldBeDisplayedByYearWithQuotas() {
-    foreach(Class_SessionActivity::findAll() as $activity)
-      $activity->resetAttendees();
-
-    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
-    $this->renderWidget(
-                        ['division' => 1,
-                         'type_module' => 'ACTIVITIES_WIDGET',
-                         'preferences' => ['selected_activities' => '3-1-5',
-                                           'display_order' => 'creation',
-                                           'display_group_mode' => 'ByYear',
-                         'display_mode' =>'Block']]);
-
-    $this->assertXPathContentContains($this->_html,
-                                      '//dd[@class="training_contributors"][preceding-sibling::dt[text()="Nombre d\'inscrits"]]',
-                                      '2', $this->_html);
-
-    $this->assertXPathContentContains($this->_html, '//h2[1]' , '2010');
-  }
-
-
   /** @test */
   public function sessionShouldBeDisplayedEvenIfNotEndedDate() {
     $this->renderWidget(
diff --git a/tests/scenarios/Activities/AbonneControllerWithQuotasTest.php b/tests/scenarios/Activities/AbonneControllerWithQuotasTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5a16dfddb49fc3bee03e7755c67919e1cbc30515
--- /dev/null
+++ b/tests/scenarios/Activities/AbonneControllerWithQuotasTest.php
@@ -0,0 +1,741 @@
+<?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
+ */
+
+
+abstract class AbonneControllerWithQuotasTestCase extends AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_amadou,
+    $_learn_java,
+    $_learn_python,
+    $_learn_smalltalk,
+    $_session_smalltalk_janvier,
+    $_session_smalltalk_juillet,
+    $_session_java_mars,
+    $_session_java_fevrier,
+    $_session_java_septembre,
+    $_session_python_juillet,
+    $_gallice_cafe,
+    $_bib_romains,
+    $_bonlieu,
+    $_mail_transport;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $_SERVER['HTTP_REFERER'] = '/activities';
+
+    $test_time = new TimeSourceForTest('2014-05-01 14:00:00');
+
+    Class_Activity::setTimeSource($test_time);
+    Class_SessionActivity::setTimeSource($test_time);
+
+    Class_AdminVar::set('ACTIVITY', '1');
+    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
+    Class_AdminVar::set('NOM_DOMAINE', 'bokeh.fr');
+
+    $this->_mail_transport = new MockMailTransport();
+    Zend_Mail::setDefaultTransport($this->_mail_transport);
+
+    $this->_amadou = $this->fixture('Class_Users',
+                                    ['id' => 435,
+                                     'nom' => 'Dou',
+                                     'prenom' => 'Ama',
+                                     'login' => 'Amadou',
+                                     'password' => 'pwd',
+                                     'mail' => 'amadou@afi-sa.fr',
+                                     'bib' => $this->fixture('Class_Bib',
+                                                             ['id' => 1,
+                                                              'libelle' => 'annecy']),
+                                     'idabon' => 435]);
+    $this->_amadou
+      ->beAbonneSIGB()
+      ->setUserGroups([$this->fixture('Class_UserGroup',['id' => 23])->addRightSuivreActivity()])
+      ->assertSave();
+
+    ZendAfi_Auth::getInstance()->logUser($this->_amadou);
+
+    $this->_gallice_cafe = $this->fixture('Class_Lieu',
+                                          ['id' => 98,
+                                           'libelle' => 'Galice']);
+
+
+    $this->_bib_romains = $this->fixture('Class_Lieu',
+                                         ['id' => '99',
+                                          'libelle' => 'Bibliothèque des romains']);
+
+
+    $this->_bonlieu = $this->fixture('Class_Lieu',
+                                     ['id' => 100,
+                                      'libelle' => 'Bonlieu',
+                                      'adresse' => "1, rue Jean-Jaures\nBP 294",
+                                      'code_postal' => 74007,
+                                      'ville' => 'Annecy',
+                                      'latitude' => '45.902179',
+                                      'longitude' => '6.128715']);
+
+    $this->_learn_smalltalk = $this
+      ->fixture('Class_Activity',
+                ['id' => 1,
+                 'libelle' => 'Learn Smalltalk']);
+
+    $this->_session_smalltalk_janvier = $this
+      ->fixture('Class_SessionActivity',
+                ['id' => 11,
+                 'activity' => $this->_learn_smalltalk,
+                 'effectif_min' => 1,
+                 'effectif_max' => 10,
+                 'effectif_inscription_max' => 5,
+                 'lieu' => $this->_gallice_cafe,
+                 'date_debut' => '2015-01-10',
+                 'date_fin' => '2015-01-10',
+                 'date_limite_inscription' => '2015-01-10',
+                 'stagiaires' => []]);
+
+    $this->_session_smalltalk_juillet = $this
+      ->fixture('Class_SessionActivity',
+                ['id' => 12,
+                 'activity' => $this->_learn_smalltalk,
+                 'effectif_min' => 1,
+                 'effectif_max' => 10,
+                 'effectif_inscription_max' => 5,
+                 'lieu' => $this->_gallice_cafe,
+                 'stagiaires' => [],
+                 'date_debut' => '2014-07-11',
+                 'date_fin' => '2014-07-15',
+                 'date_limite_inscription' => '2014-07-11']);
+
+    $this->_learn_smalltalk
+      ->setSessions([$this->_session_smalltalk_janvier,
+                     $this->_session_smalltalk_juillet])
+      ->assertSave();
+
+
+    $this->_learn_java = $this->fixture('Class_Activity',
+                                        ['id' => 3,
+                                         'libelle' => 'Learn Java',
+                                         'description' => 'whaaat ?']);
+
+
+    $this->_session_java_fevrier = $this->fixture('Class_SessionActivity',
+                                                  ['id' => 31,
+                                                   'activity' => $this->_learn_java,
+                                                   'effectif_min' => 2,
+                                                   'effectif_max' => 5,
+                                                   'effectif_inscription_max' => 5,
+                                                   'lieu' => $this->_bonlieu,
+                                                   'date_debut' => '2015-02-10',
+                                                   'date_fin' => '2015-02-20',
+                                                   'stagiaires' => [],
+                                                   'date_limite_inscription' => '2015-01-20']);
+
+    $this->_session_java_fevrier->setArticle($this->fixture('Class_Article',
+                                                            ['id' => 10,
+                                                             'titre' => 'Java est mort, vive python !',
+                                                             'contenu' => 'Java has been'])
+                                             ->setDateMaj('2020-04-11 08:00:00')
+                                             ->setEventsDebut('2020-04-11 12:00:00')
+                                             ->setEventsFin('2020-04-11 17:00:00'))
+                                ->save();
+
+
+    $this->_session_java_mars = $this->fixture('Class_SessionActivity',
+                                               ['id' => 32,
+                                                'activity' => $this->_learn_java,
+                                                'effectif_min' => 2,
+                                                'effectif_max' => 5,
+                                                'effectif_inscription_max' => 5,
+                                                'lieu' => $this->_gallice_cafe,
+                                                'date_debut' => '2015-03-01',
+                                                'stagiaires' => [],
+                                                'date_limite_inscription' => '2015-03-01']);
+
+    $this->_session_java_septembre = $this->fixture('Class_SessionActivity',
+                                                    ['id' => 30,
+                                                     'activity' => $this->_learn_java,
+                                                     'effectif_min' => 2,
+                                                     'effectif_max' => 5,
+                                                     'effectif_inscription_max' => 5,
+                                                     'date_debut' => '2014-09-10',
+                                                     'date_fin' => '2014-09-10',
+                                                     'stagiaires' => [],
+                                                     'lieu' => $this->_gallice_cafe,
+                                                     'date_limite_inscription' => '2014-09-10']);
+    $this->_session_java_septembre->beAnnule();
+
+    $this->_learn_java
+      ->setSessions([$this->_session_java_mars,
+                     $this->_session_java_septembre,
+                     $this->_session_java_fevrier])
+      ->assertSave();
+
+
+    $this->_learn_python = $this->fixture('Class_Activity',
+                                          ['id' => 12,
+                                           'libelle' => 'Learn Python']);
+
+    $this->_session_python_juillet = $this
+      ->fixture('Class_SessionActivity',
+                ['id' => 121,
+                 'activity' => $this->_learn_python,
+                 'date_debut' => '2014-07-10',
+                 'date_fin' => '2014-07-14',
+                 'date_limite_inscription' => '2014-04-29',
+                 'contenu' => 'Introduction a la syntaxe',
+                 'objectif' => 'Ecrire un premier programme',
+                 'effectif_min' => 1,
+                 'effectif_max' => 150,
+                 'effectif_inscription_max' => 5,
+                 'effectif_child_min' => 0,
+                 'effectif_child_max' => 5,
+                 'effectif_inscription_child_max' => 5,
+                 'age_child_max' => 12,
+                 'duree' => 36,
+                 'horaires' => '8h-12h, 14h-18h',
+                 'lieu' => $this->_bib_romains,
+                 'stagiaires' => [$this->_amadou],
+                 'intervenants' => [$this->fixture('Class_Users',
+                                                   ['id' =>76,
+                                                    'login' => 'jpp',
+                                                    'password' => 'pwd',
+                                                    'nom' => 'Pirant',
+                                                    'prenom' => 'Jean-Paul',
+                                                    'mail' => 'jp@bokeh.fr']),
+                                    $this->fixture('Class_users',
+                                                   ['id' => 77,
+                                                    'login' => 'cc',
+                                                    'password' => 'pwd',
+                                                    'nom' => 'Cerisier',
+                                                    'prenom' => 'Christophe',
+                                                    'mail' => 'cc@bokeh.fr'])
+                 ]]);
+
+    $this->_learn_python
+      ->setSessions([$this->_session_python_juillet])
+      ->assertSave();
+
+    $inscription_amadou_python = $this
+      ->fixture('Class_SessionActivityInscription',
+                ['id' => 1,
+                 'stagiaire' => $this->_amadou,
+                 'session_activity' => $this->_session_python_juillet,
+                 'adults' => 1,
+                 'children' => 1]);
+
+    $this->_amadou->setSessionActivityInscriptions([$inscription_amadou_python]);
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisteredActionTest
+  extends AbonneControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/abonne/activities-registered');
+  }
+
+
+  /** @test */
+  public function adultsCountShouldBePresent() {
+    $this->assertXPathContentContains('//td', '1 adulte');
+  }
+
+
+  /** @test */
+  public function childrenCountShouldBePresent() {
+    $this->assertXPathContentContains('//td', '1 enfant');
+  }
+
+
+  /** @test */
+  public function rowShouldContainEditButton() {
+    $this->assertXPath('//td//a[contains(@href, "/abonne/edit-session/id/1")]');
+  }
+}
+
+
+
+
+abstract class AbonneControllerWithQuotasEditSessionTest
+  extends AbonneControllerWithQuotasTestCase {
+
+  protected $_inscription,
+    $_other_inscription;
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
+    $this->_session_python_juillet
+      ->resetAttendees()
+      ->setEffectifMax(5)
+      ->setEffectifChildMax(15)
+      ->setEffectifInscriptionMax(3)
+      ->setEffectifInscriptionChildMax(12);
+
+    $this->_other_inscription = $this->_register(1, 1);
+    $this->_inscription = $this->_register(1, 1);
+    $this->_inscription->setStagiaire($this->_amadou)
+                       ->assertSave();
+  }
+
+
+  protected function _register($adults, $children) {
+    $inscription = Class_SessionActivityInscription::newInstance(['session_activity_id' => 121,
+                                                                  'adults' => $adults,
+                                                                  'children' => $children]);
+    $inscription->assertSave();
+
+    $this->_session_python_juillet->addSessionActivityInscription($inscription);
+    return $inscription;
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasEditSessionActionTest
+  extends AbonneControllerWithQuotasEditSessionTest {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/abonne/edit-session/id/' . $this->_inscription->getId());
+  }
+
+
+  /** @test */
+  public function inputAdultsShouldHaveValueOneAndMaxThree() {
+    $this->assertXPath('//input[@name="adults"][@value="1"][@max="3"]');
+  }
+
+
+  /** @test */
+  public function inputChildrenShouldHaveValueOneAndMaxTwelve() {
+    $this->assertXPath('//input[@name="children"][@value="1"][@max="12"]');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasEditPostTest
+  extends AbonneControllerWithQuotasEditSessionTest {
+
+  public function setUp() {
+    parent::setUp();
+    $url = $_SERVER['HTTP_REFERER'] = '/abonne/edit-session/id/' . $this->_inscription->getId();
+
+    $this->postDispatch($url, ['adults' => 2, 'children' => 3]);
+    $this->_inscription = Class_SessionActivityInscription::find($this->_inscription->getId());
+  }
+
+
+  /** @test */
+  public function inscriptionAdultsShouldBeTwo() {
+    $this->assertEquals(2, $this->_inscription->getAdults());
+  }
+
+
+  /** @test */
+  public function inscriptionChildrenShouldBeThree() {
+    $this->assertEquals(3, $this->_inscription->getChildren());
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivities() {
+    $this->assertRedirectContains('/abonne/activities-registered');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasErrorPostTest
+  extends AbonneControllerWithQuotasEditSessionTest {
+
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/abonne/edit-session/id/'
+                        . $this->_inscription->getId(), ['adults' => 5, 'children' => 15]);
+  }
+
+
+  /** @test */
+  public function editAdultsShouldHaveErrorThreeMorePlace() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', '(3 restantes)');
+  }
+
+
+  /** @test */
+  public function editChildrenShouldHaveErrorTwelvePlace() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', '(12 restantes)');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasBadUserTest
+  extends AbonneControllerWithQuotasEditSessionTest {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/abonne/edit-session/id/' . $this->_other_inscription->getId());
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivities() {
+    $this->assertRedirectContains('/activities');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionTest
+  extends AbonneControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->resetAttendees()
+      ->setEffectifMax(5)
+      ->setEffectifChildMax(15)
+      ->setEffectifInscriptionMax(5)
+      ->setEffectifInscriptionChildMax(15);
+
+    $inscription = Class_SessionActivityInscription::newInstance(['session_activity_id' => 31,
+                                                                  'adults' => 1,
+                                                                  'children' => 1]);
+    $inscription->assertSave();
+
+    $this->_session_java_fevrier->addSessionActivityInscription($inscription);
+
+    $this->dispatch('/opac/abonne/inscrire-session/id/31');
+  }
+
+
+  public function quotasElements() {
+    return [['adults', 4], ['children', 14]];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider quotasElements
+   */
+  public function pageShouldContainsElementCount($element, $count) {
+    $this->assertXPath('//input[@type="number"][@min="0"][@max="'
+                       . $count . '"][@name="'
+                       . $element . '"]');
+  }
+}
+
+
+
+
+abstract class AbonneControllerWithQuotasRegisterSessionPostTestCase
+  extends AbonneControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(31)->resetAttendees();
+  }
+
+
+  protected function _postAdultsAndChildren($adults, $children) {
+    $this->postDispatch('/opac/abonne/inscrire-session/id/31',
+                        ['adults' => $adults, 'children' => $children]);
+  }
+
+
+  protected function _alreadyRegisteredAdultsAndChildren($adults, $children) {
+    $inscription = Class_SessionActivityInscription::newInstance(['session_activity_id' => 31,
+                                                                  'adults' => $adults,
+                                                                  'children' => $children]);
+    $inscription->assertSave();
+
+    $this->_session_java_fevrier->addSessionActivityInscription($inscription);
+    return $this;
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->setEffectifMax(10)
+      ->setEffectifChildMax(20)
+      ->setEffectifInscriptionMax(5)
+      ->setEffectifInscriptionChildMax(15);
+
+    $this->_postAdultsAndChildren(1, 3);
+  }
+
+
+  /** @test */
+  public function inscriptionShouldBeCreated() {
+    $inscription = Class_SessionActivityInscription::findFirstBy(['session_activity_id' => 31]);
+    $this->assertNotNull($inscription);
+    return $inscription;
+  }
+
+
+  /**
+   * @test
+   * @depends inscriptionShouldBeCreated
+   */
+  public function inscriptionAdultsShouldBeOne($inscription) {
+    $this->assertEquals(1, $inscription->getAdults());
+  }
+
+
+  /**
+   * @test
+   * @depends inscriptionShouldBeCreated
+   */
+  public function inscriptionChildrenShouldBeThree($inscription) {
+    $this->assertEquals(3, $inscription->getChildren());
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivities() {
+    $this->assertRedirectTo('/activities');
+  }
+
+
+  /** @test */
+  public function shouldNotifyRegistered() {
+    $this->assertFlashMessengerContentContains('Vous êtes inscrit');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionZerosPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->setEffectifMax(10)
+      ->setEffectifChildMax(20)
+      ->setEffectifInscriptionMax(5)
+      ->setEffectifInscriptionChildMax(15);
+
+    $this->_postAdultsAndChildren(0, 0);
+  }
+
+
+  /** @test */
+  public function shouldDisplayAtLeastOnePersonError() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', 'Au moins un adulte ou un enfant doit s\'inscrire');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionMaxQuotasErrosPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->setEffectifInscriptionMax(1)
+      ->setEffectifInscriptionChildMax(2)
+      ->setEffectifMax(10)
+      ->setEffectifChildMax(20);
+
+    $this->_postAdultsAndChildren(2, 3);
+  }
+
+
+  /** @test */
+  public function shouldDisplayTooManyAdults() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'adultes est supérieur au nombre de places adultes disponibles (1 restante)');
+  }
+
+
+  /** @test */
+  public function shouldDisplayTooManyChildren() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'enfants est supérieur au nombre de places enfants disponibles (2 restantes)');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionChildAgeMaxPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  /** @test */
+  public function nombreEnfantsShouldContainAgeMax() {
+    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
+    $this->_session_java_mars
+      ->setEffectifInscriptionMax(5)
+      ->setEffectifInscriptionChildMax(10)
+      ->setEffectifMax(10)
+      ->setEffectifChildMax(10)
+      ->setAgeChildMax(12)
+      ->resetAttendees()
+      ->assertSave();
+    $this->dispatch('/abonne/inscrire-session/id/32');
+    $this->assertXPathContentContains('//table//td//label[@data-name="children"]',
+                                      'Nombre d\'enfants (âge max 12)');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionWithNoMoreRoomAllErrorsPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->setEffectifInscriptionMax(1)
+      ->setEffectifInscriptionChildMax(2)
+      ->setEffectifMax(1)
+      ->setEffectifChildMax(2);
+
+    $this->_alreadyRegisteredAdultsAndChildren(1, 2)
+         ->_postAdultsAndChildren(1, 2);
+  }
+
+
+  /** @test */
+  public function shouldDisplayNoMoreRoomMessage() {
+    $this->assertFlashMessengerContentContains('Inscription impossible, plus de place disponible.');
+  }
+
+
+  /** @test */
+  public function sessionShouldStillHaveOnlyOneRegistration() {
+    $this->assertEquals(1, Class_SessionActivityInscription::countBy(['session_activity_id' => 31]));
+  }
+
+
+  /** @test */
+  public function shouldRedirect() {
+    $this->assertRedirectTo('/activities');
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionWithNoMoreAdultsPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->setEffectifInscriptionMax(1)
+      ->setEffectifInscriptionChildMax(2)
+      ->setEffectifMax(1)
+      ->setEffectifChildMax(2);
+
+    $this->_alreadyRegisteredAdultsAndChildren(1, 1)
+         ->_postAdultsAndChildren(1, 2);
+  }
+
+
+  /** @test */
+  public function pageShouldContainsNoMoreAdultsRoom() {
+    $this->assertXPathContentContains('//span', 'Aucune place adulte disponible');
+  }
+
+
+  /** @test */
+  public function inputAdultsShouldNotDisplay() {
+    $this->assertNotXPath('//input[@name="adults"]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsTooManyChildrenError() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'enfants est supérieur au nombre de places enfants disponibles (1 restante)');
+  }
+
+
+  /** @test */
+  public function sessionShouldStillHaveOnlyOneRegistration() {
+    $this->assertEquals(1, Class_SessionActivityInscription::countBy(['session_activity_id' => 31]));
+  }
+}
+
+
+
+
+class Activities_AbonneControllerWithQuotasRegisterSessionWithNoMoreChildrenPostTest
+  extends AbonneControllerWithQuotasRegisterSessionPostTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_session_java_fevrier
+      ->setEffectifInscriptionMax(2)
+      ->setEffectifInscriptionChildMax(3)
+      ->setEffectifMax(2)
+      ->setEffectifChildMax(3);
+
+    $this->_alreadyRegisteredAdultsAndChildren(1, 3)
+         ->_postAdultsAndChildren(2, 2);
+  }
+
+
+  /** @test */
+  public function pageShouldContainsNoMoreChildrenRoom() {
+    $this->assertXPathContentContains('//span', 'Aucune place enfant disponible');
+  }
+
+
+  /** @test */
+  public function inputChildrenShouldNotDisplay() {
+    $this->assertNotXPath('//input[@name="children"]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsTooManyAdultsError() {
+    $this->assertXPathContentContains('//ul[@class="errors"]', 'Le nombre d\'adultes est supérieur au nombre de places adultes disponibles (1 restante)');
+  }
+
+
+  /** @test */
+  public function sessionShouldStillHaveOnlyOneRegistration() {
+    $this->assertEquals(1, Class_SessionActivityInscription::countBy(['session_activity_id' => 31]));
+  }
+}
diff --git a/tests/scenarios/Activities/AdminControllerInscriptionWithQuotasTest.php b/tests/scenarios/Activities/AdminControllerInscriptionWithQuotasTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2365c1b314df909a9f27b769ef0eea9d25f344f
--- /dev/null
+++ b/tests/scenarios/Activities/AdminControllerInscriptionWithQuotasTest.php
@@ -0,0 +1,391 @@
+<?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
+ */
+
+
+abstract class AdminControllerInscriptionWithQuotasTestCase
+  extends Admin_AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_group_profs,
+    $_groupe_stagiaires,
+    $_prof_laurent,
+    $_amandine,
+    $_patrick,
+    $_salle_reunion,
+    $_learn_java;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
+
+    $this->_group_profs = $this->fixture('Class_UserGroup',
+                                         ['id' => 344,
+                                          'libelle' => 'Profs',
+                                          'rights' => [Class_UserGroup::RIGHT_DIRIGER_ACTIVITY]]);
+
+    $this->_groupe_stagiaires = $this->fixture('Class_UserGroup',
+                                               ['id' => 12,
+                                                'libelle' => 'Stagiaires',
+                                                'rights' => [Class_UserGroup::RIGHT_SUIVRE_ACTIVITY]]);
+
+    $this->_prof_laurent = $this->fixture('Class_users',
+                                          ['id' => 34,
+                                           'nom' => 'Laffont',
+                                           'prenom' => 'Laurent',
+                                           'login' => 'lla',
+                                           'password' => 'pwd',
+                                           'user_groups' => [$this->_group_profs]]);
+
+    $this->_amandine = $this->fixture('Class_Users',
+                                      ['id' => 10,
+                                       'nom' => 'Pistache',
+                                       'prenom' => 'Amandine',
+                                       'login' => 'Amd',
+                                       'password' => 'fx9k',
+                                       'mail' => 'pist@che.io',
+                                       'user_groups' => [$this->_groupe_stagiaires]]);
+
+    $this->_patrick = $this->fixture('Class_Users',
+                                     ['id' => 5,
+                                      'id_site' => 12,
+                                      'login' => 'Pat',
+                                      'password' => '123',
+                                      'prenom' => 'Patrick',
+                                      'nom' => 'Barroca',
+                                      'mail' => 'user@server.org',
+                                      'user_groups' => [$this->_groupe_stagiaires]]);
+
+    $this->_salle_reunion = $this->fixture('Class_Lieu',
+                                           ['id' => 12,
+                                            'libelle' => 'Salle reunion AFI']);
+
+    $this->_learn_java = $this->fixture('Class_Activity',
+                                        ['id' => 3,
+                                         'libelle' => 'Learn Java',
+                                         'description' => 'Here you will learn some old and boring stuff']);
+
+    $this->fixture('Class_SessionActivity',
+                   ['id' => 32,
+                    'activity' => $this->_learn_java,
+                    'date_debut' => '2012-03-27',
+                    'date_fin' => '2012-03-29',
+                    'effectif_min' => 5,
+                    'effectif_max' => 25,
+                    'effectif_child_min' => 1,
+                    'effectif_child_max' => 5,
+                    'age_child_max' => 5,
+                    'effectif_inscription_max' => 2,
+                    'effectif_inscription_child_max' => 2,
+                    'duree'=> 8,
+                    'contenu' => 'Intro à la syntaxe',
+                    'horaires' => '9h - 12h, 13h - 18h',
+                    'lieu' => $this->_salle_reunion,
+                    'date_limite_inscription'=>'2012-03-05',
+                    'intervenants' => [$this->_prof_laurent],
+                   ]);
+
+    $inscription = $this->fixture('Class_SessionActivityInscription',
+                                  ['id' => 165,
+                                   'stagiaire_id' => 5,
+                                   'session_activity_id' => 32,
+                                   'adults' => 1,
+                                   'children' => 2,
+                                  ]);
+
+    Class_SessionActivity::find(32)
+      ->setSessionActivityInscriptions([$inscription])
+      ->assertSave();
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasSubscribeTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)->resetAttendees();
+    $this->dispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsSessionNavigation() {
+    $this->assertXPathContentContains('//div[contains(@class, "activities")]',
+                                      'Learn Java');
+  }
+
+
+  /** @test */
+  public function inputAdultsShouldBePresent() {
+    $this->assertXPath('//input[@name="adults"]');
+  }
+
+
+  /** @test */
+  public function inputChildrenShouldBePresent() {
+    $this->assertXPath('//input[@name="children"]');
+  }
+
+
+  /** @test */
+  public function backButtonUrlShouldContainsSessionActivity32() {
+    $this->assertXPath('//button[contains(@data-url, "/admin/session-activity/inscriptions/id/32")]');
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasSubscribePopupTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10/render/popup');
+  }
+
+
+  /** @test */
+  public function popupShouldNotContainsSessionNavigation() {
+    $this->assertNotXPathContentContains('//div[contains(@class, "activities")]',
+                                         'Learn Java');
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasPostTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)->resetAttendees();
+    $this->postDispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10',
+                        ['adults' => 1,
+                         'children' => 2]);
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivityInscriptions() {
+    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
+  }
+
+
+  /** @test */
+  public function pistacheShouldBeSubscribed() {
+    $this
+      ->assertNotNull(Class_SessionActivityInscription::findFirstBy(['stagiaire_id' => 10,
+                                                                     'session_activity_id' => 32]));
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasPostMaxOverloadTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)->resetAttendees();
+    $this->postDispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10',
+                        ['adults' => 3,
+                         'children' => 3]);
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivityInscriptions() {
+    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
+  }
+
+
+  /** @test */
+  public function pistacheShouldBeSubscribed() {
+    $this
+      ->assertNotNull(Class_SessionActivityInscription::findFirstBy(['stagiaire_id' => 10,
+                                                                     'session_activity_id' => 32,
+                                                                     'adults' => 3,
+                                                                     'children' => 3]));
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasEditTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)->resetAttendees();
+    $this->dispatch('/admin/session-activity-inscription/edit/id/165');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsSessionNavigation() {
+    $this->assertXPathContentContains('//div[contains(@class, "activities")]',
+                                      'Learn Java');
+  }
+
+
+  /** @test */
+  public function inputAdultsShouldBePresent() {
+    $this->assertXPath('//input[@name="adults"][@value="1"]');
+  }
+
+
+  /** @test */
+  public function inputChildrenShouldBePresent() {
+    $this->assertXPath('//input[@name="children"][@value="2"]');
+  }
+
+
+  /** @test */
+  public function backButtonUrlShouldContainsSessionActivity32() {
+    $this->assertXPath('//button[contains(@data-url, "/admin/session-activity/inscriptions/id/32")]');
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasEditPostTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/admin/session-activity-inscription/edit/id/165',
+                        ['adults' => 2,
+                         'children' => 2]);
+
+    Class_SessionActivityInscription::clearCache();
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivityInscriptions() {
+    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
+  }
+
+
+  /** @test */
+  public function patRegistrationAdultsShouldBecome2() {
+    $this->assertEquals(2, Class_SessionActivityInscription::find(165)->getAdults());
+  }
+
+
+  /** @test */
+  public function patRegistrationChildrenShouldBecome2() {
+    $this->assertEquals(2, Class_SessionActivityInscription::find(165)->getChildren());
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasEditPostOverloadTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->postDispatch('/admin/session-activity-inscription/edit/id/165',
+                        ['adults' => 3,
+                         'children' => 3]);
+
+    Class_SessionActivityInscription::clearCache();
+  }
+
+
+  /** @test */
+  public function shouldRedirectToActivityInscriptions() {
+    $this->assertRedirectTo('/admin/session-activity/inscriptions/id/32');
+  }
+
+
+  /** @test */
+  public function patRegistrationAdultsShouldBecome3() {
+    $this->assertEquals(3, Class_SessionActivityInscription::find(165)->getAdults());
+  }
+
+
+  /** @test */
+  public function patRegistrationChildrenShouldBecome3() {
+    $this->assertEquals(3, Class_SessionActivityInscription::find(165)->getChildren());
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasEditFullChildrenTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)
+      ->resetAttendees()
+      ->setEffectifChildMax(2);
+    $this->dispatch('/admin/session-activity-inscription/edit/id/165');
+  }
+
+
+  /** @test */
+  public function inputAdultsShouldBePresent() {
+    $this->assertXPath('//input[@name="adults"][@value="1"]');
+  }
+
+
+  /** @test */
+  public function inputChildrenShouldBePresent() {
+    $this->assertXPath('//input[@name="children"][@value="2"]');
+  }
+}
+
+
+
+
+class Activities_AdminControllerInscriptionWithQuotasSubscribeFullChildrenTest
+  extends AdminControllerInscriptionWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)
+      ->resetAttendees()
+      ->setEffectifChildMax(2);
+    $this->dispatch('/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10');
+  }
+
+
+  /** @test */
+  public function pageShouldContainNoMoreChildrenRoomMessage() {
+    $this->assertXPathContentContains('//div', 'Aucune place enfant disponible');
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Activities/AdminControllerWithQuotasTest.php b/tests/scenarios/Activities/AdminControllerWithQuotasTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ba87312794057089eb838bfa8709a1ff26cd237a
--- /dev/null
+++ b/tests/scenarios/Activities/AdminControllerWithQuotasTest.php
@@ -0,0 +1,363 @@
+<?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
+ */
+
+
+abstract class AdminControllerWithQuotasTestCase extends Admin_AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_group_profs,
+    $_groupe_stagiaires,
+    $_prof_laurent,
+    $_amandine,
+    $_patrick,
+    $_salle_reunion,
+    $_learn_java,
+    $_legacy_registration;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_legacy_registration = $this->fixture('Class_SessionActivityInscription',
+                                          ['id' => 38839,
+                                           'session_activity_id' => 32,
+                                           'stagiaire_id' => 999838]);
+
+    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', '1');
+
+    $this->_group_profs = $this->fixture('Class_UserGroup',
+                                         ['id' => 344,
+                                          'libelle' => 'Profs',
+                                          'rights' => [Class_UserGroup::RIGHT_DIRIGER_ACTIVITY]]);
+
+    $this->_groupe_stagiaires = $this->fixture('Class_UserGroup',
+                                               ['id' => 12,
+                                                'libelle' => 'Stagiaires',
+                                                'rights' => [Class_UserGroup::RIGHT_SUIVRE_ACTIVITY]]);
+
+    $this->_prof_laurent = $this->fixture('Class_users',
+                                          ['id' => 34,
+                                           'nom' => 'Laffont',
+                                           'prenom' => 'Laurent',
+                                           'login' => 'lla',
+                                           'password' => 'pwd',
+                                           'user_groups' => [$this->_group_profs]]);
+
+    $this->_amandine = $this->fixture('Class_Users',
+                                      ['id' => 10,
+                                       'nom' => 'Pistache',
+                                       'prenom' => 'Amandine',
+                                       'login' => 'Amd',
+                                       'password' => 'fx9k',
+                                       'mail' => 'pist@che.io',
+                                       'user_groups' => [$this->_groupe_stagiaires]]);
+
+    $this->_patrick = $this->fixture('Class_Users',
+                                     ['id' => 5,
+                                      'id_site' => 12,
+                                      'login' => 'Pat',
+                                      'password' => '123',
+                                      'prenom' => 'Patrick',
+                                      'nom' => 'Barroca',
+                                      'mail' => 'user@server.org',
+                                      'user_groups' => [$this->_groupe_stagiaires]]);
+
+    $this->_salle_reunion = $this->fixture('Class_Lieu',
+                                           ['id' => 12,
+                                            'libelle' => 'Salle reunion AFI']);
+
+    $this->_learn_java = $this->fixture('Class_Activity',
+                                        ['id' => 3,
+                                         'libelle' => 'Learn Java',
+                                         'description' => 'Here you will learn some old and boring stuff']);
+
+    $this->fixture('Class_SessionActivity',
+                   ['id' => 32,
+                    'activity' => $this->_learn_java,
+                    'date_debut' => '2012-03-27',
+                    'date_fin' => '2012-03-29',
+                    'effectif_min' => 5,
+                    'effectif_max' => 25,
+                    'effectif_child_min' => 1,
+                    'effectif_child_max' => 5,
+                    'age_child_max' => 5,
+                    'effectif_inscription_max' => 2,
+                    'effectif_inscription_child_max' => 3,
+                    'duree'=> 8,
+                    'contenu' => 'Intro à la syntaxe',
+                    'horaires' => '9h - 12h, 13h - 18h',
+                    'lieu' => $this->_salle_reunion,
+                    'date_limite_inscription'=>'2012-03-05',
+                    'intervenants' => [$this->_prof_laurent],
+                   ]);
+
+    $inscription = $this->fixture('Class_SessionActivityInscription',
+                                  ['id' => 165,
+                                   'stagiaire_id' => 5,
+                                   'session_activity_id' => 32,
+                                   'children' => 2,
+                                   'adults' => 1,
+                                  ]);
+
+    Class_SessionActivity::find(32)
+      ->setSessionActivityInscriptions([$inscription])
+      ->assertSave();
+
+    Zend_Registry::set('sql',
+                       $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->with('select user_id as id from user_group_memberships where user_group_id=12')
+                       ->answers([['id' => 5]])
+
+                       ->whenCalled('fetchAll')
+                       ->with('select user_id as id from user_group_memberships where user_group_id=344')
+                       ->answers([])
+
+                       ->whenCalled('fetchAll')
+                       ->with('select stagiaire_id as id from session_activity_inscriptions where session_activity_id=32')
+                       ->answers([['id' => 5]])
+    );
+  }
+}
+
+
+
+
+class Activities_AdminControllerWithQuotasInscriptionTest extends AdminControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/session-activity/inscriptions/id/32/search_session_activity_subscription_status/all');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsCounters() {
+    $this->assertXPathContentContains('//a[contains(@href, "/session-activity/inscriptions/id/32")]',
+                                      '3 / 6-30');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsParticipantsColumn() {
+    $this->assertXPathContentContains('//th', 'Participants');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsAttendees() {
+    $this->assertXPathContentContains('//td', '1 adulte, 2 enfants');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsLinkToEditAttendees() {
+    $this->assertXPath('//a[contains(@href, "/admin/session-activity-inscription/edit/id/165")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsLinkToSubscribePistache() {
+    $this->assertXPath('//a[contains(@href, "/admin/session-activity-inscription/subscribe/id/32/stagiaire_id/10")][not(@onclick)][@data-popup]');
+  }
+}
+
+
+
+
+class Activities_AdminControllerWithQuotasInscriptionLegacyTest
+  extends AdminControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)
+      ->addSessionActivityInscription($this->_legacy_registration)
+      ->resetAttendees()
+      ->assertSave();
+    $this->dispatch('/admin/session-activity/inscriptions/id/32/search_session_activity_subscription_status/all');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsCountersWithLegacyRegistration() {
+    $this->assertXPathContentContains('//a[contains(@href, "/session-activity/inscriptions/id/32")]',
+                                      '4 / 6-30');
+  }
+}
+
+
+
+
+class Activities_AdminControllerWithQuotasInscriptionOngletEffectifTest
+  extends AdminControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/session-activity/inscriptions/id/32/search_session_activity_subscription_status/all');
+  }
+
+
+  public function quotasElements() {
+    return [['effectif_min'],
+            ['effectif_child_min'],
+            ['effectif_max'],
+            ['effectif_child_max'],
+            ['effectif_inscription_max'],
+            ['effectif_inscription_child_max'],
+            ['age_child_max']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider quotasElements
+   */
+  public function withQuotasFormShouldContainsElement($element) {
+    $this->dispatch('/admin/session-activity/edit/id/32');
+    $this->assertXPath('//fieldset[@id="fieldset-quotas"]//input[@name="'
+                       . $element . '"][@required="required"][@type="number"][@min="0"]');
+  }
+}
+
+
+
+
+class Activities_AdminControllerWithQuotasInscriptionInvalidPostTest
+  extends AdminControllerWithQuotasTestCase {
+
+  protected $_post_values = ['effectif_child_min' => 'chaine',
+                             'effectif_child_max' => 'chaine',
+                             'age_child_max' => 'chaine',
+                             'effectif_min' => 'chaine',
+                             'effectif_max' => 'chaine',
+                             'effectif_inscription_max' => 'chaine',
+                             'effectif_inscription_child_max' => 'chaine'];
+
+
+  public function setUp() {
+    parent::setUp();
+  }
+
+
+  public function quotasElements() {
+    return array_map(function($name) { return [$name]; },
+                     array_keys($this->_post_values));
+  }
+
+
+  /**
+   * @test
+   * @dataProvider quotasElements
+   */
+  public function pageShouldContainErrorNotIntegerFor($element) {
+    $this->postDispatch('admin/session-activity/edit/id/32', $this->_post_values);
+    $this->assertXPathContentContains('//ul[preceding-sibling::input[@name="' . $element . '"]][@class="errors"]',
+                                      "'chaine' n'est pas un nombre entier");
+  }
+
+
+  /** @test */
+  public function minGreaterThanMaxShouldDisplayError() {
+    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_child_min' => 30,
+                                                              'effectif_child_max' => 10]);
+    $this->assertXPathContentContains('//ul[@class="errors"]',
+                                      'L\'effectif enfants maximum doit être supérieur ou égal à l\'effectif enfants minimum');
+  }
+
+
+  /** @test */
+  public function inscriptionMaxGreaterThanMaxShouldDisplayError() {
+    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_inscription_max' => 30,
+                                                              'effectif_max' => 10]);
+    $this->assertXPathContentContains('//ul[@class="errors"]',
+                                      'L\'effectif maximum doit être supérieur ou égal à l\'effectif maximum par inscription');
+  }
+
+
+  /** @test */
+  public function inscriptionChildMaxGreaterThanMaxShouldDisplayError() {
+    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_inscription_child_max' => 10,
+                                                              'effectif_child_max' => 5]);
+    $this->assertXPathContentContains('//ul[@class="errors"]',
+                                      'L\'effectif enfants maximum doit être supérieur ou égal à l\'effectif enfants maximum par inscription');
+  }
+
+
+  /** @test */
+  public function inscriptionWithoutAgeChilMaxShouldBerequired() {
+    $this->postDispatch('admin/session-activity/edit/id/32', ['effectif_inscription_child_max' => 5,
+                                                              'effectif_child_max' => 10,
+                                                              'age_child_max' => 0]);
+    $this->assertXPathContentContains('//ul[@class="errors"]',
+                                      'L\'âge maximum pour les enfants doit être renseigné');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsInscriptionMaxAdultesRequired() {
+    $this->postDispatch('/admin/session-activity/edit/id/32',
+                        ['effectif_inscription_max' => 0]);
+    $this->assertXPathContentContains('//ul[@class="errors"]//li',
+                                      "Adultes maximum par inscription doit être renseigné");
+  }
+
+
+  /** @test */
+  public function pageShouldContainsInscriptionMaxEnfantsRequired() {
+    $this->postDispatch('/admin/session-activity/edit/id/32',
+                        ['effectif_inscription_child_max' => 0]);
+    $this->assertXPathContentContains('//ul[@class="errors"]//li',
+                                      "Enfants maximum par inscription doit être renseigné");
+  }
+}
+
+
+
+
+class Activities_AdminControllerWithQuotasInscriptionExportTest
+  extends AdminControllerWithQuotasTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_SessionActivity::find(32)
+      ->resetAttendees()
+      ->assertSave();
+    $this->dispatch('/admin/session-activity/exportinscriptions/id/32');
+  }
+
+
+  /** @test */
+  public function exportShouldContainsEffectif6_30() {
+    $this->assertContains('Effectif;6-30', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function exportShouldContainsAgeMaxChildren() {
+    $this->assertContains('Enfants (âge max 5)', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function exportShouldContainsAdultsAndChildrenNumber() {
+    $this->assertContains('Barroca;Patrick;Pat;user@server.org;;1;2', $this->_response->getBody());
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Activities/BatchTest.php b/tests/scenarios/Activities/BatchTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d89505bab283728512a152f6f48a91d904b65e3a
--- /dev/null
+++ b/tests/scenarios/Activities/BatchTest.php
@@ -0,0 +1,232 @@
+<?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
+ */
+
+
+class Activities_BatchActivationTest extends ModelTestCase {
+
+  /** @test */
+  public function shouldNotBeEnabled() {
+    $this->assertFalse((new Class_Batch_ActivitiesNotifications)->isEnabled());
+  }
+
+
+  /** @test */
+  public function shouldBeEnabled() {
+    Class_AdminVar::set('ACTIVITY', 1);
+    $this->assertTrue((new Class_Batch_ActivitiesNotifications)->isEnabled());
+  }
+}
+
+
+
+
+class Activities_BatchAdminTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('ACTIVITY', 1);
+    $this->dispatch('/admin/batch');
+  }
+
+
+  /** @test */
+  public function pagheShouldContainsNotifierLesProchainesActivites() {
+    $this->assertXPathContentContains('//td', 'Notifier les prochaines activités');
+  }
+}
+
+
+
+
+abstract class Activities_BatchRunTestCase extends ModelTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_mock_transport,
+    $_sent_mails,
+    $_log_output = '';
+
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_Profil::setCurrentProfil($this->fixture('Class_Profil',
+                                                  ['id' => 1,
+                                                   'mail_site' => 'from@server.com']));
+
+    Class_AdminVar::set('ACTIVITY', 1);
+    Class_AdminVar::set('ACTIVITY_NOTIFICATION_DELAY', 3);
+    Class_AdminVar::set('ACTIVITY_NOTIFICATION_SUBJECT', 'Rappel : {session.libelle_activity}');
+    Class_SessionActivity_Notification::setTimeSource(new TimeSourceForTest('2021-02-08 15:38:00'));
+
+    $this->_mock_transport = new MockMailTransport();
+    Zend_Mail::setDefaultTransport($this->_mock_transport);
+
+    $this->_prepare();
+    Class_SessionActivity::clearCache();
+    $this->onLoaderOfModel('Class_SessionActivity');
+
+    $log_append = function($message) {
+      $this->_log_output .= $message;
+    };
+
+    (new Class_Batch_ActivitiesNotifications)
+      ->setLogger($this->mock()
+                  ->whenCalled('log')
+                  ->willDo($log_append)
+
+                  ->whenCalled('error')
+                  ->willDo($log_append))
+      ->run();
+
+    $this->_sent_mails = $this->_mock_transport->getSentMails();
+  }
+
+
+  protected function _prepare() {
+    $stagiaire_group = $this->fixture('Class_UserGroup',['id' => 23])
+                            ->addRightSuivreActivity();
+
+    $this->fixture('Class_SessionActivity',
+                   ['id' => 22,
+                    'date_debut' => '2021-02-11 00:00:00',
+                    'date_limite_inscription' => '2021-02-11',
+                    'activity' => $this->fixture('Class_Activity',
+                                                 ['id' => 12,
+                                                  'libelle' => 'Stephanie\'s Birthday Party']),
+                    'stagiaires' => [$this->fixture('Class_Users',
+                                                    ['id' => 9938,
+                                                     'login' => 'pistache',
+                                                     'password' => 'OMG',
+                                                     'mail' => 'pist@a.ch',
+                                                     'user_groups' => [$stagiaire_group]])],
+                   ]);
+  }
+
+
+  public function tearDown() {
+    $this->_mock_transport = null;
+    parent::tearDown();
+  }
+}
+
+
+
+
+class Activities_BatchRunTest extends Activities_BatchRunTestCase {
+
+  /** @test */
+  public function shouldHaveCalledFindAllByWithDelay3() {
+    $this->assertContains('DATEDIFF(date_debut, NOW()) <= 3',
+                          Class_SessionActivity::getFirstAttributeForLastCallOn('findAllBy')['where']);
+  }
+
+
+  /** @test */
+  public function shouldHaveSentEmail() {
+    $this->assertEquals(1, $this->_mock_transport->count());
+  }
+
+
+  /** @test */
+  public function mailFromShouldBeFromAtServerDotCom() {
+    $this->assertEquals('from@server.com', $this->_sent_mails[0]->getFrom());
+  }
+
+
+  /** @test */
+  public function mailSubjectShouldBeRappelStephaniesBirthdayParty() {
+    $this->assertEquals('Rappel : Stephanie\'s Birthday Party',
+                        $this->_sent_mails[0]->getSubject());
+  }
+
+
+  /** @test */
+  public function shouldLogOneActivityOneNotification() {
+    $this->assertContains('Stephanie\'s Birthday Party : 1 rappel(s) envoyé(s)',
+                          $this->_log_output);
+  }
+
+
+  /** @test */
+  public function mailContenuShouldBePistacheActivityStephanie11_02() {
+    $this->assertEquals('<p>Bonjour pistache,</p><p>L\'activité Stephanie\'s Birthday Party à laquelle vous êtes inscrit, commencera le 11 février 2021.</p><p>Cordialement</p>',
+                        quoted_printable_decode($this->_sent_mails[0]->getBodyHtml(true)));
+  }
+
+
+  /** @test */
+  public function pistacheInscriptionShouldHaveNotifiedAt2021_02_08_153800() {
+    Class_SessionActivityInscription::clearCache();
+    $this->assertEquals('2021-02-08 15:38:00',
+                        Class_SessionActivityInscription::findFirstBy(['stagiaire_id' => '9938',
+                                                                       'session_activity_id' => 22])
+                        ->getNotifiedAt());
+  }
+}
+
+
+
+
+class Activities_BatchRunAlreadySentTest extends Activities_BatchRunTestCase {
+
+  protected function _prepare() {
+    parent::_prepare();
+    Class_SessionActivityInscription::findFirstBy(['stagiaire_id' => '9938',
+                                                   'session_activity_id' => 22])
+      ->setNotifiedAt('2021-02-09 00:00:00')
+      ->assertSave();
+  }
+
+
+  /** @test */
+  public function shouldHaveNotSentEmail() {
+    $this->assertEquals(0, $this->_mock_transport->count());
+  }
+}
+
+
+
+
+class Activities_BatchRunWithMailErrorTest extends Activities_BatchRunTestCase {
+  protected function _prepare() {
+    parent::_prepare();
+    $this->_mock_transport
+      ->onSendDo(function()
+                 {
+                   throw new RuntimeException('OUPS !, I did it again');
+                 });
+  }
+
+
+  /** @test */
+  public function shouldLogNoMailSent() {
+    $this->assertContains('Stephanie\'s Birthday Party : 0 rappel(s) envoyé(s)',
+                          $this->_log_output);
+  }
+
+
+  /** @test */
+  public function shouldLogError() {
+      $this->assertContains('OUPS !, I did it again',
+                          $this->_log_output);
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Activities/WidgetWithQuotasTest.php b/tests/scenarios/Activities/WidgetWithQuotasTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2eeff4650e4663bcb0b32fb1ca1fb663c81d9790
--- /dev/null
+++ b/tests/scenarios/Activities/WidgetWithQuotasTest.php
@@ -0,0 +1,111 @@
+<?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
+ */
+
+
+class Activities_WidgetWithQuotasDisplayTest extends ViewHelperTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+  public function setup() {
+    parent::setup();
+    Class_AdminVar::set('ACTIVITY',1);
+    Class_AdminVar::set('ACTIVITY_SESSION_QUOTAS', 1);
+    $time_source = new TimeSourceForTest('2014-05-06 14:00:00');
+    Class_Activity::setTimeSource($time_source);
+    Class_SessionActivity::setTimeSource($time_source);
+    $this->_createActivity(1, '2016-04', '2016-05');
+    $this->_createActivity(3, '2010-04', '2010-05');
+    $this->_createActivity(5, '2016-10', '2016-02');
+
+    $this->_user_group = $this->fixture('Class_UserGroup',
+                                        ['id' => 3,
+                                         'libelle'=> 'Stagiaires',
+                                         'group_type' => Class_UserGroup::TYPE_MANUAL,
+                                         'rights' => [Class_UserGroup::RIGHT_SUIVRE_ACTIVITY],
+                                         'users' => [Class_Users::find(1)]]);
+    $this->fixture('Class_Users',
+                   ['id' => 1,
+                    'login' => 'stagiaire2',
+                    'user_groups' => [$this->_user_group],
+                    'password' => 'pwd',
+                    'id_abon' => 438]);
+
+    Class_SessionActivity::find(2)
+      ->setSessionActivityInscriptions([$this->fixture('Class_SessionActivityInscription',
+                                                       ['id' => 2,
+                                                        'stagiaire' => Class_Users::find(1),
+                                                        'session_activity' => Class_SessionActivity::find(2),
+                                                        'adults' => 1,
+                                                        'children' => 1])])
+      ->save();
+
+    $this->_renderWidget();
+  }
+
+
+  protected function _createActivity($id, $date_session, $date_session2) {
+    $activity = $this->fixture('Class_Activity',
+                               ['id' => $id,
+                                'libelle' => 'Activity numero ' . $id,
+                                'visible' => true]);
+
+    $activity->setSessions([$this->fixture('Class_SessionActivity',
+                                           ['id' => $id + 1,
+                                            'activity' => $activity,
+                                            'date_debut' => $date_session . '-01',
+                                            'date_limite_inscription' => $date_session . '-01',
+                                            'date_fin' => $date_session . '-04',
+                                            'effectif_inscription_max' => 2,
+                                            'effectif_inscription_child_max' => 2,
+                                            'effectif_child_min' => 0,
+                                            'effectif_child_max' => 2,
+                                            'age_child_max' => 12])
+                            ])
+             ->assertSave();
+  }
+
+
+  protected function _renderWidget() {
+    $preferences = ['division' => 1,
+                    'type_module' => 'ACTIVITIES_WIDGET',
+                    'preferences' => ['selected_activities' => '3-1-5',
+                                      'display_order' => 'creation',
+                                      'display_group_mode' => 'ByYear',
+                                      'display_mode' => 'Block']];
+    $helper = new ZendAfi_View_Helper_Accueil_ActivitiesWidget('ACTIVITIES_WIDGET', $preferences);
+    $helper->setView($this->view);
+    $this->_html = $helper->getBoite();
+  }
+
+
+  /** @test */
+  public function activitieShouldBeDisplayedWithNombreInscrits2() {
+    $this->assertXPathContentContains($this->_html,
+                                      '//dd[@class="training_contributors"][preceding-sibling::dt[text()="Nombre d\'inscrits"]]',
+                                      '2');
+  }
+
+
+  /** @test */
+  public function activitieShouldBeDisplayedByYear() {
+    $this->assertXPathContentContains($this->_html, '//h2[1]', '2010');
+  }
+}
diff --git a/tests/scenarios/NoveltyFacet/NoveltyFacetTest.php b/tests/scenarios/NoveltyFacet/NoveltyFacetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a53cbb29bb963345fcb5b7c18c3f2c74d73915ad
--- /dev/null
+++ b/tests/scenarios/NoveltyFacet/NoveltyFacetTest.php
@@ -0,0 +1,199 @@
+<?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
+ */
+
+
+class NoveltyFacetSearchResultConfigTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+  public function setUp() {
+    parent::setUp();
+    Class_CodifThesaurus::ensureRecordNovelty();
+  }
+
+
+  /** @test */
+  public function noveltyFacetShouldBeAvailableInHistoric() {
+    $this->dispatch('/admin/modules/recherche/config/site/type_module/recherche/id_profil/1/action1/resultat/action2/simple');
+    $this->_assertNoveltyIsAvailable();
+  }
+
+
+  /** @test */
+  public function noveltyFacetShouldBeAvailableInTemplates() {
+    $id = (new Intonation_Template)->tryOn(Class_Profil::getCurrentProfil());
+    $this->dispatch('/admin/widget/edit-action/id/recherche_resultat_simple/id_profil/' . $id);
+    $this->_assertNoveltyIsAvailable();
+  }
+
+
+  protected function _assertNoveltyIsAvailable() {
+    $this->assertXPathContentContains('//div[@data-heading="facettes"]//li[@data-id="HNRNR"]',
+                                      'Nouveauté');
+  }
+}
+
+
+
+
+class NoveltyFacetDomainEditTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    Class_CodifThesaurus::ensureRecordNovelty();
+    $this->onLoaderOfModel('Class_CodifThesaurus');
+    $this->dispatch('/admin/catalogue/add');
+  }
+
+
+  /** @test */
+  public function pageShouldNotContainRecordNoveltyInput() {
+    $this->assertContains('id_thesaurus not in ("NRNR", "NNNN", "NANA", "CCCC")',
+                          Class_CodifThesaurus::getFirstAttributeForLastCallOn('findAllBy')['where']);
+  }
+}
+
+
+
+
+class NoveltyFacetSearchResultSimpleTest extends AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    Class_CodifThesaurus::ensureRecordNovelty();
+
+    $this->fixture('Class_Notice', ['id' => 2]);
+
+    Class_Profil::getCurrentProfil()
+      ->setModulePreference('recherche', 'resultatsimple', 'facettes_codes', 'HNRNR');
+  }
+
+
+  public function facets() {
+    return [['HNRNR0001', 'Oui'],
+            ['HNRNR0002', 'Non']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider facets
+   */
+  public function withFacetPageShouldContainLinkToFilterByNovelty($facet, $label) {
+    Zend_Registry::set('sql',
+                       $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->with('select id_notice, facettes from notices Where type=1', true, false)
+                       ->answers([[2, $facet]]));
+
+    $this->dispatch('/opac/recherche/simple/expressionRecherche/*');
+    $this->assertXPathContentContains('//a[contains(@href, "/facette/'. $facet .'")]', $label);
+  }
+}
+
+
+
+
+abstract class NoveltyFacetRecordUpdateTestCase extends ModelTestCase {
+  protected $_record;
+
+  public function setUp() {
+    parent::setUp();
+    Class_CodifThesaurus::ensureRecordNovelty();
+    $this->_record = $this->fixture('Class_Notice',
+                                    ['id' => 2,
+                                     'type_doc' => Class_TypeDoc::LIVRE]);
+    Class_Exemplaire::setTimeSource(new TimeSourceForTest('2021-01-14 16:55:16'));
+  }
+
+
+  public function tearDown() {
+    Class_Exemplaire::setTimeSource(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function notNoveltyShouldBecomeNovelty() {
+    $this->_recordFacetAndNovelty('T1 HNRNR0002', '2021-02-19');
+    $this->_updateRecord();
+    $this->assertContains('HNRNR0001', $this->_record->getFacettes());
+    $this->assertNotContains('HNRNR0002', $this->_record->getFacettes());
+  }
+
+
+  /** @test */
+  public function noveltyShouldBecomeNotNovelty() {
+    $this->_recordFacetAndNovelty('T1 HNRNR0001', '');
+    $this->_updateRecord();
+    $this->assertContains('HNRNR0002', $this->_record->getFacettes());
+    $this->assertNotContains('HNRNR0001', $this->_record->getFacettes());
+  }
+
+
+  protected function _recordFacetAndNovelty($facets, $novelty_date) {
+    $this->_record
+      ->setFacettes($facets)
+      ->setExemplaires([$this->fixture('Class_Exemplaire',
+                                       ['id' => 3,
+                                        'date_nouveaute' => $novelty_date])])
+      ->assertSave();
+  }
+
+
+  protected function _updateRecord() {
+  }
+}
+
+
+
+
+class NoveltyFacetUpdateFacetsFromExemplairesTest extends NoveltyFacetRecordUpdateTestCase {
+  protected function _updateRecord() {
+    $this->_record->updateFacetsFromExemplaires();
+  }
+}
+
+
+
+
+class NoveltyFacetUpdateNoveltyFacetsTest extends NoveltyFacetRecordUpdateTestCase {
+  protected function _updateRecord() {
+    $this->_record->updateNoveltyFacets();
+  }
+}
+
+
+
+
+class NoveltyFacetIndexTest extends ModelTestCase {
+  /** @test */
+  public function findAllByShouldContainRecordNoveltyFacet() {
+    Class_CodifThesaurus::ensureRecordNovelty();
+    $this->onLoaderOfModel('Class_Notice');
+    Class_Notice::indexNoveltyFacets();
+    $this->assertContains('+(HNNNN* HNANA* HNRNR0001)',
+                          Class_Notice::getFirstAttributeForLastCallOn('findAllBy')['where']);
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/PnbDilicom/PnbDilicomTest.php b/tests/scenarios/PnbDilicom/PnbDilicomTest.php
index 4f46447088ce73cc5dc3f9ea564b1eb4e2aecf02..3d172cc4999cf7103efdfb3a72bd114814449347 100644
--- a/tests/scenarios/PnbDilicom/PnbDilicomTest.php
+++ b/tests/scenarios/PnbDilicom/PnbDilicomTest.php
@@ -2933,10 +2933,18 @@ abstract class PnbDilicomAdminAlbumControllerTestCase extends Admin_AbstractCont
                    ['id' => 23,
                     'libelle' => 'Heavy Metal']);
 
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 24,
+                    'libelle' => 'Death Metal']);
+
     $this->fixture('Class_CodifSection',
                    ['id' => 33,
                     'libelle' => 'Espace métal']);
 
+    $this->fixture('Class_CodifSection',
+                   ['id' => 34,
+                    'libelle' => 'Espace musique']);
+
     $fondu = $this->fixture('Class_AlbumCategorie',
                             ['id' => 1301,
                              'libelle' => 'Fondu']);
@@ -2946,8 +2954,8 @@ abstract class PnbDilicomAdminAlbumControllerTestCase extends Admin_AbstractCont
                     'id_origine' => 'Dilicom-3663608260879',
                     'titre' => 'Hell is from here to eternity',
                     'type_doc_id' => Class_TypeDoc::DILICOM,
-                    'genre' => '23',
-                    'sections' => '33',
+                    'genre' => '23;24',
+                    'sections' => '33;34',
                     'categorie' => $fondu])
          ->addAuthor('Iron Maiden')
          ->addEditor('EMI')
@@ -3034,6 +3042,7 @@ class PnbDilicomAdminAlbumControllerImportDilicomTest extends PnbDilicomAdminAlb
 
   public function setUp() {
     parent::setUp();
+    Class_AdminVar::set('DILICOM_PNB_BOARD_DISPLAY_SECTION', true);
     $this->dispatch('/admin/album/dilicom', true);
   }
 
@@ -3087,6 +3096,24 @@ class PnbDilicomAdminAlbumControllerImportDilicomTest extends PnbDilicomAdminAlb
   }
 
 
+  /** @test */
+  public function sectionsShouldBeDisplayed() {
+    $this->assertXPathContentContains('//table//tr/td', 'Heavy Metal, Death Metal');
+  }
+
+
+  /** @test */
+  public function genreShouldBeDisplayed() {
+    $this->assertXPathContentContains('//table//tr/td', 'Espace métal, Espace musique');
+  }
+
+
+  /** @test */
+  public function multiselectBasketShouldBeDisplayedWithAlbumId() {
+    $this->assertXPath('//a[contains(@href,"/admin/album/add-model-to-selection/select_id/23")]',$this->_response->getBody());
+  }
+
+
   /** @test */
   public function nbOfHoldsShouldBeSix() {
     $this->assertXPath('//table//tr[3]/td[text()="6"]');
diff --git a/tests/scenarios/RendezVous/RendezVousAdminTest.php b/tests/scenarios/RendezVous/RendezVousAdminTest.php
index 19decf5f40c49267b1e79ab4178278a4e49b2754..1a739d6cfa25c42f3ab1f089572f71dfcd4ed8c8 100644
--- a/tests/scenarios/RendezVous/RendezVousAdminTest.php
+++ b/tests/scenarios/RendezVous/RendezVousAdminTest.php
@@ -921,7 +921,7 @@ class RendezVousSendNotificationTest extends RendezVousNotificationTestCase {
 
 
   /** @test */
-  public function batchSendRendezVousShouldHaveSentOneEmail() {
+  public function batchSendRendezVousShouldHaveSentThreeEmail() {
     $this->assertEquals(3, $this->_mock_transport->count());
   }
 
diff --git a/tests/scenarios/Templates/PolygoneTemplateAbonneAgendaTest.php b/tests/scenarios/Templates/PolygoneTemplateAbonneAgendaTest.php
index a2739db47c327a174c860c28b8f7a0eec0438293..13d93977be2adee9a0b90339cbbac83e8561b89f 100644
--- a/tests/scenarios/Templates/PolygoneTemplateAbonneAgendaTest.php
+++ b/tests/scenarios/Templates/PolygoneTemplateAbonneAgendaTest.php
@@ -159,6 +159,7 @@ abstract class PolygoneTemplateAbonneAgendaTestCase extends AbstractControllerTe
                                                 'effectif_max' => 5,
                                                 'effectif_child_min' => 1,
                                                 'effectif_child_max' => 5,
+                                                'age_child_max' => 5,
                                                 'effectif_inscription_max' => 2,
                                                 'effectif_inscription_child_max' => 3,
                                                 'lieu' => $this->_gallice_cafe,
@@ -206,6 +207,7 @@ abstract class PolygoneTemplateAbonneAgendaTestCase extends AbstractControllerTe
                  'effectif_max' => 150,
                  'effectif_child_min' => 0,
                  'effectif_child_max' => 100,
+                 'age_child_max' => 5,
                  'effectif_inscription_max' => 10,
                  'effectif_inscription_child_max' => 5,
                  'duree' => 36,
@@ -536,8 +538,8 @@ class PolygoneTemplateAbonneAgendaDetailSessionWithRightRegisterTest
 
   /** @test */
   public function pageShouldContainsLinkToGererLesInscriptions() {
-    $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/31")][@target="_blank"]',
-                                      'Gérer les inscriptions');
+    $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/31/search_session_activity_subscription_status/all")][@target="_blank"]',
+                                      'Nouvelle inscription');
   }
 }
 
@@ -562,8 +564,8 @@ class PolygoneTemplateAbonneAgendaWithRightRegisterTest
 
   /** @test */
   public function pageShouldContainsLinkToGererLesInscriptions() {
-    $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/31")][@target="_blank"]',
-                                      'Gérer les inscriptions');
+    $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/31/search_session_activity_subscription_status/all")][@target="_blank"]',
+                                      'Nouvelle inscription');
   }
 }
 
diff --git a/tests/scenarios/Templates/TemplatesAgendaTest.php b/tests/scenarios/Templates/TemplatesAgendaTest.php
index 947be47b3194887227fcd3f0da819bb86220b679..509ef31b214ae50a959e51b39e71b80be9e54173 100644
--- a/tests/scenarios/Templates/TemplatesAgendaTest.php
+++ b/tests/scenarios/Templates/TemplatesAgendaTest.php
@@ -113,4 +113,11 @@ class TemplatesAgendaWidgetWithSessionActivityAndRightRegisterTest
     $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/12")][@target="_blank"]',
                                       'Gérer les inscriptions');
   }
+  
+  
+  /** @test */
+  public function pageShouldContainsLinkToNouvelleInscription() {
+    $this->assertXPathContentContains('//a[contains(@href, "admin/session-activity/inscriptions/id/12/search_session_activity_subscription_status/all")][@target="_blank"]',
+                                      'Nouvelle inscription');
+  }
 }
\ No newline at end of file