diff --git a/cosmogramme/cosmozend/application/modules/cosmo/controllers/DeleteItemsController.php b/cosmogramme/cosmozend/application/modules/cosmo/controllers/DeleteItemsController.php
new file mode 100644
index 0000000000000000000000000000000000000000..cd0a2d5ce388edd7213945e697996c106b53d517
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/controllers/DeleteItemsController.php
@@ -0,0 +1,95 @@
+<?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 Cosmo_DeleteItemsController extends ZendAfi_Controller_Action {
+
+  public function indexAction() {
+    $this->view->titre = $this->_('Suppression des notices ou des exemplaires');
+    $this->view->libraries = Class_IntBib::query()->order('id_bib')->fetchAll();
+  }
+
+
+  public function allDashboardAction() {
+    $this->view->titre = $this->_('Supprimer toutes les notices et tous les exemplaires');
+  }
+
+
+  public function libraryDashboardAction() {
+    $id_bib = $this->_getParam('id');
+    $bib_name = Class_IntBib::find($id_bib)->getNomCourt();
+
+    $this->view->id_bib = $id_bib;
+    $this->view->titre = $this->_("Supprimer tous les exemplaires de $bib_name");
+  }
+
+
+  public function allAction() {
+    session_write_close();
+
+    $this->view->titre = $this->_('Suppression des notices et tout le reste en cours');
+
+    if ( null === $step = $this->_getParam('step')) {
+      $this->_helper->notify($this->_('Impossible de supprimer les notices et tout le reste'));
+      $this->_redirectClose($this->_getReferer());
+      return;
+    }
+
+    $cleaner =
+      ((new Class_Cosmogramme_Cleaner($step, $this->_getParam('counter', 0)))
+       ->delete());
+
+    $this->view->content = $this->view->Cosmo_Cleaner($cleaner);
+
+    if ( 'ajax' !== $this->_getParam('render', ''))
+      return;
+
+    $this->_helper->getHelper('HTMLAjaxResponse')
+                  ->htmlAjaxResponseWithScript($this->view->content);
+  }
+
+
+  public function libraryAction() {
+    session_write_close();
+
+    $id_bib = $this->_getParam('id');
+    $step = $this->_getParam('step');
+    $count_items = $this->_getParam('count_items');
+    $count_records = $this->_getParam('count_succinct_records');
+    $counter = $this->_getParam('counter');
+    $limit = $this->_getParam('limit') ?? 0;
+
+    if ( null === ($id_bib = $this->_getParam('id', null))) {
+      $this->_helper->notify($this->_('Identifiant de bibliothèque manquant.'));
+      return $this->_redirectToReferer();
+    }
+
+    $cleaner = ((new Class_Cosmogramme_ItemsCleaner($id_bib, $step, $count_items, $count_records, $counter, $limit))
+      ->delete());
+
+    $this->view->content = $this->view->Cosmo_ItemsCleaner($cleaner);
+
+    if ( 'ajax' !== $this->_getParam('render', ''))
+      return;
+
+    $this->_helper->getHelper('HTMLAjaxResponse')
+                  ->htmlAjaxResponseWithScript($this->view->content);
+  }
+}
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/all-dashboard.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/all-dashboard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..14364c5de0a5ad3869b48c570135b26da30b5337
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/all-dashboard.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->Cosmo_DeleteItemsAllDashboard();
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/all.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/all.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..ff71d31e883048cb05ea7b632370dd47b1fe1517
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/all.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->content;
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/index.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/index.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..5d33d1409524b4a6b2769202a19b02ed01b85e37
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/index.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->Cosmo_DeleteItems();
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/library-dashboard.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/library-dashboard.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..afa99919ec8b1fac561d56acb62f8683fb998468
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/library-dashboard.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->Cosmo_DeleteItemsLibraryDashboard();
diff --git a/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/library.phtml b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/library.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..ff71d31e883048cb05ea7b632370dd47b1fe1517
--- /dev/null
+++ b/cosmogramme/cosmozend/application/modules/cosmo/views/scripts/delete-items/library.phtml
@@ -0,0 +1,2 @@
+<?php
+echo $this->content;
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DeleteItemsControllerTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DeleteItemsControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..99e855b09bdaae3b13b205e2f50cdd465f000fbe
--- /dev/null
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/DeleteItemsControllerTest.php
@@ -0,0 +1,617 @@
+<?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 Cosmo_DeleteItemsControllerIndexActionTest extends CosmoControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture(Class_IntBib::class,
+                   ['id' => 0,
+                    'nom_court' => 'Authume']);
+
+    $this->fixture(Class_IntBib::class,
+                   ['id' => 5,
+                    'nom_court' => 'Foucherans']);
+
+    $this->dispatch('/cosmo/delete-items/index');
+  }
+
+
+  /** @test */
+  public function titleShouldBeSuppressionDeNoticeOuDExemplaires() {
+    $this->assertXPathContentContains('//h1',
+                                      'Suppression des notices ou des exemplaires');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsButtonSupprimerToutesLesNoticesDeLaBase() {
+    $this->assertXPathContentContains('//button[@data-url="/cosmo/delete-items/all-dashboard"]', 'Supprimer toutes les notices de la base');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsDeletionNotice() {
+    $this->assertXPathContentContains('//p[@class="notice"]', 'Les boutons suivants vous permettent de supprimer les exemplaires d\'une bibliothèque.');
+  }
+
+
+  /** @test */
+  public function tableShouldContainsLibraryIdZero() {
+    $this->assertXPath('//table/tbody/tr[1]/td[1][contains(text(),"0")]');
+  }
+
+
+  /** @test */
+  public function tableShoudContainsLibraryAuthume() {
+    $this->assertXPathContentContains('//table/tbody/tr[1]/td[2]', 'Authume');
+  }
+
+
+  /** @test */
+  public function tableShouldContainsDeleteItemsLibraryIdZero() {
+    $this->assertXPath('//table/tbody/tr[1]/td[3]/a[@href="/cosmo/delete-items/library-dashboard/id/0"]/img[@title="Supprimer les exemplaires"]');
+  }
+
+
+  /** @test */
+  public function tableShouldContainsLibraryIdFive() {
+    $this->assertXPathContentContains('//table/tbody/tr[2]/td[1]', '5');
+  }
+
+
+  /** @test */
+  public function tableShouldContainsLibraryFocherans() {
+    $this->assertXPathContentContains('//table/tbody/tr[2]/td[2]', 'Foucherans');
+  }
+
+
+  /** @test */
+  public function tableShouldContainsDeleteItemsLibraryIdFive() {
+    $this->assertXPath('//table/tbody/tr[2]/td[3]/a[@href="/cosmo/delete-items/library-dashboard/id/5"]/img[@title="Supprimer les exemplaires"]');
+  }
+}
+
+
+
+
+abstract class Cosmo_DeleteItemsControllerAllTestCase extends CosmoControllerTestCase {
+
+  public function setup() {
+    parent::setUp();
+
+    $this->fixture(Class_Notice::class, ['id' => 1]);
+    $this->fixture(Class_Notice::class, ['id' => 2]);
+    $this->fixture(Class_Notice::class, ['id' => 3]);
+    $this->fixture(Class_Notice::class, ['id' => 4]);
+
+    $this->fixture(Class_Notice_SerialArticles::class, ['id' => 1,
+                                                        'clef_chapeau' => 'MY-KEY',
+                                                        'clef_numero' => '001']);
+
+    $this->fixture(Class_NoticeSuccincte::class, ['id' => 1]);
+    $this->fixture(Class_Exemplaire::class, ['id' => 1]);
+    $this->fixture(Class_StatsNotices::class, ['id' => 1]);
+    $this->fixture(Class_CodifTags::class, ['id' => 1]);
+    $this->fixture(Class_Cosmogramme_Integration::class, ['id' => 1]);
+    $this->fixture(Class_IntAnalyse::class, ['id' => 1]);
+    $this->fixture(Class_Pret::class, ['id' => 1]);
+    $this->fixture(Class_Reservation::class, ['id' => 1]);
+
+    Class_CosmoVar::setValueOf('cache_path', './cosmogramme/fichiers/my_cache');
+    Class_CosmoVar::setValueOf('integration_path', './cosmogramme/fichiers/my_integration');
+    Class_CosmoVar::setValueOf('log_path', './cosmogramme/fichiers/my_log');
+
+    $disk = (new Storm_FileSystem_Volatile)
+      ->mkdir('cosmogramme/fichiers/my_cache')
+      ->mkdir('cosmogramme/fichiers/my_integration')
+      ->mkdir('cosmogramme/fichiers/my_log')
+      ->cd('cosmogramme/fichiers/my_cache')
+      ->touch('8934789UI98E.cache')
+      ->cd('../my_integration')
+      ->touch('9456G6734YC.txt')
+      ->cd('/');
+
+    ZendAfi_View_Helper_Cosmo_DeleteItemsAllDashboard::setFileSystem($disk);
+
+    $fs = $this->mock();
+    $fs->whenCalled('opendir')
+       ->answers('dir_pointer')
+
+       ->whenCalled('readdir')
+       ->willDo(function()
+       {
+         Class_Cosmogramme_Cleaner::getFileSystem()->whenCalled('readdir')
+                                                   ->answers(false);
+         return '8934789UI98E.cache';
+       })
+
+       ->whenCalled('is_dir')
+       ->answers(false)
+
+       ->whenCalled('unlink')
+       ->answers(true)
+
+       ->whenCalled('closedir')
+       ->answers(true);
+
+    Class_Cosmogramme_Cleaner::setFileSystem($fs);
+  }
+
+
+  public function tearDown() {
+    ZendAfi_View_Helper_Cosmo_DeleteItemsAllDashboard::setFileSystem(null);
+    Class_Cosmogramme_Cleaner::setFileSystem(null);
+    return parent::tearDown();
+  }
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerAllRunDeleteArticleRecordsTest
+  extends Cosmo_DeleteItemsControllerAllTestCase {
+
+  public function setUp() {
+    parent::setUp();
+  }
+
+
+  public function stepClassMapping() {
+    return [[0, Class_Notice::class],
+            [1, Class_Notice_SerialArticles::class],
+            [2, Class_NoticeSuccincte::class],
+            [3, Class_Exemplaire::class],
+            [4, Class_StatsNotices::class],
+            [5, Class_CodifTags::class],
+            [6, Class_Cosmogramme_Integration::class],
+            [7, Class_IntAnalyse::class],
+            [8, Class_Pret::class],
+            [9, Class_Reservation::class]];
+  }
+
+
+  /**
+    @dataProvider stepClassMapping
+    @test
+   */
+  public function pageShouldContainsNextUrlWithNoticesSuccinctes(int $step, string $class) {
+    $this->dispatch('/cosmo/delete-items/all/step/' . $step . '/counter/' . $step );
+    $step++;
+    $this->assertXPath('//div[@data-delete-url="/cosmo/delete-items/all/step/' . $step . '/counter/' . $step . '"]');
+  }
+
+
+  /**
+    @dataProvider stepClassMapping
+    @test
+  */
+  public function entitiesShouldBeDeleted(int $step, string $class) {
+    $this->dispatch('/cosmo/delete-items/all/step/' . $step . '/counter/' . $step );
+    $this->assertEmpty($class::findAll());
+  }
+
+  /**
+    @dataProvider stepClassMapping
+    @test
+  */
+  public function pageShouldContainsTitleSuppressionEnCours(int $step, string $class) {
+    $this->dispatch('/cosmo/delete-items/all/step/' . $step . '/counter/' . $step );
+    $this->assertXPathContentContains('//h3', 'Suppression en cours');
+  }
+
+
+  public function stepDirectoryMapping() {
+    return [[10, 'cache_path'],
+            [11, 'integration_path'],
+            [12, 'log_path']
+    ];
+  }
+
+
+  /**
+    @dataProvider stepDirectoryMapping
+    @test
+   */
+  public function directoryShouldBeEmpty(int $step, string $variable) {
+    $path = Class_CosmoVar::getValueOf($variable);
+    $this->dispatch('/cosmo/delete-items/all/step/' . $step . '/counter/' . $step );
+    $this->assertFalse(Class_Cosmogramme_Cleaner::getFileSystem()->readdir($path));
+  }
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerAllDashboardTest
+  extends Cosmo_DeleteItemsControllerAllTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/delete-items/all-dashboard');
+  }
+
+
+  /** @test */
+  public function pageTitleShouldBeDeleteAllRecordsAndItems() {
+    $this->assertXPathContentContains('//h1', 'Supprimer toutes les notices et tous les exemplaires');
+  }
+
+
+  /** @test */
+  public function pageShouldContainConfirmButtonLancerLaSuppression() {
+    $this->assertXPathContentContains('//button[@id="run_delete"]', 'Lancer la suppression');
+  }
+
+
+  /** @test */
+  public function pageShouldContainBackButton() {
+    $this->assertXPathContentContains('//button[@class="back admin-button"]', 'Retour');
+  }
+
+
+  /** @test */
+  public function pageShouldContainModelsWarning() {
+    $this->assertXPathContentContains('//p[@class="notice"]', 'Êtes-vous sûr de vouloir supprimer les données suivantes');
+  }
+
+
+  /** @test */
+  public function pageShouldContain4Notices() {
+    $this->assertXPathContentContains('//li', '4 notices');
+  }
+
+
+  public function modelsToCount() : array {
+    return
+      [['notices'],
+       ['notices d\'articles'],
+       ['notices succinctes'],
+       ['exemplaires'],
+       ['statistiques des notices'],
+       ['codifications de tags'],
+       ['integrations'],
+       ['analyses d\'intégrations'],
+       ['prêts'],
+       ['réservations']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider modelsToCount
+   */
+  public function pageShouldContainModelLabel($label) {
+    $this->assertXPathContentContains('//ul/li', $label);
+  }
+
+
+  public function filesToCount() : array {
+    return [['my_cache'],
+            ['my_integration'],
+            ['my_log']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider filesToCount
+   */
+  public function pageShouldContainFolderToDelete($dir_name) {
+    $this->assertXPathContentContains('//ul/li', './cosmogramme/fichiers/' . $dir_name);
+  }
+
+
+  /** @test */
+  public function pageShouldContainsFolderMyCacheWith1Files() {
+    $this->assertXPathContentContains('//ul/li', './cosmogramme/fichiers/my_cache (1 fichiers)');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsFolderMyIntegrationWith1Files() {
+    $this->assertXPathContentContains('//ul/li', './cosmogramme/fichiers/my_integration (1 fichiers)');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsFolderMyLogWith1Files() {
+    $this->assertXPathContentContains('//ul/li', './cosmogramme/fichiers/my_log (0 fichiers)');
+  }
+
+
+  /** @test */
+  public function pageShouldHaveScriptAjaxDelete() {
+    $this->assertXPath('//script[contains(@src, "/public/admin/js/ajax-delete/ajax-delete.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldHaveScriptModalLoading() {
+    $this->assertXPath('//script[contains(@src, "/public/admin/js/modal-loading/modal-loading.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldHaveCssModalLoading() {
+    $this->assertXPath('//link[contains(@href, "/public/admin/js/modal-loading/modal-loading.css")]');
+  }
+
+
+  /** @test */
+  public function pageShouldHaveScriptToInitAjaxDeleteOnDeleteAll() {
+    $this->assertXPathContentContains('//script', '$(function(){$("#delete_all").ajax_delete();});');
+  }
+
+
+  /** @test */
+  public function pageShouldHaveDivIdDeleteAllWithDataAfterDeleteUrlDashboard() {
+    $this->assertXPath('//div[@id="delete_all"][@data-after-delete-url="/cosmo/delete-items/all-dashboard"]');
+  }
+
+
+  /** @test */
+  public function pageShouldHaveDivIdDeleteAllWithDataDeleteUrlAll() {
+    $this->assertXPath('//div[@id="delete_all"][@data-delete-url="/cosmo/delete-items/all/step/0/counter/0"]');
+  }
+}
+
+
+
+abstract class Cosmo_DeleteItemsControllerLibraryTestCase extends CosmoControllerTestCase {
+
+  public function setup() {
+    parent::setUp();
+
+    $this->fixture(Class_IntBib::class,
+                   ['id' => 1,
+                    'id_bib' => 1,
+                    'nom_court' => 'Champvans']);
+
+    $this->fixture(Class_IntBib::class,
+                   ['id' => 3,
+                    'ib_bib' => 3,
+                    'nom_court' => 'Authume']);
+
+    $this->fixture(Class_Notice::class, ['id' => 1,
+                                         'facettes' => 'HCCC001 B1 B3']);
+    $this->fixture(Class_Notice::class, ['id' => 2,
+                                         'facettes' => 'HCCC001 B1 B4']);
+    $this->fixture(Class_Notice::class, ['id' => 3,
+                                         'facettes' => 'HCCC001 B1 B5']);
+    $this->fixture(Class_Notice::class, ['id' => 4,
+                                         'facettes' => 'HCCC001 B3']);
+
+    foreach (range(5, 104) as $id)
+      $this->fixture(Class_Notice::class, ['id' => $id,
+                                           'facettes' => 'HCCC001']);
+
+
+    $this->fixture(Class_Exemplaire::class, ['id' => 1,
+                                             'id_bib' => 1,
+                                             'id_notice' => 1]);
+
+    $this->fixture(Class_Exemplaire::class, ['id' => 2,
+                                             'id_bib' => 3,
+                                             'id_notice' => 1]);
+
+    $this->fixture(Class_Exemplaire::class, ['id' => 3,
+                                             'id_bib' => 3,
+                                             'id_notice' => 2]);
+
+    $this->fixture(Class_Exemplaire::class, ['id' => 4,
+                                             'id_bib' => 3,
+                                             'id_notice' => 3]);
+
+    foreach (range(5, 104) as $item_id)
+      $this->fixture(Class_Exemplaire::class, ['id' => $item_id,
+                                               'id_bib' => 3]);
+
+    $this->fixture(Class_NoticeSuccincte::class, ['id' => 1, 'id_bib' => 1]);
+    $this->fixture(Class_NoticeSuccincte::class, ['id' => 2, 'id_bib' => 3]);
+
+    foreach (range(3, 102) as $id)
+      $this->fixture(Class_NoticeSuccincte::class, ['id' => $id, 'id_bib' => 3]);
+
+    $this->fixture(Class_Notice_SerialArticles::class, ['id' => 1,
+                                                        'clef_chapeau' => 'MY-KEY',
+                                                        'clef_numero' => '001']);
+
+    $this->fixture(Class_CodifTags::class, ['id' => 1]);
+  }
+}
+
+
+
+class Cosmo_DeleteItemsControllerLibraryDashboardTest
+  extends Cosmo_DeleteItemsControllerLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/delete-items/library-dashboard/id/1');
+  }
+
+
+  /** @test */
+  public function pageTitleShouldBeDeleteItemsForChampvans() {
+    $this->assertXPathContentContains('//h1', 'Supprimer tous les exemplaires de Champvans');
+  }
+
+
+  /** @test */
+  public function pageShouldContainConfirmButtonLancerLaSuppression() {
+    $this->assertXPathContentContains('//button[@id="run_delete"]', 'Lancer la suppression');
+  }
+
+
+  /** @test */
+  public function pageShouldContainBackButton() {
+    $this->assertXPathContentContains('//button[@class="back admin-button"]', 'Retour');
+  }
+
+
+  /** @test */
+  public function pageShouldContainModelsWarning() {
+    $this->assertXPathContentContains('//p[@class="notice"]', 'Êtes-vous sûr de vouloir supprimer les données suivantes');
+  }
+
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerLibraryActionFirstStepFirstRangeTest
+  extends Cosmo_DeleteItemsControllerLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/2/step/0/counter/0/limit/100');
+  }
+
+
+  /** @test */
+  public function pageShouldContainNextStepUrl() {
+    $this->assertXPath('//div[@data-delete-url="/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/2/step/0/counter/100/limit/100"]');
+  }
+
+
+  /** @test */
+  public function hundredItemsShouldHaveBeenDeletedForLibraryThree() {
+    $this->assertEquals(3, count(Class_Exemplaire::findAllBy(['id_bib' => 3])));
+  }
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerLibraryActionFirstStepSecondRangeTest
+  extends Cosmo_DeleteItemsControllerLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_Exemplaire::deleteBy(['id <' => 100]);
+    $this->dispatch('/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/2/step/0/counter/100');
+  }
+
+
+  /** @test */
+  public function pageShouldContainNextStepUrl() {
+    $this->assertXPath('//div[@data-delete-url="/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/2/step/1/counter/0"]');
+  }
+
+
+  /** @test */
+  public function allItemsShouldHaveBeenDeletedForLibraryThree() {
+    $this->assertCount(0, Class_Exemplaire::findAllBy(['id_bib' => 3]));
+  }
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerLibraryActionSecondStepFirstRangeTest
+  extends Cosmo_DeleteItemsControllerLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/101/step/1/counter/0/limit/100');
+  }
+
+
+  /** @test */
+  public function pageShouldContainNextStepUrl() {
+    $this->assertXPath('//div[@data-delete-url="/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/101/step/1/counter/100/limit/100"]');
+  }
+
+
+  /** @test */
+  public function hundredRecordsShouldHaveBeenDeletedForLibraryThree() {
+    $this->assertCount(1, Class_NoticeSuccincte::findAllBy(['id_bib' => 3]));
+  }
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerLibraryActionSecondStepSecondRangeTest
+  extends Cosmo_DeleteItemsControllerLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_NoticeSuccincte::deleteBy(['id <' => 100]);
+    $this->dispatch('/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/101/step/1/counter/100');
+  }
+
+
+  /** @test */
+  public function pageShouldContainNextStepUrl() {
+    $this->assertXPath('//div[@data-delete-url="/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/101/step/2/counter/0"]');
+  }
+
+
+  /** @test */
+  public function allRecordsShouldHaveBeenDeletedForLibraryThree() {
+    $this->assertCount(0, Class_NoticeSuccincte::findAllBy(['id_bib' => 3]));
+  }
+}
+
+
+
+
+class Cosmo_DeleteItemsControllerLibraryActionThirdStepFirstRangeTest
+  extends Cosmo_DeleteItemsControllerLibraryTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_Exemplaire::deleteBy(['id_bib' => 3]);
+    $this->dispatch('/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/101/step/2/counter/0/limit/100');
+  }
+
+
+  /** @test */
+  public function pageShouldContainNextStepUrl() {
+    $this->assertXPath('//div[@data-delete-url="/cosmo/delete-items/library/id/3/count_items/103/count_succinct_records/101/step/2/counter/100/limit/100"]');
+  }
+
+
+  /** @test */
+  public function firstNoticeShouldHaveLostFacetB3() {
+    $this->assertEquals(' HCCC001 B1', Class_Notice::find(1)->getFacettes());
+  }
+
+
+  /** @test */
+  public function secondNoticeShouldHaveLostFacetB4AndB1() {
+    $this->assertEquals(' HCCC001', Class_Notice::find(2)->getFacettes());
+  }
+
+
+  /** @test */
+  public function thirdNoticeShouldHaveLostFacetB5() {
+    $this->assertEquals(' HCCC001', Class_Notice::find(3)->getFacettes());
+  }
+
+
+  /** @test */
+  public function fourthNoticeShouldHaveLostFacetB3() {
+    $this->assertEquals(' HCCC001', Class_Notice::find(4)->getFacettes());
+  }
+}
diff --git a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IndexTest.php b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IndexTest.php
index 535fad224dba1214dd5999d8e7d79e8c6b2a9699..d33d17f1ca0964e9eeb660a439d14120ec55f0b2 100644
--- a/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IndexTest.php
+++ b/cosmogramme/cosmozend/tests/application/modules/cosmo/controllers/IndexTest.php
@@ -24,6 +24,7 @@ class Cosmo_IndexTest extends CosmoControllerTestCase {
 
   public function setUp() {
     parent::setUp();
+    Class_IntBib::setTimeSource(new TimeSourceForTest('2021-10-16'));
 
     $this->fixture(Class_IntBib::class,
                    ['id' => 23,
@@ -79,6 +80,8 @@ class Cosmo_IndexTest extends CosmoControllerTestCase {
 
     Class_CosmoVar::setValueOf('integration_path', '/cosmo/integration/');
 
+    Class_AdminVar::set('TEXT_REPLACEMENT','');
+
     $integre12 = $this
       ->mock()
       ->whenCalled('getFileSize')
diff --git a/cosmogramme/php/_menu.php b/cosmogramme/php/_menu.php
index 2337aa82d19eb9c938b356599942d53aa2a6f64a..777021a4abf90df2d0f63b04d653103382023c1d 100644
--- a/cosmogramme/php/_menu.php
+++ b/cosmogramme/php/_menu.php
@@ -117,7 +117,7 @@ else
 	?>
 	<div class="menu_section">Utilitaires</div>
 	<?php
-	ligneMenu("Suppression d'exemplaires","integre_supprimer_exemplaires.php");
+        ligneMenu("Suppression d'exemplaires","../cosmozend/cosmo/delete-items/index");
 	ligneMenu("Réindexation des identifiants","util_indexation.php");
 	ligneMenu("Réindexation phonétique","util_fulltext.php?action=PARAM");
 	?>
diff --git a/cosmogramme/php/integre_supprimer_exemplaires.php b/cosmogramme/php/integre_supprimer_exemplaires.php
deleted file mode 100644
index 11b39f71f24a4e3defcc33f4c862dfea4d3ea4be..0000000000000000000000000000000000000000
--- a/cosmogramme/php/integre_supprimer_exemplaires.php
+++ /dev/null
@@ -1,240 +0,0 @@
-<?PHP
-/**
- * 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 
- */
-///////////////////////////////////////////////////////////////////
-//
-//         SUPPRESSION DES EXEMPLIARES D'UNE BIBLIOTHEQUE
-//
-////////////////////////////////////////////////////////////////////
-include("_init_frame.php");
-
-// Includes
-require_once("classe_bib.php");
-$oBib=new bibliotheque();
-require_once("classe_chronometre.php"); 
-$chrono = new chronometre();
-
-?>
-
-<h1>Suppression de notices ou d'exemplaires</h1>
- 
-<?PHP
-
-//---------------------------------------------------------------------------------
-// SUPPRESSION
-//---------------------------------------------------------------------------------
-if($_REQUEST["action"]=="SUPPRIMER")
-{
-	// Suppression de toutes les notices de la base
-	if($_REQUEST["id_bib"] == "ALL")
-	{
-		if($_SESSION["passe"]!= "admin_systeme") afficherErreur("Vous devez être connecté en tant qu'administrateur système pour exécuter cette fonction"); 
-		viderTable("notices");
-		viderTable("notices_articles");
-		viderTable("notices_succintes");
-		viderTable("exemplaires");
-		viderTable("stats_notices");
-		//viderTable("codif_auteur");
-		//viderTable("codif_matiere");
-		//viderTable("codif_interet");
-		viderTable("codif_tags");
-		viderTable("integrations");
-		viderTable("int_analyse");
-		viderTable("prets");
-		viderTable("reservations");
-		viderRepertoire("Cache","cache_path");
-		viderRepertoire("Integration","integration_path");
-		viderRepertoire("Logs","log_path");
-		
-		print(BR.'<h3>Traitement terminé</h3>'.BR.BR.'</body></html>');
-		exit;
-	}
-	
-	// Initialisations
-	$id_bib=$_REQUEST["id_bib"];
-	$nb_notices=0;
-	$nb_total=0;
-	$avance=-1;
-	$pointeur_reprise=0;
-	$timeout=intval(ini_get("max_execution_time") * 0.75);
-	$timeStart=time();
-	$chrono->start();
-	
-	if($_REQUEST["reprise"]=="oui") restaureContext();
-	else 
-	{
-		$nb_total = $sql->fetchOne("select count(*) from notices");
-		// Suppression des exemplaires et des notices succintes
-		$entete=BR.'<h4>Suppression des exemplaires de : '.$oBib->getNomCourt($id_bib).'</h4>';
-		$nombre=$sql->execute("delete from exemplaires where id_bib=$id_bib");
-		$nombre1=$sql->execute("delete from notices_succintes where id_bib=$id_bib");
-		$entete.='<span class="vert" style="margin-left:10px">'.$nombre.' exemplaires supprimés</span>'.BR;
-		$entete.='<span class="vert" style="margin-left:10px">'.$nombre1.' notices succintes supprimées</span>'.BR;
-	}
-	
-	// Recalcul de la facette bibliotheque
-	print($entete);
-	print(BR.'<h4>Mise à jour des facettes bibliothèques</h4>');
-	
-	// Jauge
-	print(BR.'<div class="jauge" style="border:none"><div id="pct" class="pct">0 %</div>');
-	print('<div class="jauge"><div id="jauge" class="jauge_avance"></div></div>');
-	print('</div>');
-	print('<br><div>');
-	print('<span id="notice"></span>');
-	print('</div>');
-	flush();
-	
-	// Boucle de traitement
-	$resultat=$sql->prepareListe("select id_notice,facettes from notices where id_notice > $pointeur_reprise Order by id_notice ");
-	while($ligne=$sql->fetchNext($resultat)) 
-	{
-		if($chrono->tempsPasse() > $timeout) sauveContexte();
-		$id_notice=$ligne["id_notice"];
-		$facette=explode(" ",$ligne["facettes"]);
-		$facettes="";
-		for($i=0; $i < count($facette); $i++)
-		{
-			if(!$facette[$i]) continue;
-			if(substr($facette[$i],0,1) != "B")$facettes.=" ".$facette[$i];
-		}
-		// Chercher les exemplaires
-		$bibs=$sql->fetchAll("select distinct(id_bib) from exemplaires where id_notice=$id_notice");
-		if(count($bibs))
-		{
-			foreach($bibs as $enreg)
-			{
-				$bib=" B".$enreg["id_bib"];
-				$facettes.= $bib;
-			}
-		}
-		// Ecrire
-		$sql->execute("update notices set facettes='$facettes' where id_notice=$id_notice");
-		$pointeur_reprise=$ligne["id_notice"];
-		$nb_notices++;
-		afficherAvance($nb_notices,$nb_total);
-	}
-
-	// Fin
-	afficherAvance($nb_total,$nb_total);
-	$chrono->timeStart=$timeStart;
-	print("<h4>Fin du traitement</h4>");
-	print('<span class="vert" style="margin-left:10px">Temps de traitement : '.$chrono->end().'</span>'.BR.BR);
-	exit;
-}
-
-//---------------------------------------------------------------------------------
-// LISTE DES BIBLIOTHEQUES
-//---------------------------------------------------------------------------------
-print(BR.'<div style="margin-left:140px">');
-print(rendBouton("Supprimer toutes les notices de la base","integre_supprimer_exemplaires.php","action=SUPPRIMER&id_bib=ALL","Etes vous sûr de vouloir supprimer toutes les notices de la base ?"));
-print('</div>');
-
-print(BR.'<span class="violet"><b>Attention : cette fonction va supprimer tous les exemplaires de la bibliothèque que vous allez choisir</b></span>'.BR.BR);
-print('<div class="liste" style="margin-left:100px">');
-print('<table>');
-$liste=$oBib->getAll();
-foreach( $liste as $id_bib => $bib)
-{
-	$suppr=rendBouton("Supprimer","integre_supprimer_exemplaires.php","action=SUPPRIMER&id_bib=".$bib["id_bib"]);
-	print('<tr><td style="padding:7px"><b>'. $bib["id_bib"].'</b></td>');
-	print('<td style="padding:7px"><b>'. $bib["nom_court"].'</b></td>');
-	print('<td>'.$suppr.'</td</tr>');
-}
-print('</table></div>'.BR.BR);
-print('</body></html>');
-
-// ----------------------------------------------------------------
-// Gestion du contexte pour les timeout
-// ----------------------------------------------------------------
-function sauveContexte()
-{
-	global $timeStart,$pointeur_reprise,$entete;
-	global $nb_notices,$nb_total;
-
-	$data=compact("timeStart","pointeur_reprise","nb_notices","nb_total","entete");
-	$_SESSION["reprise"]=$data;
-	redirection( "integre_supprimer_exemplaires.php?reprise=oui&action=SUPPRIMER");
-}
-
-function restaureContext()
-{
-	global $timeStart,$pointeur_reprise,$entete;
-	global $nb_notices,$nb_total;
-	
-	extract($_SESSION["reprise"]);
-	unset($_SESSION["reprise"]);
-}
-
-function afficherAvance($pointeur,$nb_total)
-{
-	global $avance;
-	$pct=(int)(($pointeur / $nb_total) * 100);
-	if($pct != $avance)
-	{
-		$avance=$pct;
-		print('<script>');
-		print("document.getElementById('pct').innerHTML='".$pct."%';");
-		$jauge="document.getElementById('jauge').style.width='".$pct."%';";
-		print($jauge);		
-		print('</script>');
-	}
-	if($pointeur % 100 == 0 or $pct==100)
-	{
-		print('<script>');
-		print("document.getElementById('notice').innerHTML='".$pointeur." / ".$nb_total."';");
-		print('</script>');
-	}
-	flush();
-}
-
-function viderTable($table)
-{
-	global $sql;
-	$nb=$sql->fetchOne("select count(*) from ".$table);
-	print('<span class="violet" style="margin-left:10px"><b>&raquo;&nbsp;Table : '.$table.'</b></span>'); 
-	$sql->execute("TRUNCATE TABLE ".$table);
-	if(!$nb) $msg="aucun enregistrement supprimé";
-	elseif($nb == 1) $msg="1 enregistrement supprimé";
-	else $msg=$nb." enregistrements supprimés";
-	print('<span class="vert" style="margin-left:10px">-&nbsp;'.$msg.'</span>'.BR);
-}
-
-function viderRepertoire($titre,$type)
-{
-	print('<span class="violet" style="margin-left:10px"><b>&raquo;&nbsp;Dossier : '.$titre.'</b></span>'); 
-	$path=getVariable($type);
-	
-	@$dir = opendir($path) or AfficherErreur("Impossible d'ouvrir le dossier : " .$path);
-	while (($file = readdir($dir)) !== false)
-	{
-		$fichier=$path.$file;
-		if(is_dir($fichier)) continue;
-		$nb++;
-		unlink($fichier);
-	}
-	closedir( $dir);
-	// affichage du nombre
-	if(!$nb) $msg="aucun fichier supprimé";
-	elseif($nb == 1) $msg="1 fichier supprimé";
-	else $msg=$nb." fichiers supprimés";
-	print('<span class="vert" style="margin-left:10px">-&nbsp;'.$msg.'</span>'.BR);
-}
-?>
\ No newline at end of file
diff --git a/library/Class/Cosmogramme/Cleaner.php b/library/Class/Cosmogramme/Cleaner.php
new file mode 100644
index 0000000000000000000000000000000000000000..c0f02b24f2f86864bdfe4f2da3f981a2eeb03cc9
--- /dev/null
+++ b/library/Class/Cosmogramme/Cleaner.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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_Cosmogramme_Cleaner {
+
+  use Trait_StormFileSystem;
+  use Trait_Translator;
+
+  protected array $_truncate_steps = [];
+  protected array $_clear_steps = [];
+
+  const RECORDS = 0;
+  const SERIAL_ARTICLES = 1;
+  const SUCCINCTE = 2;
+  const ITEMS = 3;
+  const STATS = 4;
+  const TAGS = 5;
+  const INTEGRATION = 6;
+  const ANALYSE = 7;
+  const LOAN = 8;
+  const HOLD = 9;
+
+  const CACHE_PATH = 10;
+  const INTEGRATION_PATH = 11;
+  const LOG_PATH = 12;
+
+  protected string $_step;
+  protected int $_count_steps_done;
+
+
+  public function __construct(string $step, int $count_steps_done) {
+    $this->_truncate_steps =
+      [static::RECORDS => [Class_Notice::class, $this->_('suppression des notices')],
+       static::SERIAL_ARTICLES => [Class_Notice_SerialArticles::class, $this->_('suppression des notices d\'articles')],
+       static::SUCCINCTE => [Class_NoticeSuccincte::class, $this->_('suppression des notices succinctes')],
+       static::ITEMS => [Class_Exemplaire::class, $this->_('suppression des exemplaires')],
+       static::STATS => [Class_StatsNotices::class, $this->_('suppression des statistiques de notice')],
+       static::TAGS => [Class_CodifTags::class, $this->_('suppression des codifications de tag')],
+       static::INTEGRATION => [Class_Cosmogramme_Integration::class, $this->_('suppression des intégrations')],
+       static::ANALYSE => [Class_IntAnalyse::class, $this->_('suppression des analyses d\'intégrations')],
+       static::LOAN => [Class_Pret::class, $this->_('suppression des prêts')],
+       static::HOLD => [Class_Reservation::class, $this->_('suppression des réservations')]];
+
+    $this->_clear_steps =
+      [static::CACHE_PATH => ['cache_path', $this->_('Nettoyage du dossier cache')],
+       static::INTEGRATION_PATH => ['integration_path', $this->_('Nettoyage du dossier d\'intégration')],
+       static::LOG_PATH => ['log_path', $this->_('Nettoyage du dossier de log')]];
+
+    $this->_step = $step;
+    $this->_count_steps_done = $count_steps_done;
+  }
+
+
+  protected function _isTruncateStep() : bool {
+    return in_array($this->_step, array_keys($this->_truncate_steps));
+  }
+
+
+  protected function _isClearStep() : bool {
+    return in_array($this->_step, array_keys($this->_clear_steps));
+  }
+
+
+  public function getStepLabel() : string {
+    return $this->_isTruncateStep()
+      ? $this->_truncate_steps[$this->_step][1]
+    : $this->_clear_steps[$this->_step][1];
+  }
+
+
+  public function delete() : self {
+    if ($this->_isTruncateStep())
+      return $this->_truncateStep();
+
+    if ($this->_isClearStep())
+      return $this->_clearDirectoryStep();
+
+    return $this;
+  }
+
+
+  protected function _truncateStep() : self {
+    $definition = $this->_truncate_steps[$this->_step];
+    $class = reset($definition);
+    $class::truncate();
+    $this->_count_steps_done++;
+    $this->_next = $this->_step + 1;
+    return $this;
+  }
+
+
+  protected function _clearDirectoryStep() : self {
+    $definition = $this->_clear_steps[$this->_step];
+    $variable = reset($definition);
+
+    $path = Class_CosmoVar::get($variable);
+
+    $this->_count_steps_done++;
+    $this->_next = $this->_step + 1;
+
+    $fs = static::getFileSystem();
+    $dir = $fs->opendir($path);
+
+    if (!$dir)
+      return $this;
+
+    while (($file = $fs->readdir($dir)) !== false)
+    {
+      $fichier = $path . $file;
+      if($fs->is_dir($fichier)) continue;
+      $fs->unlink($fichier);
+    }
+    $fs->closedir($dir);
+
+    return $this;
+  }
+
+
+  public function countStepsDone() : int {
+    return $this->_count_steps_done;
+  }
+
+
+  public function countTotalSteps() : int {
+    return count($this->_truncate_steps) + count($this->_clear_steps);
+  }
+
+
+  public function countStepsDonePercent() : int {
+    return ( 0 !== ($total_albums = $this->countTotalSteps()))
+      ? intval(($this->_count_steps_done / $total_albums) * 100)
+      : 0;
+  }
+
+
+  public function nextStep() : ?string {
+    return $this->_next && $this->_next < $this->countTotalSteps()
+      ? $this->_next
+      : null;
+  }
+
+
+  public function nextStepCounter() : int {
+    return $this->_count_steps_done ++;
+  }
+
+
+  public function countRemainingSteps() : int {
+    return $this->countTotalSteps() - $this->_count_steps_done;
+  }
+}
diff --git a/library/Class/Cosmogramme/ItemsCleaner.php b/library/Class/Cosmogramme/ItemsCleaner.php
new file mode 100644
index 0000000000000000000000000000000000000000..cfaec5200d7414ed9ca9974f60d2669f915c64ca
--- /dev/null
+++ b/library/Class/Cosmogramme/ItemsCleaner.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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_Cosmogramme_ItemsCleaner {
+
+  use Trait_Translator;
+
+  const LIMIT = 1000;
+
+  const ITEMS = 0;
+  const SUCCINCTE = 1;
+  const FACETS = 2;
+
+  protected int
+    $_id_bib,
+    $_step,
+    $_count_steps_done,
+    $_count_items,
+    $_count_records,
+    $_count_entities_done,
+    $_limit;
+
+    protected array
+      $_delete_steps = [],
+      $_clean_steps = [];
+
+  public function __construct(int $id_bib,
+                              int $step,
+                              int $count_items,
+                              int $count_records,
+                              int $count_entities_done,
+                              int $limit=self::LIMIT) {
+
+    $this->_delete_steps =
+      [static::ITEMS => [Class_Exemplaire::class, $this->_('suppression des exemplaires')],
+       static::SUCCINCTE => [Class_NoticeSuccincte::class, $this->_('suppression des notices succinctes')]];
+
+    $this->_clean_steps =
+      [static::FACETS => ['facets', $this->_('nettoyage des facettes')]];
+
+    $this->_id_bib = $id_bib;
+    $this->_step = $step;
+    $this->_count_items = $count_items;
+    $this->_count_records = $count_records;
+    $this->_count_entities_done = $count_entities_done;
+    $this->_limit = $limit;
+  }
+
+
+  protected function _isDeleteStep() : bool {
+    return in_array($this->_step, array_keys($this->_delete_steps));
+  }
+
+
+  protected function _isCleanStep() : bool {
+    return in_array($this->_step, array_keys($this->_clean_steps));
+  }
+
+
+  public function getStepLabel() : string {
+    $label = ($this->_isDeleteStep()
+       ? $this->_delete_steps[$this->_step][1]
+       : $this->_clean_steps[$this->_step][1]);
+
+    return $this->_('Étape %s/%s - %s',
+                    $this->_step +1,
+                    $this->countTotalSteps(),
+                    $label);
+  }
+
+
+  public function delete() : self {
+    if ( $this->_isDeleteStep())
+      return $this->_deleteStep();
+
+    if ( $this->_isCleanStep())
+      return $this->_cleanStep();
+
+    return $this;
+  }
+
+
+  protected function _deleteStep() : self {
+    $definition = $this->_delete_steps[$this->_step];
+    $class = reset($definition);
+
+    $entities = $class::findAllBy(['id_bib' => $this->_id_bib,
+                                   'limit' => $this->getLimit()]);
+    foreach($entities as $entity)
+      $this->_deleteEntity($entity);
+
+    return $this;
+  }
+
+
+  protected function _cleanStep() : self {
+    $notices = Class_Notice::query()
+      ->select()
+      ->gt('id_notice', $this->_count_entities_done )
+      ->order('id_notice')
+      ->limit($this->getLimit())
+      ->fetchAll();
+
+    foreach($notices as $notice)
+      $this->_cleanFacets($notice);
+
+    return $this;
+  }
+
+
+  protected function _cleanFacets($notice) : self {
+    $orig_facette = array_filter(explode(" ", $notice->getFacettes()));
+    $facettes = '';
+
+    foreach($orig_facette as $facet)
+      if(substr($facet,0,1) != Class_Bib::CODE_FACETTE)
+        $facettes .= ' ' . $facet;
+
+    $bibs = Class_Exemplaire::query()
+      ->distinct(['id_bib'])
+      ->eq('id_notice', $notice->getIdNotice())
+      ->fetchAll();
+
+    foreach($bibs as $bib)
+      $facettes.=" " . Class_Bib::CODE_FACETTE . $bib->getIdBib();
+
+    Class_Notice::find($notice->getIdNotice())->setFacettes($facettes)->save();
+    $this->_count_entities_done++;
+
+    return $this;
+  }
+
+
+  protected function _deleteEntity($entity) : self {
+    $entity->delete();
+    $this->_count_entities_done++;
+
+    return $this;
+  }
+
+
+  public function getLimit() : int {
+    return $this->_limit;
+  }
+
+
+  public function currentStep() : int {
+    return $this->_step;
+  }
+
+
+  public function nextStep() : ? int {
+    $next_step = $this->_step;
+
+    if ($this->_count_entities_done >= $this->_totalToDo()) {
+      $next_step++;
+      $this->_count_entities_done = 0;
+    }
+
+    return $next_step >= $this->countTotalSteps() ? null : $next_step;
+  }
+
+
+  public function countDonePercent() : int {
+    return (0 !== ($total = $this->_totalToDo()))
+      ? intval(($this->_count_entities_done / $this->_totalToDo()) * 100)
+      : 0;
+  }
+
+
+  public function countTotalSteps() : int {
+    return count($this->_delete_steps) + count($this->_clean_steps);
+  }
+
+
+
+  public function nextCount() : int {
+    return $this->_count_entities_done;
+  }
+
+
+  public function totalItems() : int {
+    return $this->_count_items;
+  }
+
+
+  public function totalRecords() : int {
+    return $this->_count_records;
+  }
+
+
+  public function idBib() : int {
+    return $this->_id_bib;
+  }
+
+  protected function _totalToDo() : int {
+    if ($this->_step == 0)
+      return $this->_count_items;
+
+    if ($this->_step == 1)
+      return $this->_count_records;
+
+    if ($this->_step == 2)
+      return Class_Notice::countBy([]);
+
+
+    return 0;
+  }
+}
diff --git a/library/Class/IntAnalyse.php b/library/Class/IntAnalyse.php
new file mode 100644
index 0000000000000000000000000000000000000000..aceefc458f00c1b4aa0340a853818663c139fd2c
--- /dev/null
+++ b/library/Class/IntAnalyse.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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_IntAnalyse extends Storm_Model_Abstract {
+
+  protected $_table_name = 'int_analyse';
+}
diff --git a/library/Class/StatsNotices.php b/library/Class/StatsNotices.php
index ac55c190745b85b8bd3af3eda5c6ce36f1667d47..dbd9431f654e7632d7308e80c7dc16a0ccbd519f 100644
--- a/library/Class/StatsNotices.php
+++ b/library/Class/StatsNotices.php
@@ -19,10 +19,13 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class Class_StatsNotices {
+class Class_StatsNotices extends Storm_Model_Abstract {
 
   use Trait_TimeSource;
 
+  protected
+    $_table_name = 'stats_notices';
+
 
   private $lib_mois =
     ['', 'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
@@ -201,4 +204,4 @@ class Class_StatsNotices {
     return false;
   }
 }
-?>
\ No newline at end of file
+?>
diff --git a/library/Class/TableDescription/CosmoCleanLibraryItems.php b/library/Class/TableDescription/CosmoCleanLibraryItems.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f1031452d70e35a89a82ae556cd62d636b85ed8
--- /dev/null
+++ b/library/Class/TableDescription/CosmoCleanLibraryItems.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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_TableDescription_CosmoCleanLibraryItems extends Class_TableDescription {
+  public function init() {
+    $this
+      ->addColumn($this->_('#id'), 'id')
+      ->addColumn($this->_('Nom'), 'nom_court')
+      ->addRowAction(['url' => ['action' => 'library-dashboard',
+                                'id' => '%s'],
+                      'icon' => 'delete',
+                      'label' => $this->_('Supprimer les exemplaires')]);
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Admin/DeleteAjaxDashboard.php b/library/ZendAfi/View/Helper/Admin/DeleteAjaxDashboard.php
new file mode 100644
index 0000000000000000000000000000000000000000..96e52b1274ccdc1c1708e8297f2c2b43f4cfce70
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Admin/DeleteAjaxDashboard.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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 ZendAfi_View_Helper_Admin_DeleteAjaxDashboard
+  extends ZendAfi_View_Helper_BaseHelper {
+
+  public function deleteAjaxDashboard(string $id,
+                                      string $delete_url,
+                                      string $after_delete_url,
+                                      string $counter_message) : string {
+
+    Class_ScriptLoader::getInstance()
+      ->addScript(Class_Url::absolute('public/admin/js/ajax-delete/ajax-delete.js'))
+      ->addJqueryReady(sprintf('$("#%s").ajax_delete();', $id));
+
+    $html = [$this->_tag('h1',
+                         $this->view->titre)];
+
+    $html[] = $counter_message;
+    $html[] = $this->_tagWarning($this->_('Pendant la suppression, la navigation sur le site peut être dégradée.'));
+
+    $html[]= $this->view->button((new Class_Button)
+                                 ->setImage($this->view->renderIcon('class fa fa-exclamation-triangle'))
+                                 ->setText($this->_('Lancer la suppression'))
+                                 ->setAttribs(['onclick' => '',
+                                               'data-confirm-message' => $this->_('Êtes vous sûr ?'),
+                                               'id' => 'run_delete']));
+    $html[] = $this->view->Button_Back();
+    $html[] = $this->view->modalLoading($id);
+
+    return $this->_div(['id' => $id,
+                        'data-delete-url' => $delete_url,
+                        'data-after-delete-url' => $after_delete_url],
+                       implode($html));
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategory.php b/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategory.php
index 490881bd81e4bcf24c571e8ffde5d9428e83c805..3c1d437e246a55520bf0f38a4106cf5100b5d887 100644
--- a/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategory.php
+++ b/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategory.php
@@ -40,7 +40,7 @@ class ZendAfi_View_Helper_Admin_DeleteAlbumCategory extends ZendAfi_View_Helper_
       . BR
       . $this->_cancelButton();
 
-    return $this->_div(['class' => 'delete_album_category',
+    return $this->_div(['class' => 'delete_ajax delete_album_category',
                         'data-delete-url' => $this->_getNewDeleteUrl(),
                         'data-count-remaining-albums' => $this->_cleaner->countRemainingAlbums(),
                         'data-count-remaining-sub-categories' => $this->_cleaner->countRemainingSubCategories(),
diff --git a/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategoryDashboard.php b/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategoryDashboard.php
index 2b73e7fdf7c1ed9b788d0067815aa28d54c13d8f..b3d0c976460707960006d8ca07f3d88bd4dfadea 100644
--- a/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategoryDashboard.php
+++ b/library/ZendAfi/View/Helper/Admin/DeleteAlbumCategoryDashboard.php
@@ -20,58 +20,50 @@
  */
 
 
-class ZendAfi_View_Helper_Admin_DeleteAlbumCategoryDashboard extends ZendAfi_View_Helper_BaseHelper {
+class ZendAfi_View_Helper_Admin_DeleteAlbumCategoryDashboard
+  extends ZendAfi_View_Helper_BaseHelper {
+
+  protected
+    $_counter,
+    $_category;
+
 
   public function deleteAlbumCategoryDashboard(Class_AlbumCategorie $category) : string {
+    $this->_category = $category;
+    $this->_counter = new Class_AlbumCategorie_DataCounters($this->_category);
+
     $id = 'delete_album_category_dashboard_' . $category->getId();
+    return $this->view->deleteAjaxDashboard($id,
+                                            $this->_deleteUrl(),
+                                            '/admin/album',
+                                            $this->_addCounterMessage());
+  }
+
+
+  protected function _deleteUrl() : string {
+    return $this->view->url(['module' => 'admin',
+                             'controller' => 'album',
+                             'action' => 'run-delete-category',
+                             'id' => $this->_category->getId(),
+                             'count_albums' => $this->_counter->countTotalAlbums(),
+                             'count_sub_categories' => $this->_counter->countTotalSubCategories(),
+                             'count_records' => $this->_counter->countTotalRecords()],
+                            null, true);
+  }
+
+
+  protected function _addCounterMessage() : string {
+    return $this->_tagNotice($this->_('Êtes-vous sûr de vouloir supprimer la catégorie %s%s%s%s',
+                                      $this->_category->getLibelle(),
+                                      $this->_countAlbumsToText($this->_counter),
+                                      $this->_countRecordsToText($this->_counter),
+                                      $this->_countSubCategoriesToText($this->_counter)));
 
-    Class_ScriptLoader::getInstance()
-      ->addScript(Class_Url::absolute('public/admin/js/delete-album-category/delete-album-category.js'))
-      ->addJqueryReady(sprintf('$("#%s").delete_album_category();', $id));
-
-    $confirm_message = $this->_('Êtes vous sûr ?');
-
-    $counter = new Class_AlbumCategorie_DataCounters($category);
-
-    $delete_url = $this->view->url(['module' => 'admin',
-                                    'controller' => 'album',
-                                    'action' => 'run-delete-category',
-                                    'id' => $category->getId(),
-                                    'count_albums' => $counter->countTotalAlbums(),
-                                    'count_sub_categories' => $counter->countTotalSubCategories(),
-                                    'count_records' => $counter->countTotalRecords()],
-                                   null, true);
-
-    $redirect_url = $this->view->url(['action' => 'index',
-                                      'id' => null,
-                                      'cat_id' => null,
-                                      'render' => null]);
-
-    $html =
-      $this->_tagNotice($this->_('Êtes-vous sûr de vouloir supprimer la catégorie %s%s%s%s',
-                                  $category->getLibelle(),
-                                  $this->_countAlbumsToText($counter),
-                                  $this->_countRecordsToText($counter),
-                                  $this->_countSubCategoriesToText($counter)))
-      . $this->_tagWarning($this->_('Pendant la suppression, la navigation sur le site peut être dégradée.'))
-      . $this->view->button((new Class_Button)
-                            ->setImage($this->view->renderIcon('class fa fa-exclamation-triangle'))
-                            ->setText($this->_('Lancer la suppression'))
-                            ->setAttribs(['onclick' => '',
-                                          'data-confirm-message' => $confirm_message,
-                                          'id' => 'run_delete']))
-      . $this->view->Button_Back()
-      . $this->view->modalLoading($id);
-
-    return $this->_div(['id' => $id,
-                        'data-delete-url' => $delete_url,
-                        'data-after-delete-url' => '/admin/album'],
-                       $html);
   }
 
 
   protected function _countAlbumsToText(Class_AlbumCategorie_DataCounters $counter) : string {
-    $count = $counter->countTotalAlbums();
+    $count = $this->_counter->countTotalAlbums();
     return $this->_plural($count,
                           '',
                           ', son album',
diff --git a/library/ZendAfi/View/Helper/Cosmo/Cleaner.php b/library/ZendAfi/View/Helper/Cosmo/Cleaner.php
new file mode 100644
index 0000000000000000000000000000000000000000..318c3301e1194f500c3e7cb2e5fea6afea6b67f5
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Cosmo/Cleaner.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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 ZendAfi_View_Helper_Cosmo_Cleaner extends ZendAfi_View_Helper_BaseHelper {
+
+  protected Class_Cosmogramme_Cleaner $_cleaner;
+
+
+  public function Cosmo_Cleaner(Class_Cosmogramme_Cleaner $cleaner) : string {
+    $this->_cleaner = $cleaner;
+
+    if ( null === $this->_cleaner->nextStep())
+      return '';
+
+    $html =
+      $this->_tag('h3', $this->_('Suppression en cours'))
+      . $this->_tagWarning($this->_('Pour continuer la suppression, veuillez garder cette page ouverte'))
+      . $this->_tagNotice($this->_('Vous serez redirigé une fois la suppression terminée'))
+      . $this->_countSteps()
+      . $this->_progressBar()
+      . BR
+      . $this->_cancelButton();
+
+    return $this->_div(['class' => 'delete_ajax',
+                        'data-delete-url' => $this->_getNewDeleteUrl(),
+                        'data-count-remaining-steps' => $this->_cleaner->countRemainingSteps()],
+                       $html);
+  }
+
+
+  protected function _countSteps() : string {
+    return $this->_addTextToCounters($this->_cleaner->countStepsDone(),
+                                     $this->_cleaner->countTotalSteps());
+  }
+
+
+  protected function _addTextToCounters(int $count_deleted,
+                                        int $count_total) {
+    if ( 0 === $count_total)
+      return '';
+
+    $step_label = $this->_cleaner->getStepLabel();
+    $status_message = $this->_("Étape %s/%s (%s)", $count_deleted, $count_total, $step_label);
+
+    return $this->_tagWarning($status_message);
+  }
+
+
+  protected function _progressBar() : string {
+    $id = 'progress_bar_ajax_delete';
+
+    $script = sprintf('$("#%s").progressbar({value: %d});',
+                      $id,
+                      $this->_cleaner->countStepsDonePercent());
+
+    Class_ScriptLoader::getInstance()->addJQueryReady($script);
+
+    return $this->_div(['id' => $id], '');
+  }
+
+
+  protected function _getNewDeleteUrl() : string {
+    $new_counters_params =
+      ['module' => 'cosmo',
+       'controller' => 'delete-items',
+       'action' => 'all',
+       'step' => $this->_cleaner->nextStep(),
+       'counter' => $this->_cleaner->nextStepCounter()];
+
+    return $this->view->url($new_counters_params, null, true);
+  }
+
+
+  protected function _cancelButton() : string {
+    return
+      $this->view->button((new Class_Button)
+                          ->setImage($this->view->renderIcon('class fa fa-fire-extinguisher'))
+                          ->setText($this->_('Stopper la suppression'))
+                          ->setAttribs(['onclick' => '',
+                                        'id' => 'stop_delete']));
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Cosmo/DeleteItems.php b/library/ZendAfi/View/Helper/Cosmo/DeleteItems.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e931c5744566fbd29757bfa6c7a0b6462c36538
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Cosmo/DeleteItems.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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 ZendAfi_View_Helper_Cosmo_DeleteItems extends ZendAfi_View_Helper_BaseHelper {
+  public function Cosmo_DeleteItems() : string {
+    $html = [$this->_tag('h1',
+                         $this->view->titre)];
+
+    $html[] = $this->view->button((new Class_Button)
+                                  ->setText($this->_('Supprimer toutes les notices de la base'))
+                                  ->setUrl($this->view->url(['module' => 'cosmo',
+                                                             'controller' => 'delete-items',
+                                                             'action' => 'all-dashboard'], null, true)));
+
+    $html[] = $this->_tagNotice('Les boutons suivants vous permettent de supprimer les exemplaires d\'une bibliothèque.');
+
+    $html [] = $this->view->renderTable(new Class_TableDescription_CosmoCleanLibraryItems('clean_libraries'), $this->view->libraries);
+
+    return implode($html);
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Cosmo/DeleteItemsAllDashboard.php b/library/ZendAfi/View/Helper/Cosmo/DeleteItemsAllDashboard.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9ded6a3a6fa9045f6674531c0654958a224cdf0
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Cosmo/DeleteItemsAllDashboard.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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 ZendAfi_View_Helper_Cosmo_DeleteItemsAllDashboard
+  extends ZendAfi_View_Helper_BaseHelper {
+
+  use Trait_StormFileSystem;
+
+
+  public function Cosmo_DeleteItemsAllDashboard() : string {
+    return $this->view->deleteAjaxDashboard('delete_all',
+                                            $this->_deleteUrl(),
+                                            $this->_afterDeleteUrl(),
+                                            $this->_addCounterMessage());
+  }
+
+
+  protected function _deleteUrl() : string {
+    return $this->view->url(['action' => 'all',
+                             'step' => 0,
+                             'counter' => '0']);
+  }
+
+
+  protected function _afterDeleteUrl() : string {
+    return $this->view->url(['action' => 'all-dashboard',
+                             'render' => null]);
+  }
+
+
+  protected function _addCounterMessage() : string {
+    return
+      $this->_tagNotice($this->_('Êtes-vous sûr de vouloir supprimer les données suivantes : %s', $this->_modelsCounterList()))
+    . $this->_tagNotice($this->_('et de vider les répertoires suivants : %s', $this->_filesCounterList()));
+  }
+
+
+  protected function _filesCounterList() : string {
+        $list =
+          [$this->_countIn('cache_path'),
+           $this->_countIn('integration_path'),
+           $this->_countIn('log_path')];
+
+    return
+      $this->_tag('ul',
+                  implode(array_map(fn($text) => $this->_tag('li', $text), $list)));
+  }
+
+
+  protected function _modelsCounterList() : string {
+    $list =
+      [$this->_('%d notices', Class_Notice::count()),
+       $this->_('%d notices d\'articles', Class_Notice_SerialArticles::count()),
+       $this->_('%d notices succinctes', Class_NoticeSuccincte::count()),
+       $this->_('%d exemplaires', Class_Exemplaire::count()),
+       $this->_('%d statistiques des notices', Class_StatsNotices::count()),
+       $this->_('%d codifications de tags', Class_CodifTags::count()),
+       $this->_('%d integrations', Class_Cosmogramme_Integration::count()),
+       $this->_('%d analyses d\'intégrations', Class_IntAnalyse::count()),
+       $this->_('%d prêts', Class_Pret::count()),
+       $this->_('%d réservations', Class_Reservation::count())];
+
+    return
+      $this->_tag('ul',
+                  implode(array_map(fn($text) => $this->_tag('li', $text), $list)));
+  }
+
+
+  protected function _countIn(string $key) : string {
+    $path = (string) Class_CosmoVar::get($key);
+    return $this->_('%s (%d fichiers)',
+                    $path,
+                    $this->_countFilesIn($path));
+  }
+
+
+  protected function _countFilesIn(string $path) : int {
+    return count($this->getFileSystem()->fileNamesAt($path));
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Cosmo/DeleteItemsLibraryDashboard.php b/library/ZendAfi/View/Helper/Cosmo/DeleteItemsLibraryDashboard.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c7e9a7b45b99012aa3c4fc3fe4928d1ca58de0c
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Cosmo/DeleteItemsLibraryDashboard.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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 ZendAfi_View_Helper_Cosmo_DeleteItemsLibraryDashboard
+  extends ZendAfi_View_Helper_BaseHelper {
+
+  protected
+    $_count_items,
+    $_count_succinct_records,
+    $_id_bib;
+
+  public function Cosmo_DeleteItemsLibraryDashboard() : string {
+    $this->_id_bib = $this->view->id_bib;
+    $this->_count_items = count(Class_Exemplaire::findAllBy(['id_bib' => $this->_id_bib]));
+    $this->_count_succinct_records = count(Class_NoticeSuccincte::findAllBy(['id_bib' => $this->_id_bib]));
+
+    return $this->view->deleteAjaxDashboard('delete_library',
+                                            $this->_deleteUrl(),
+                                            $this->_afterDeleteUrl(),
+                                            $this->_addCountersMessage());
+  }
+
+
+  protected function _deleteUrl() : string {
+    $count_items = count(Class_Exemplaire::findAllBy(['id_bib' => $this->_id_bib]));
+    $count_succinct_records = count(Class_NoticeSuccincte::findAllBy(['id_bib' => $this->_id_bib]));
+    return $this->view->url(['action' => 'library',
+                             'id' => $this->view->id_bib,
+                             'count_items' => $this->_count_items,
+                             'count_succinct_records' => $this->_count_succinct_records,
+                             'step' => 0,
+                             'counter' => 0]);
+  }
+
+
+  protected function _afterDeleteUrl() : string {
+    return $this->view->url(['action' => 'library-dashboard',
+                             'id' => $this->view->id_bib,
+                             'render' => null]);
+  }
+
+
+  protected function _addCountersMessage() : string {
+    return
+      $this->_tagNotice($this->_('Êtes-vous sûr de vouloir supprimer les données suivantes : %s',
+                                 $this->_modelsCounterList($this->_count_items,
+                                                           $this->_count_succinct_records)));
+  }
+
+
+  protected function _modelsCounterList($count_items, $count_succinct_records) : string {
+    $list =
+      [$this->_('%d exemplaires', $count_items),
+       $this->_('%d notices succinctes', $count_succinct_records)];
+
+    return
+      $this->_tag('ul',
+                  implode(array_map(fn($text) => $this->_tag('li', $text), $list)));
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Cosmo/ItemsCleaner.php b/library/ZendAfi/View/Helper/Cosmo/ItemsCleaner.php
new file mode 100644
index 0000000000000000000000000000000000000000..75201a391e85d9ff4ec2d7032eff885b486c0867
--- /dev/null
+++ b/library/ZendAfi/View/Helper/Cosmo/ItemsCleaner.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Copyright (c) 2012-2022, 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 ZendAfi_View_Helper_Cosmo_ItemsCleaner extends ZendAfi_View_Helper_BaseHelper {
+
+  protected Class_Cosmogramme_ItemsCleaner $_cleaner;
+
+
+  public function Cosmo_ItemsCleaner(Class_Cosmogramme_ItemsCleaner $cleaner) : string {
+    $this->_cleaner = $cleaner;
+
+    if ( null === $nextstep = $this->_cleaner->nextStep())
+      return '';
+
+    $html =
+      $this->_tag('h3', $this->_('Suppression en cours'))
+      . $this->_tagWarning('Pour continuer la suppression, veuillez garder cette page ouverte')
+      . $this->_tagNotice('Vous serez redirigé une fois la suppression terminée')
+      . $this->_countSteps()
+      . $this->_progressBar()
+      . BR
+      . $this->_cancelButton();
+
+    return $this->_div(['class' => 'delete_ajax',
+                        'data-delete-url' => $this->_getNewDeleteUrl($nextstep)],
+                        $html);
+  }
+
+
+  protected function _countSteps() : string {
+    $status_message = $this->_($this->_cleaner->getStepLabel())
+      . ': ' . $this->_cleaner->countDonePercent() . '%';
+
+    return $this->_tagWarning($status_message) ;
+  }
+
+
+  protected function _progressBar() : string {
+    $id = 'progress_bar_ajax_delete';
+
+    $script = sprintf('$("#%s").progressbar({value: %d});',
+                      $id,
+                      $this->_cleaner->countDonePercent());
+
+    Class_ScriptLoader::getInstance()->addJQueryReady($script);
+
+    return $this->_div(['id' => $id], '');
+  }
+
+
+  protected function _getNewDeleteUrl($nextstep) : string {
+    $new_counters_params =
+      ['module' => 'cosmo',
+       'controller' => 'delete-items',
+       'action' => 'library',
+       'id' => $this->_cleaner->idBib(),
+       'count_items' => $this->_cleaner->totalItems(),
+       'count_succinct_records' => $this->_cleaner->totalRecords(),
+       'step' => $nextstep,
+       'counter' => $this->_cleaner->nextCount()];
+
+    return $this->view->url($new_counters_params);
+  }
+
+
+  protected function _cancelButton() : string {
+    return
+      $this->view->button((new Class_Button)
+                          ->setImage($this->view->renderIcon('class fa fa-fire-extinguisher'))
+                          ->setText($this->_('Stopper la suppression'))
+                          ->setAttribs(['onclick' => '',
+                                        'id' => 'stop_delete']));
+  }
+
+}
diff --git a/library/ZendAfi/View/Helper/RenderModelAction.php b/library/ZendAfi/View/Helper/RenderModelAction.php
index 0bf578604c5d8afe099b2978051085a45ba39995..39c8744c934ee5bf15b66b6b3e8f37514c52ecdd 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 = ($model_id = $this->_getModelId($model))
+    $anchorOptions = ('' !== ($model_id = $this->_getModelId($model)))
       ? $this->_initAnchorOptions($model_id, $url)
       : [];
 
diff --git a/library/storm b/library/storm
index 4859dae8444425a97da09b7989a566afbeba4e1a..9c8586c2c3523037428a115cb7060f4e567bc0ab 160000
--- a/library/storm
+++ b/library/storm
@@ -1 +1 @@
-Subproject commit 4859dae8444425a97da09b7989a566afbeba4e1a
+Subproject commit 9c8586c2c3523037428a115cb7060f4e567bc0ab
diff --git a/public/admin/js/delete-album-category/delete-album-category.js b/public/admin/js/ajax-delete/ajax-delete.js
similarity index 85%
rename from public/admin/js/delete-album-category/delete-album-category.js
rename to public/admin/js/ajax-delete/ajax-delete.js
index aedaa3ac2eca6425d061eb7efa700f155f4ba216..da236f752c398b17f4ee4d3f8f83513515e70ffe 100644
--- a/public/admin/js/delete-album-category/delete-album-category.js
+++ b/public/admin/js/ajax-delete/ajax-delete.js
@@ -18,7 +18,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
  */
 (function ( $ ) {
-  $.fn.delete_album_category = function () {
+  $.fn.ajax_delete = function () {
     var html = $(this);
     var after_delete_url = html.attr('data-after-delete-url');
     var url = html.attr('data-delete-url');
@@ -31,11 +31,11 @@
       reloadAfterCloseModalLoading();
       openModalLoading();
       disableModalLoadingMinimize();
-      deleteCategory(url, after_delete_url);
+      callDeleteAction(url, after_delete_url);
     });
     
     
-    function deleteCategory(url, after_delete_url) {
+    function callDeleteAction(url, after_delete_url) {
       if ( ! html.find('#modal-loading-ajax-content').length)
 	return closeModalLoadingAndRedirect(after_delete_url);
 
@@ -46,15 +46,16 @@
 	url: url + '/render/ajax',
 	type: 'GET',
 	dataType: 'html',
-	success : function(response) {return public_functions.onSuccess(response);}
+	success : function(response) {return public_functions.onSuccess(response);},
+	error : function(response) {return closeModalLoadingAndRedirect(after_delete_url);}
       }));
     }
 
     var public_functions = {
       'onSuccess': function(response) {
 	html.find('#modal-loading-ajax-content').html(response);
-	deleteCategory(html.find('.delete_album_category').attr('data-delete-url'),
-		       after_delete_url);
+	callDeleteAction(html.find('[data-delete-url]').attr('data-delete-url'),
+			 after_delete_url);
 	html.find('#stop_delete').off().click(function(event) {
 	  event.preventDefault();
 	  return closeModalLoadingAndRedirect();
diff --git a/public/admin/js/delete-album-category/tests/delete-album-category-tests.html b/public/admin/js/ajax-delete/tests/ajax-delete-tests.html
similarity index 96%
rename from public/admin/js/delete-album-category/tests/delete-album-category-tests.html
rename to public/admin/js/ajax-delete/tests/ajax-delete-tests.html
index e5e9d5ad9afd96178a9aaf09f5021736f0c0562c..ff2c36f76303a8f9e205896e3e6a2ecebda5ab7b 100644
--- a/public/admin/js/delete-album-category/tests/delete-album-category-tests.html
+++ b/public/admin/js/ajax-delete/tests/ajax-delete-tests.html
@@ -29,8 +29,8 @@
     <script src="../../jquery-3.6.0.min.js"></script>
     <script src="../../../../qunit-1.13.0.js"></script>
     <script src="../../modal-loading/modal-loading.js"></script>
-    <script src="../delete-album-category.js"></script>
-    <script src="delete-album-category-tests.js"></script>
+    <script src="../ajax-delete.js"></script>
+    <script src="ajax-delete-tests.js"></script>
   </head>
   <body>
     <div id="qunit"></div>
diff --git a/public/admin/js/delete-album-category/tests/delete-album-category-tests.js b/public/admin/js/ajax-delete/tests/ajax-delete-tests.js
similarity index 96%
rename from public/admin/js/delete-album-category/tests/delete-album-category-tests.js
rename to public/admin/js/ajax-delete/tests/ajax-delete-tests.js
index 909bd9204907b210cbc55868a51b07160bcb134b..8e41465e3de5837e83763ee4eb4f1c6ba0372255 100644
--- a/public/admin/js/delete-album-category/tests/delete-album-category-tests.js
+++ b/public/admin/js/ajax-delete/tests/ajax-delete-tests.js
@@ -30,7 +30,7 @@ testStart( function() {
 
 
 test('click on run delete should add ajax call to queue', function() {
-  $("#delete_album_category_dashboard_14").delete_album_category();
+  $("#delete_album_category_dashboard_14").ajax_delete();
   closeModalLoadingAndRedirect = function () {};
   $('#run_delete').click();
   deepEqual(ajax_call_queue.length, 1);
@@ -39,7 +39,7 @@ test('click on run delete should add ajax call to queue', function() {
 
 test('delete album category on success function should inject response in modal-loading ajax-content', function() {
   $("#delete_album_category_dashboard_14")
-    .delete_album_category()
+    .ajax_delete()
     .onSuccess('<div class="delete_album_category" data-delete-url="/admin/album/run-delete-category/id/1/count_albums/0/count_sub_categories/0/count_records/0/render/ajax/count_deleted_albums/0/count_deleted_records/0/count_deleted_sub_categories/0" data-count-remaining-albums="0" data-count-remaining-sub-categories="0" data-count-remaining-records="0">'
 	       + '<h3>Suppression en cours</h3>'
 	       + '<p class="warning" title="Attention: &quot;Pour continuer la suppression, veuillez garder cette page ouverte&quot;. Ce message vous indique une incompl&eacute;tude.">Pour continuer la suppression, veuillez garder cette page ouverte</p>'
diff --git a/public/admin/js/modal-loading/modal-loading.css b/public/admin/js/modal-loading/modal-loading.css
index 62ff74e63c4b80e0e689cd138e2e6f11add2cef2..8e0e42e5b5802ce5d50c0b93b6b674e71b7650dc 100644
--- a/public/admin/js/modal-loading/modal-loading.css
+++ b/public/admin/js/modal-loading/modal-loading.css
@@ -17,8 +17,12 @@
     transition: height 0.4s, width 0.4s;
 }
 
+.modal-loading #modal-loading-ajax-content > * {
+    text-align: center !important;
+}
+
 .modal-loading #modal-loading-ajax-content,
-.modal-loading #modal-loading-ajax-content .delete_album_category,
+.modal-loading #modal-loading-ajax-content .delete_ajax,
 .modal-loading img {
     position: absolute;
     top: 0;
@@ -57,23 +61,24 @@
 }
 
 
-.modal-loading #modal-loading-ajax-content .delete_album_category {
+.modal-loading #modal-loading-ajax-content .delete_ajax {
     width: 300px;
-    height: 340px;
+    height: 400px;
     overflow: hidden;
     border: 1px solid var(--border-highlight);
     border-radius: 3px;
     background-color: var(--button-highlight);
 }
 
-.modal-loading.minimize #modal-loading-ajax-content .delete_album_category {
+.modal-loading.minimize #modal-loading-ajax-content .delete_ajax {
     left: auto;
     bottom: auto;
 }
 
-
-.modal-loading #modal-loading-ajax-content .delete_album_category h3 {
-    margin-top : 0;
+.modal-loading #modal-loading-ajax-content .delete_ajax h3 {
+    margin-top: 0;
+    padding-top: 1em;
+    padding-bottom: 1em;
 }
 
 .modal-loading .ui-progressbar {
diff --git a/tests/application/modules/admin/controllers/AlbumControllerDeleteCategoryTest.php b/tests/application/modules/admin/controllers/AlbumControllerDeleteCategoryTest.php
index 76284135a615ca00b744d3842a17409138d01ae8..d0e1153a5257529c4b0791ecc0b4889c162f7e2f 100644
--- a/tests/application/modules/admin/controllers/AlbumControllerDeleteCategoryTest.php
+++ b/tests/application/modules/admin/controllers/AlbumControllerDeleteCategoryTest.php
@@ -127,7 +127,7 @@ class AlbumControllerDeleteCategoryDispatchActionTest
 
   /** @test */
   public function pageShouldContainsScriptPublicAdminDeleteNodeJs() {
-    $this->assertXPath('//script[contains(@src, "/public/admin/js/delete-album-category/delete-album-category.js")]');
+    $this->assertXPath('//script[contains(@src, "/public/admin/js/ajax-delete/ajax-delete.js")]');
   }
 
 
@@ -145,7 +145,7 @@ class AlbumControllerDeleteCategoryDispatchActionTest
 
   /** @test */
   public function shouldContainsJQueryReadyDeleteAlbumCategoryOnDashboard() {
-    $this->assertXPathContentContains('//script', '$(function(){$("#delete_album_category_dashboard_2").delete_album_category();});');
+    $this->assertXPathContentContains('//script', '$(function(){$("#delete_album_category_dashboard_2").ajax_delete();});');
   }
 
 
@@ -180,7 +180,7 @@ class AlbumControllerDeleteCategoryAjaxDeleteCategoryDispatchTest
 
   /** @test */
   public function shouldReturnDivClassDeleteAlbumCategoryWithNewCounters() {
-    $this->assertXPath('//div[@class="delete_album_category"][contains(@data-delete-url, "/count_deleted_albums/20/count_deleted_records/20/count_deleted_sub_categories/1")]');
+    $this->assertXPath('//div[@class="delete_ajax delete_album_category"][contains(@data-delete-url, "/count_deleted_albums/20/count_deleted_records/20/count_deleted_sub_categories/1")]');
   }
 
 
@@ -227,7 +227,6 @@ class AlbumControllerDeleteCategoryAjaxDeleteCategoryRenderAjaxDispatchTest
 class AlbumControllerDeleteCategoryAjaxDeleteCategoryNextRunDispatchTest
   extends AlbumControllerDeleteCategoryTestCase {
 
-
   public function setUp() {
     parent::setUp();
 
@@ -246,33 +245,33 @@ class AlbumControllerDeleteCategoryAjaxDeleteCategoryNextRunDispatchTest
 
   /** @test */
   public function shouldReturnDivClassDeleteAlbumCategoryWithCumulatedCounters() {
-    $this->assertXPath('//div[@class="delete_album_category"][@data-delete-url= "/admin/album/run-delete-category/id/2/count_deleted_albums/40/count_deleted_records/40/count_deleted_sub_categories/2"][@data-count-remaining-albums="20"][@data-count-remaining-sub-categories="0"][@data-count-remaining-records="20"]');
+    $this->assertXPath('//div[@class="delete_ajax delete_album_category"][@data-delete-url= "/admin/album/run-delete-category/id/2/count_deleted_albums/40/count_deleted_records/40/count_deleted_sub_categories/2"][@data-count-remaining-albums="20"][@data-count-remaining-sub-categories="0"][@data-count-remaining-records="20"]');
   }
 
 
   /** @test */
   public function titleH3ShouldContainsSuppressionEnCours() {
-    $this->assertXPathContentContains('//div[@class="delete_album_category"]/h3', 'Suppression en cours');
+    $this->assertXPathContentContains('//div[@class="delete_ajax delete_album_category"]/h3', 'Suppression en cours');
   }
 
 
   /** @test */
   public function paragrapheWarningShouldContainsKeepPageOpenMessage() {
-    $this->assertXPathContentContains('//div[@class="delete_album_category"]/p[@class="warning"]',
+    $this->assertXPathContentContains('//div[@class="delete_ajax delete_album_category"]/p[@class="warning"]',
                                       'Pour continuer la suppression, veuillez garder cette page ouverte');
   }
 
 
   /** @test */
   public function paragrapheNoticeShouldContainsRedirectWhenJobIsDoneMessage() {
-    $this->assertXPathContentContains('//div[@class="delete_album_category"]/p[@class="notice"]',
+    $this->assertXPathContentContains('//div[@class="delete_ajax delete_album_category"]/p[@class="notice"]',
                                       'Vous serez redirigé sur la liste des collections une fois la suppression terminée');
   }
 
 
   /** @test */
   public function buttonStopProcessShouldBeDisplay() {
-    $this->assertXPathContentContains('//div[@class="delete_album_category"]/button[@id="stop_delete"]',
+    $this->assertXPathContentContains('//div[@class="delete_ajax delete_album_category"]/button[@id="stop_delete"]',
                                       'Stopper la suppression');
   }
-}
\ No newline at end of file
+}
diff --git a/tests/js/DeleteAlbumCategoryTest.php b/tests/js/DeleteAlbumCategoryTest.php
index 92c199ee4324531f81f5feac8447909fa878690b..5b7223e6d12942ca686d94b148d572f6a29650ff 100644
--- a/tests/js/DeleteAlbumCategoryTest.php
+++ b/tests/js/DeleteAlbumCategoryTest.php
@@ -21,5 +21,5 @@
 
 
 class DeleteAlbumCategoryTest extends BrowserTest {
-  protected $_test_path = 'public/admin/js/delete-album-category/tests/delete-album-category-tests.html';
+  protected $_test_path = 'public/admin/js/ajax-delete/tests/ajax-delete-tests.html';
 }