diff --git a/VERSIONS_WIP/64203 b/VERSIONS_WIP/64203
new file mode 100644
index 0000000000000000000000000000000000000000..6aa97dafcf2fe4c4d304fd1463b2faaebc0bb67b
--- /dev/null
+++ b/VERSIONS_WIP/64203
@@ -0,0 +1,2 @@
+ - ticket #64203 : Administration : ajout d'une interface de gestion des doublons d'utilsateurs.
+ 
\ No newline at end of file
diff --git a/application/modules/admin/controllers/UsersController.php b/application/modules/admin/controllers/UsersController.php
index 72f3753164d161305e965e69a8add42b392a0f95..c9570c46edc08a7f812659989e93216d3f9e4438 100644
--- a/application/modules/admin/controllers/UsersController.php
+++ b/application/modules/admin/controllers/UsersController.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ * 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
@@ -62,5 +62,88 @@ class Admin_UsersController extends ZendAfi_Controller_Action {
     $settings = Class_User_Settings::newWith($user);
     $settings->set($this->_getParam('key'), $this->_getParam('value'));
     $user->save();
-   }
+  }
+
+
+  public function manageDoubleAction() {
+    $this->view->titre = $this->_('Gestion des doublons d\'abonnés');
+    $this->view->double_manager = new Class_User_DbDoubleFinder;
+
+    if(!$this->view->double_manager->hasDouble()) {
+      $this->_helper->notify($this->_('Aucun doublon d\'abonné dans votre base de données'));
+      return $this->_redirectToIndex();
+    }
+  }
+
+
+  public function manageDoubleManualAction() {
+    $this->view->titre = $this->_('Gestion manuelle des doublons d\'abonnés');
+    $this->view->double_manager = new Class_User_DbDoubleFinder;
+
+    if(!$this->view->double_manager->hasDouble()) {
+      $this->_helper->notify($this->_('Aucun doublon d\'abonné dans votre base de données'));
+      return $this->_redirectToIndex();
+    }
+  }
+
+
+  public function deleteDoubleAction() {
+    $total = (int) $this->_getParam('total', 0);
+    $cursor = (int) $this->_getParam('cursor', 0);
+
+    $db_double_finder = new Class_User_DbDoubleFinder;
+    $new_cursor = $db_double_finder->dedupeUsers($cursor);
+
+    $continue = $new_cursor
+      ? true
+      : false;
+
+    if(!$continue)
+      $this->_helper->notify($this->_('Le dédoublonnage automatique des doublons est terminé.'));
+
+    $done = $total - $db_double_finder->countDouble();
+
+    return $this->_helper->progressbar($total, $done, $new_cursor, $continue);
+  }
+
+
+  public function manageDoubleUserAction() {
+    if(!$id_user = $this->_getParam('id_user', null)) {
+      $this->_helper->notify($this->_('ID_USER invalide'));
+      return $this->_redirectToIndex();
+    }
+
+    if(!$user = Class_Users::find($id_user)) {
+      $this->_helper->notify($this->_('Utilisateur invalide'));
+      return $this->_redirectToIndex();
+    }
+
+    $this->view->double_manager = new Class_User_DbDoubleFinder;
+    $this->view->users = $this->view->double_manager->findDoubleFor($user);
+    $this->view->titre = $this->_('Gestion manuelle des doublon pour l\'ID ABON : %s', $user->getIdabon());
+    $this->view->id_user = $id_user;
+    $this->view->next = $this->view->double_manager->getNextDouble($id_user);
+    $this->view->previous = $this->view->double_manager->getPreviousDouble($id_user);
+  }
+
+
+  public function manageDoubleMergeAction() {
+
+    if ((!$users_from = $this->_getParam('id_user_from',null))
+         || (!$user_to = Class_Users::find($this->_getParam('id_user_to',null)))) {
+      $this->_helper->notify($this->_('id_users invalides'));
+      return $this->_redirectToIndex();
+    }
+    $double_manager = new Class_User_DbDoubleFinder;
+    $next = $double_manager->getNextDouble($this->_getParam('id_user'));
+
+    if (!$double_manager->mergeUsersInto($user_to, Class_Users::findAllBy(['id_user' => explode('_',$users_from)]))) {
+      $this->_helper->notify($this->_('Echec de la migration'));
+      return $this->_redirectToIndex();
+    }
+
+    $this->_helper->notify($this->_('La migration pour l\'utilisateur %s a été effectué.',$user_to->getLogin()));
+    return $this->_redirect('/admin/users/manage-double-user/id_user/'.$next);
+  }
+
 }
diff --git a/application/modules/admin/views/scripts/users/index.phtml b/application/modules/admin/views/scripts/users/index.phtml
index 22a26d3d9f8ea923421f4bfe4e7b0f15c0467d06..25072612d2a4aaee996c7a49ed623697121d1da2 100644
--- a/application/modules/admin/views/scripts/users/index.phtml
+++ b/application/modules/admin/views/scripts/users/index.phtml
@@ -1,43 +1,26 @@
 <?php
 if(Class_Users::getIdentity()->isAdmin())
   echo $this->Button_New((new Class_Entity())
-                             ->setText($this->_('Ajouter un utilisateur')));
+                         ->setText($this->_('Ajouter un utilisateur')));
 
-$map =[['url' => ['module' => 'admin',
-                  'controller' => 'users',
-                  'action' => 'edit',
-                       'id' => '%s'],
-        'icon' => 'edit',
-        'label' => $this->_('modifier')],
+$double_finder = (new Class_User_DbDoubleFinder);
+$has_double = $double_finder->hasDouble();
 
-       ['url' => ['module' => 'admin',
-                  'controller' => 'users',
-                  'action' => 'delete',
-                  'id' => '%s'],
-        'icon' => 'delete',
-        'label' => $this->_('supprimer')],
-
-       ['url' => ['module' => 'opac',
-                  'controller' => 'blog',
-                  'action' => 'viewauteur',
-                  'id' => '%s'],
-        'icon' => 'star',
-        'label' => $this->_('avi(s)'),
-        'condition' => 'hasAvis',
-        'anchorOptions' => ['target' => '_blank']],
-
-       ['url' => ['module' => 'opac',
-                  'controller' => 'panier',
-                  'action' => 'viewauteur',
-                  'id' => '%s'],
-        'icon' => 'basket',
-        'label' => $this->_('panier(s)'),
-        'condition' => 'hasPaniers',
-        'anchorOptions' => ['target' => '_blank']]];
-
-$actions = function($user) use($map) {
-  return $this->renderModelActions($user,$map);
-};
+echo $this->button(
+                   (new Class_Entity())
+                   ->setText($this->_('Gérer les doublons'))
+                   ->setAttribs($has_double
+                                ? ['title' => $this->_('Il y a %s doublons', $double_finder->countDouble())]
+                                : ['title' => $this->_('Il n\'y a pas de doublon'),
+                                   'disabled' => 'disabled'])
+                   ->setUrl($this->url(['module' => 'admin',
+                                        'controller' => 'users',
+                                        'action' => 'manage-double'],
+                                       null, true))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                            ->getIconUrl('actions',
+                                                         'users'),
+                                            ['style' => 'filter: invert();'])));
 
 
 echo $this->Admin_SearchUsers($this->users,
@@ -45,4 +28,7 @@ echo $this->Admin_SearchUsers($this->users,
                               $this->form,
                               $this->page,
                               $this->params,
-                              $actions);
+                              function($model)
+                              {
+                                return $this->renderPluginsActions($model);
+                              });
diff --git a/application/modules/admin/views/scripts/users/manage-double-manual.phtml b/application/modules/admin/views/scripts/users/manage-double-manual.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..a9dac65ca9474f5c661077176837203c802bba60
--- /dev/null
+++ b/application/modules/admin/views/scripts/users/manage-double-manual.phtml
@@ -0,0 +1,13 @@
+<?php
+echo $this->tag('p',
+                $this->_('Vous avez %s comptes abonnés possédant au moins un doublon dans votre base de données qui ne peuvent pas être supprimés automatiquement.',
+                         $this->double_manager->countDouble()));
+
+echo $this->tag('p',
+                $this->tag('small',
+                           $this->_('Requête d\'identification des doublons : %s', $this->tag('b', $this->double_manager->getRequest()))));
+
+echo $this->button((new Class_Entity())
+                     ->setUrl($this->url(['action' => 'manage-double-user',
+                                          'id_user' => $this->double_manager->getFirstDouble()]))
+                   ->setText($this->_('Gérer manuellement les doublons')));
diff --git a/application/modules/admin/views/scripts/users/manage-double-user.phtml b/application/modules/admin/views/scripts/users/manage-double-user.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..8b9e94368b55cc11e61d851b47e05bebda7616b6
--- /dev/null
+++ b/application/modules/admin/views/scripts/users/manage-double-user.phtml
@@ -0,0 +1,99 @@
+<?php
+
+echo $this->tag('p',
+                $this->_('Il reste %s comptes abonnés possédant au moins un doublon dans votre base de données.',
+                         $this->tag('b', $this->double_manager->countDouble())));
+
+
+$description = (new Class_TableDescription('double-users'))
+  ->addColumn($this->_('ID'), ['attribute' => 'id_user'])
+  ->addColumn($this->_('Login'), ['attribute' => 'login'])
+  ->addColumn($this->_('Nom'), ['attribute' => 'nom'])
+  ->addColumn($this->_('Prenom'), ['attribute' => 'prenom'])
+  ->addColumn($this->_('Email'), ['attribute' => 'mail'])
+  ->addColumn($this->_('Bibliothèque'), ['attribute' => 'libelleBib'])
+  ->addColumn($this->_('Expiration de l\'abo'), function($user)
+              {
+                return Class_Date::humanDate($user->getDateFin(), 'dd MMMM yyyy');
+              })
+  ->addColumn($this->_('Exporté par le SIGB'), function($model)
+              {
+                return $model->getStatut()
+                  ? $this->_('Non')
+                  : $this->_('Oui');
+              })
+  ->addRowAction(function ($model)
+                 {
+                   $id_users = (new Storm_Model_Collection($this->users))
+                     ->collect('id_user')
+                     ->getArrayCopy();
+                   $id_users = array_diff($id_users, [$model->getId()]);
+                   return $this->button((new Class_Entity())
+                                        ->setUrl($this->url(['module' => 'admin',
+                                                             'controller' => 'users',
+                                                             'action' => 'manage-double-merge',
+                                                             'id_user_to' => $model->getIdUser(),
+                                                             'id_user_from' => implode('_', $id_users)]))
+
+                                        ->setText($this->_('Conserver')));});
+
+echo $this->renderTable($description,
+                        $this->users,
+                        ['sorter' => true]);
+
+$attribs = [];
+if ($this->previous == '')
+  $attribs = ['disabled' => 'disabled'];
+
+echo $this->button((new Class_Entity())
+                   ->setUrl($this->url(['module' => 'admin',
+                                        'controller' => 'users',
+                                        'action' => 'index'
+                                        ]))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                            ->getIconUrl('actions',
+                                                         'back'),
+                                            ['style' => 'filter: invert();']))
+                   ->setText($this->_('Retour à la gestion des utilisateurs')));
+
+echo $this->button((new Class_Entity())
+                   ->setUrl($this->url(['module' => 'admin',
+                                        'controller' => 'users',
+                                        'action' => 'manage-double'
+                                        ]))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                        ->getIconUrl('actions',
+                                                     'back'),
+                                            ['style' => 'filter: invert();']))
+                   ->setText($this->_('Retour à la gestion des doublons')));
+
+echo $this->button((new Class_Entity())
+                   ->setUrl($this->url(['module' => 'admin',
+                                        'controller' => 'users',
+                                        'action' => 'manage-double-user',
+                                        'id_user' => $this->previous]
+                                       ))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                        ->getIconUrl('actions',
+                                                     'down'),
+                                            ['style' => 'filter: invert(); transform: rotate(90deg);']))
+
+                   ->setText($this->_('Précédent'))
+                   ->setAttribs($attribs));
+
+$attribs = [];
+if ($this->next == '')
+  $attribs = ['disabled' => 'disabled'];
+
+echo $this->button((new Class_Entity())
+                   ->setUrl($this->url(['module' => 'admin',
+                                        'controller' => 'users',
+                                        'action' => 'manage-double-user',
+                                        'id_user' => $this->next]
+                                       ))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                        ->getIconUrl('actions',
+                                                     'down'),
+                                            ['style' => 'filter: invert(); transform: rotate(270deg);']))
+
+                   ->setText($this->_('Ignorer et continuer')));
diff --git a/application/modules/admin/views/scripts/users/manage-double.phtml b/application/modules/admin/views/scripts/users/manage-double.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..36d21803f3af86eca94fcc23cb8992ea879ea055
--- /dev/null
+++ b/application/modules/admin/views/scripts/users/manage-double.phtml
@@ -0,0 +1,45 @@
+<?php
+echo $this->tag('p',
+                $this->_('Vous avez %s comptes abonnés possédant au moins un doublon dans votre base de données.',
+                         $this->tag('b', $this->double_manager->countDouble())));
+
+echo $this->button((new Class_Entity())
+                   ->setUrl($this->url(['module' => 'admin',
+                                        'controller' => 'users',
+                                        'action' => 'index'
+                                        ]))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                        ->getIconUrl('actions',
+                                                     'back'),
+                                            ['style' => 'filter: invert();']))
+                   ->setText($this->_('Retour à la gestion des utilisateurs')));
+
+echo $this->button((new Class_ButtonDescription())
+                   ->beProgressbar($this, Class_Url::relative('/admin/users/delete-double'),
+                                   $this->double_manager->countDouble(),
+                                   $this->_('Lancer le dédoublonnage automatique')));
+
+
+echo $this->button((new Class_Entity())
+                   ->setUrl($this->url(['action' => 'manage-double-user',
+                                        'id_user' => $this->double_manager->getFirstDouble()]))
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                        ->getIconUrl('actions',
+                                                     'users'),
+                                            ['style' => 'filter: invert();']))
+
+                   ->setText($this->_('Gérer manuellement les doublons')));
+
+
+echo $this->button((new Class_Entity())
+                     ->setAttribs([
+                                   'onclick' => '$(this).next().toggle();'])
+                   ->setImage($this->tagImg(Class_Admin_Skin::current()
+                                            ->getIconUrl('actions',
+                                                         'help'),
+                                            ['style' => 'filter: invert();']))
+                   ->setText($this->_('Voir la requete')));
+
+echo  $this->tag('pre',
+                 $this->double_manager->getRequest(),
+                 ['style' => 'display: none;']);
diff --git a/library/Class/Admin/Skin.php b/library/Class/Admin/Skin.php
index 9765371c682d45fa802f4dbee0944f269bea4b99..c63b83785e96abe8cca3333a7bcd2b5875deeab7 100644
--- a/library/Class/Admin/Skin.php
+++ b/library/Class/Admin/Skin.php
@@ -143,7 +143,8 @@ class Class_Admin_Skin {
 
 
   public function renderButtonCssOn($script_loader) {
-    return $this->_renderCssOn($script_loader, 'buttons.css');
+    return $this->_renderCssOn($script_loader, 'buttons.css')
+                ->renderJQueryCssOn($script_loader);
   }
 
 
diff --git a/library/Class/ButtonDescription.php b/library/Class/ButtonDescription.php
new file mode 100644
index 0000000000000000000000000000000000000000..c55089245620a6a5ece5d93cc4b541b3910a7faf
--- /dev/null
+++ b/library/Class/ButtonDescription.php
@@ -0,0 +1,41 @@
+<?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_ButtonDescription extends Class_Entity {
+  public function beProgressbar($view, $url, $total, $text) {
+
+    Class_ScriptLoader::getInstance()->addAdminScript('progressbar.js');
+    $id = md5(implode(',',$this->_attribs));
+    $this->setAttribs([
+                       'id' => $id,
+                       'onclick' => sprintf('progressbar(\'%s\',\'%s\', %d);',
+                                            $id, $url,$total)
+                       ])
+         ->setImage($view->tagImg(Class_Admin_Skin::current()
+                                  ->getIconUrl('actions',
+                                               'test'),
+                                  ['style' => 'filter: invert();']))
+         ->setText($text.$view->tag('span', ''));
+    return $this;
+  }
+}
+?>
\ No newline at end of file
diff --git a/library/Class/User/Datas.php b/library/Class/User/Datas.php
index 2f80c2d4b076ad809c7783d68a63773024c30e5c..aad8a96adaac3817a0a88d96e091c3c2ef2dda30 100644
--- a/library/Class/User/Datas.php
+++ b/library/Class/User/Datas.php
@@ -20,20 +20,58 @@
  */
 
 
-class Class_User_Datas extends Class_Entity {
-  protected $_relations = [];
+class Class_User_Datas {
+  protected
+    $_attribs = [],
+    $_relations = [];
+
+
+  public static function hasDataInIds($ids) {
+    $instance = new static(null);
+    return $instance->hasDataIn($ids);
+  }
+
 
   public function __construct($user) {
     $this->setUser($user);
 
-    $this->_relations = [new Class_User_DatasRelation('notices_paniers', 'id_user'),
+    $this->_relations = [
+                         new Class_User_DatasRelation('notices_paniers', 'id_user'),
+                         new Class_User_DatasRelation('user_group_memberships', 'user_id'),
                          new Class_User_DatasRelationAvisNotice(),
                          new Class_User_DatasRelation('cms_avis', 'id_user'),
+                         new Class_User_DatasRelation('linked_cards', 'parent_id'),
+                         new Class_User_DatasRelation('linked_cards', 'child_id'),
+                         new Class_User_DatasRelation('session_activity_inscriptions', 'stagiaire_id'),
                          new Class_User_DatasRelation('suggestion_achat', 'user_id'),
-                         new Class_User_DatasRelation('session_formation_inscriptions', 'stagiaire_id'),
-                         new Class_User_DatasRelation('user_group_memberships', 'user_id'),
                          new Class_User_DatasRelation('formulaires', 'id_user'),
-                         new Class_User_DatasRelation('multimedia_devicehold', 'id_user')];
+                         new Class_User_DatasRelation('multimedia_devicehold', 'id_user')
+                         ];
+  }
+
+
+  public function hasDataIn($ids) {
+    foreach($this->_relations as $relation)
+      if (0 < $this->_countIdsContainningData($relation, $ids))
+        return true;
+
+    return false;
+  }
+
+
+  protected function _countIdsContainningData($relation, $ids) {
+    return $relation->countIdsContainingData($ids);
+  }
+
+
+  public function setUser($user) {
+    $this->_attribs['User'] = $user;
+    return $this;
+  }
+
+
+  public function getUser() {
+    return $this->_attribs['User'];
   }
 
 
@@ -85,11 +123,25 @@ class Class_User_Datas extends Class_Entity {
 
     return current($targets);
   }
+
+
+  public function setError($key) {
+    $this->_attribs['Error'] = $key;
+    return $this;
+  }
+
+
+  public function getError() {
+    return $this->_attribs['Error'];
+  }
 }
 
 
 
-class Class_User_DatasRelation extends Class_Entity {
+
+class Class_User_DatasRelation {
+  protected $_attribs;
+
   public function __construct($table, $key) {
     $this->setTable($table)
          ->setKey($key)
@@ -97,6 +149,39 @@ class Class_User_DatasRelation extends Class_Entity {
   }
 
 
+  public function setTable($table) {
+    $this->_attribs['Table'] = $table;
+    return $this;
+  }
+
+
+  public function getTable() {
+    return $this->_attribs['Table'];
+  }
+
+
+  public function setSql($sql) {
+    $this->_attribs['Sql'] = $sql;
+    return $this;
+  }
+
+
+  public function getSql() {
+    return $this->_attribs['Sql'];
+  }
+
+
+  public function setKey($key) {
+    $this->_attribs['Key'] = $key;
+    return $this;
+  }
+
+
+  public function getKey() {
+    return $this->_attribs['Key'];
+  }
+
+
   public function countFor($user) {
     return $this
       ->getSql()
@@ -105,6 +190,16 @@ class Class_User_DatasRelation extends Class_Entity {
   }
 
 
+  public function countIdsContainingData($ids) {
+    return $this
+      ->getSql()
+      ->fetchOne(sprintf('select count(id) from %s where %s in (%s)',
+                         $this->getTable(),
+                         $this->getKey(),
+                         implode(',', $ids)));
+  }
+
+
   public function giveFromTo($from, $to) {
     $this
       ->getSql()
diff --git a/library/Class/User/DbDoubleFinder.php b/library/Class/User/DbDoubleFinder.php
new file mode 100644
index 0000000000000000000000000000000000000000..e99195cbd1ae11039e494597301899d12c1b31f8
--- /dev/null
+++ b/library/Class/User/DbDoubleFinder.php
@@ -0,0 +1,236 @@
+<?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_User_DbDoubleFinder {
+
+  const LIMIT_DEDUPE = 10;
+
+
+  public function getRequest($cursor = 0) {
+    return sprintf('select min(id_user) as id_user, password, login, idabon, ordreabon, count(*) as doublon from bib_admin_users where role_level = 2 and id_user > %s group by idabon, ordreabon, login, password having doublon > 1 order by id_user asc',
+                   $cursor);
+  }
+
+
+  public function getDouble($cursor = 0) {
+    return Zend_Registry::get('sql')
+      ->fetchAll($this->getRequest($cursor));
+  }
+
+
+  public function hasDouble() {
+    return $this->countDouble() > 0;
+  }
+
+
+  public function countDouble() {
+    return count($this->getDouble());
+  }
+
+
+  public function getFirstDoubleInLimit($cursor = 0) {
+    $double = $this->getDouble($cursor);
+    return array_slice($double, 0, static::LIMIT_DEDUPE);
+  }
+
+
+  public function findDoubleFor($user) {
+    return $this->_findUsersWithParams($user->getIdabon(),
+                                        $user->getLogin(),
+                                        $user->getOrdreabon(),
+                                        $user->getPassword());
+  }
+
+
+  public function hasDoubleFor($user) {
+    return 1 < count($this->findDoubleFor($user));
+  }
+
+
+  public function getFirstDouble() {
+    $ids = $this->_getUserIdsFromDouble();
+    return array_shift($ids);
+  }
+
+
+  public function getNextDouble($id_user) {
+    $ids  = $this->_getUserIdsFromDouble();
+
+    if(false === ($position = array_search($id_user, $ids)))
+      return '';
+
+    $next = $position + 1;
+    return isset($ids[$next])
+      ? $ids[$next]
+      : '';
+  }
+
+
+  public function getPreviousDouble($id_user) {
+    $ids  = $this->_getUserIdsFromDouble();
+
+    if(false === ($position = array_search($id_user, $ids)))
+      return '';
+
+    $previous = $position - 1;
+    return isset($ids[$previous])
+      ? $ids[$previous]
+      : '';
+  }
+
+
+  public function dedupeUsers($cursor = 0) {
+    $double = $this->getFirstDoubleInLimit($cursor);
+    foreach($double as $data)
+      $this->_autoMergeOrDelete($this->_findUsersFromDouble($data));
+
+    $last = array_pop($double);
+
+    if(empty($last))
+      return null;
+
+    return isset($last['id_user'])
+      ? (int) $last['id_user']
+      : null;
+  }
+
+
+  protected function _autoMergeOrDelete($users) {
+    if(!$user = $this->_findUserToKeep($users))
+      return $this->_autoDeleteDoubles($users);
+
+    return $this->mergeUsersInto($user, $users);
+  }
+
+
+  public function mergeUsersInto($user, $users) {
+    $users = array_filter($users, function($instance) use ($user)
+                                    {
+                                      return $instance->getId() !== $user->getId();
+                                    });
+
+    foreach ($users as $deletable_user) {
+      $datas = new Class_User_Datas($deletable_user);
+
+      if (!$datas->giveTo(['id_user' => $user->getIdUser()], true))
+        return;
+
+      $deletable_user->delete();
+    }
+
+    return true;
+  }
+
+
+  protected function _findUsersFromDouble($data) {
+    return $this->_findUsersWithParams($data['idabon'],
+                                       $data['login'],
+                                       $data['ordreabon'],
+                                       $data['password']);
+  }
+
+
+  protected function _findUsersWithParams($idabon, $login, $ordreabon, $password) {
+    return Class_Users::findAllBy(['idabon' => $idabon,
+                                   'password' => $password,
+                                   'login' => $login,
+                                   'ordreabon' => $ordreabon]);
+  }
+
+
+  protected function _findUserToKeep($users) {
+    $last_name = '';
+    $first_name ='';
+    $status = null;
+
+    $keep_user = null;
+    foreach($users as $user) {
+      if ((!$last_name = $this->_userAsSame($user->getNom(), $last_name))
+          || (!$first_name = $this->_userAsSame($user->getPrenom(), $first_name)))
+        return;
+
+      if (!$user->hasDateFin())
+        continue;
+
+      if ($user->hasToBeDelete())
+        continue;
+
+      if ($keep_user)
+        return;
+
+      $keep_user = $user;
+    }
+
+    return $this->_hasGreaterDates($keep_user, $users);
+  }
+
+
+  protected function _userAsSame($name, $expected_name) {
+    $name = Class_Indexation::getInstance()->alphaMaj($name);
+
+    if ($expected_name == '')
+      $expected_name = $name;
+
+    return $expected_name == $name
+      ? $name
+      : false;
+  }
+
+
+  protected function _hasGreaterDates($user, $users) {
+    if(!$user)
+      return null;
+
+    foreach($users as $other) {
+      if (strtotime($other->getDateFin()) > strtotime($user->getDateFin()))
+        return null;
+    }
+
+    return $user;
+  }
+
+
+  protected function _autoDeleteDoubles($users) {
+    if(!$ids = $this->_getDoubleUsersWithOutDatas($users))
+      return;
+
+    return Zend_Registry::get('sql')
+      ->query(sprintf('delete from bib_admin_users where id_user in (%s)', implode(',',$ids)));
+  }
+
+
+  protected function _getDoubleUsersWithOutDatas($users) {
+    $ids = (new Storm_Model_Collection($users))
+      ->collect('id_user')
+      ->getArrayCopy();
+
+    if(Class_User_Datas::hasDataInIds($ids))
+      return null;
+
+    return $ids;
+  }
+
+
+  protected function _getUserIdsFromDouble() {
+    return array_column($this->getDouble(), 'id_user');
+  }
+}
\ No newline at end of file
diff --git a/library/Class/Users.php b/library/Class/Users.php
index c5c17052a5216d40e2db294fbf960da88f0108cd..70e0e32881c4b74d63031722387c83317e7d86c4 100644
--- a/library/Class/Users.php
+++ b/library/Class/Users.php
@@ -1859,4 +1859,9 @@ class Class_Users extends Storm_Model_Abstract {
   public function hasPaniers() {
     return (0 < $this->numberOfPaniers());
   }
+
+
+  public function hasToBeDelete() {
+    return static::STATUT_TO_BE_DELETED == $this->getStatut();
+  }
 }
diff --git a/library/ZendAfi/Controller/Action/Helper/Progressbar.php b/library/ZendAfi/Controller/Action/Helper/Progressbar.php
new file mode 100644
index 0000000000000000000000000000000000000000..df7391cc8f97f5869cd47d4323cae4d87ef28bd4
--- /dev/null
+++ b/library/ZendAfi/Controller/Action/Helper/Progressbar.php
@@ -0,0 +1,38 @@
+<?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 ZendAfi_Controller_Action_Helper_Progressbar extends Zend_Controller_Action_Helper_Abstract {
+  public function progressbar($total, $done, $cursor, $run) {
+    $this->getActionController()
+         ->getHelper('json')
+         ->direct( ['total' => $total,
+                    'done' => $done,
+                    'cursor' => $cursor,
+                    'run' => $run]);
+
+  }
+
+
+  public function direct($total, $done, $cursor, $run) {
+    return $this->progressbar($total, $done, $cursor, $run);
+  }
+}
diff --git a/library/ZendAfi/Controller/Plugin/Manager/User.php b/library/ZendAfi/Controller/Plugin/Manager/User.php
index 34148948ee8ec00ad75a0bdbdf07fbc4ba1397ee..40fd1f0d35f295c02cada011f1291acf45c05961 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/User.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/User.php
@@ -21,6 +21,53 @@
 
 
 class ZendAfi_Controller_Plugin_Manager_User extends ZendAfi_Controller_Plugin_Manager_Manager {
+  public function getActions($model) {
+    return [['url' => ['module' => 'admin',
+                       'controller' => 'users',
+                       'action' => 'edit',
+                       'id' => '%s'],
+             'icon' => 'edit',
+             'label' => $this->_('modifier')],
+
+            ['url' => ['module' => 'admin',
+                       'controller' => 'users',
+                       'action' => 'delete',
+                       'id' => '%s'],
+             'icon' => 'delete',
+             'label' => $this->_('supprimer')],
+
+            ['url' => ['module' => 'opac',
+                       'controller' => 'blog',
+                       'action' => 'viewauteur',
+                       'id' => '%s'],
+             'icon' => 'star',
+             'label' => $this->_('avi(s)'),
+             'condition' => 'hasAvis',
+             'anchorOptions' => ['target' => '_blank']],
+
+            ['url' => ['module' => 'opac',
+                       'controller' => 'panier',
+                       'action' => 'viewauteur',
+                       'id' => '%s'],
+             'icon' => 'basket',
+             'label' => $this->_('panier(s)'),
+             'condition' => 'hasPaniers',
+             'anchorOptions' => ['target' => '_blank']],
+
+            ['url' => ['module' => 'admin',
+                       'controller' => 'users',
+                       'action' => 'manage-double-user',
+                       'id_user' => '%s'],
+             'icon' => 'help',
+             'label' => $this->_('Doublons'),
+             'condition' => function ($model) {
+                return (new Class_User_DbDoubleFinder())->hasDoubleFor($model);
+              }
+            ]
+    ];
+  }
+
+
   protected function _getPost() {
     $post = $this->_request->getPost();
     $post['user_groups'] = array_filter(
diff --git a/library/ZendAfi/View/Helper/Admin/Button.php b/library/ZendAfi/View/Helper/Admin/Button.php
index d6d364d4fb4686c09dd81626dc516c000ec378eb..91f9d69613da7807f7533c7669b57def006f8858 100644
--- a/library/ZendAfi/View/Helper/Admin/Button.php
+++ b/library/ZendAfi/View/Helper/Admin/Button.php
@@ -76,4 +76,5 @@ class ZendAfi_View_Helper_Admin_Button extends ZendAfi_View_Helper_Button {
 
     return $button->getElement();
   }
+
 }
\ No newline at end of file
diff --git a/public/admin/js/onload_utils.js b/public/admin/js/onload_utils.js
index 696095416f11c87c76fb55e50f6d9380f4a50c4c..d11eacdfc50612ff8f94b153ffe14b80afc66aea 100644
--- a/public/admin/js/onload_utils.js
+++ b/public/admin/js/onload_utils.js
@@ -150,3 +150,5 @@ var updateSelectWidget = function(element) {
   url = url + '/categories/' + categories_ids;
   $('.selected_articles_widget a').attr('href', url);
 }
+
+
diff --git a/public/admin/js/progressbar.js b/public/admin/js/progressbar.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1c0bb9c384a01612a65bce71986541fd55cfa65
--- /dev/null
+++ b/public/admin/js/progressbar.js
@@ -0,0 +1,28 @@
+var progressbar = function(id, url, total, cursor = 0) {
+  var run;
+  var tag = $("#" + id + " span");
+  $.ajax({
+    type: 'GET',
+    url: url,
+    data: {'total': total,
+	         'cursor': cursor},
+    success: function(data)
+    {
+      var percent = data.done * 100 / total;
+      var new_cursor = data.cursor;
+      run = data.run;
+      percent = percent ? percent : 1;
+      tag.progressbar({value: percent});
+
+      if(run)
+	      progressbar(id, url, total, new_cursor);
+
+    },
+    complete: function(event, ui) {
+      if(run)
+	      return;
+      tag.progressbar({value: 100});
+
+	    location.reload();      
+    }});
+}
diff --git a/public/admin/skins/bokeh74/buttons.css b/public/admin/skins/bokeh74/buttons.css
index 7e78097b72029592ea93b8eaed8fa8c471a31dde..ffa3ba453b01f66066a6306779f517bdd8c6dda9 100644
--- a/public/admin/skins/bokeh74/buttons.css
+++ b/public/admin/skins/bokeh74/buttons.css
@@ -26,6 +26,7 @@
     transition: background 0.4s;
     border: none;
     cursor: pointer;
+    vertical-align: top;
 }
 
 .admin-button > img {
@@ -44,4 +45,10 @@ div.admin-buttons {
 
 .admin-button:disabled > img {
     filter: opacity(0.2);
+}
+
+
+.admin-button span.ui-progressbar {
+    display: block;
+    margin: 5px;
 }
\ No newline at end of file
diff --git a/public/admin/skins/bokeh74/global.css b/public/admin/skins/bokeh74/global.css
index 59733f9854b7238266e11d111e938522a4fe4e17..f8fe1afe0084d8a12026e48af5cf3ec39585c935 100755
--- a/public/admin/skins/bokeh74/global.css
+++ b/public/admin/skins/bokeh74/global.css
@@ -161,6 +161,11 @@ td[id*="menu_item"] {
     box-shadow: 1px 1px 5px var(--widget-shadow);
 }
 
+.modules pre {
+    box-shadow: inset 1px 1px 5px var(--widget-shadow);
+}
+
+
 /* Font */ 
 body,
 body * {
@@ -994,3 +999,9 @@ table#logs img {
 .modules a[data-order$="desc"] {
     background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);
 }
+
+.modules pre {
+    margin: 1ex 1em;
+    padding: 1ex 1em;
+    overflow: auto;
+}
diff --git a/public/admin/skins/bokeh74/jquery.css b/public/admin/skins/bokeh74/jquery.css
index bf00c573346b7de0b5f37d445c1b1bd5487aaaf4..8225cfd2499f3e73a07bffd2121aa3932ca5c3d1 100644
--- a/public/admin/skins/bokeh74/jquery.css
+++ b/public/admin/skins/bokeh74/jquery.css
@@ -151,3 +151,13 @@ body .ui-tabs a.errors.ui-tabs-anchor {
     color: white;
     background: var(--error-text);
 }
+
+body .ui-progressbar .ui-progressbar-value {
+    background : var(--success-background);
+    margin: 0;
+
+}
+
+body .ui-progressbar {
+    height: 1em;
+}
diff --git a/public/opac/js/subModal.js b/public/opac/js/subModal.js
index ebc20192df84a48d45fe89850890d53d766860ab..4445f2cc5283d2cf7709e54bccbbb206b1d30fd4 100644
--- a/public/opac/js/subModal.js
+++ b/public/opac/js/subModal.js
@@ -5,8 +5,10 @@
   var current_anchor;
 
   window.initializePopups = function() {
-    $('[data-popup="true"]')
-      .unbind('click').click(function(event){
+    $('[data-popup="true"], [data-popup="1"]')
+      .unbind('onclick')
+      .unbind('click')
+      .click(function(event){
         event.preventDefault();
         current_anchor=$(this);
         addLoadingClass();
diff --git a/tests/application/modules/admin/controllers/UsersControllerTest.php b/tests/application/modules/admin/controllers/UsersControllerTest.php
index d65c549915a9d87fd3ad6c7bd0f66db2d544243d..c1d09857f9f967336a5f9ebc744e74c422a705eb 100644
--- a/tests/application/modules/admin/controllers/UsersControllerTest.php
+++ b/tests/application/modules/admin/controllers/UsersControllerTest.php
@@ -27,12 +27,12 @@ abstract class UsersControllerWithMarcusTestCase extends AbstractControllerTestC
     $group_vodeclic = $this->fixture('Class_UserGroup',
                                      ['id' => 20,
                                       'libelle' => 'Multimedia'])
-      ->addRight(Class_UserGroup::RIGHT_ACCES_VODECLIC);
+                           ->addRight(Class_UserGroup::RIGHT_ACCES_VODECLIC);
 
     $group_referent = $this->fixture('Class_UserGroup',
                                      ['id' => 22,
                                       'libelle' => 'Referent'])
-      ->addRight(Class_UserGroup::RIGHT_USER_DOMAINES_SUPPRESSION_LIMIT);
+                           ->addRight(Class_UserGroup::RIGHT_USER_DOMAINES_SUPPRESSION_LIMIT);
 
     $group_stagiaires = $this->fixture('Class_UserGroup',['id' => 25,
                                                           'libelle' => 'Stagiaires']);
@@ -86,14 +86,26 @@ abstract class UsersControllerWithMarcusTestCase extends AbstractControllerTestC
 
     $this->dispatch('/admin/users/edit/id/10', true);
   }
+
 }
 
 
+
+
+
 class UsersControllerIndexTest extends UsersControllerWithMarcusTestCase {
   public function setUp() {
     parent::setUp();
 
     Zend_Registry::set('sql', $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->with('select min(id_user) as id_user, password, login, idabon, ordreabon, count(*) as doublon from bib_admin_users where role_level = 2 and id_user > 0 group by idabon, ordreabon, login, password having doublon > 1 order by id_user asc')
+                       ->answers([])
+
+                       ->whenCalled('fetchAll')
+                       ->with('select idabon, count(*) as doublon from bib_admin_users where role_level = 2 group by idabon having doublon > 1;')
+                       ->answers([])
+
                        ->whenCalled('fetchAllByColumn')
                        ->with('select distinct(id_user) as id from notices_avis')
                        ->answers([2233, 987398]));
@@ -119,6 +131,13 @@ class UsersControllerIndexTest extends UsersControllerWithMarcusTestCase {
          ->whenCalled('hasIdentity')
          ->answers($user)
 
+         ->whenCalled('findAllBy')
+         ->with(['idabon' => '',
+                 'password' => 'francis',
+                 'login' => 'francis',
+                 'ordreabon' => ''])
+         ->answers($francis)
+
          ->whenCalled('findAllBy')
          ->with(['role_level' => 2,
                  'order' => 'nom asc',
@@ -131,7 +150,6 @@ class UsersControllerIndexTest extends UsersControllerWithMarcusTestCase {
                  'order' => 'nom asc',
                  'where' => '(STR_TO_DATE(date_fin, \'%Y-%m-%d\') >= CURDATE()) AND (id_user in (2233,987398)) AND (login LIKE "%francis%" OR nom LIKE "%francis%" OR prenom LIKE "%francis%" OR pseudo LIKE "%francis%" OR mail LIKE "%francis%" OR idabon LIKE "%francis%") AND (role_level <= 7)'])
          ->answers(55)
-
          ->beStrict();
 
     $this->dispatch('/admin/users?search_id_site=all&search_role_level=2&search_valid_subscription=1&search_review=1&search_search_for=\"\'fra"n\'cis"', true);
@@ -733,10 +751,10 @@ class UsersControllerReferentIndexTest extends UsersControllerWithMarcusTestCase
 
     $this->user_loader = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Users');
     $this->user_loader->whenCalled('getIdentity')
-      ->answers($user = Class_Users::newInstanceWithId(2)
-                ->setLogin('referent')
-                ->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL)
-                ->setPseudo('referent'));
+                      ->answers($user = Class_Users::newInstanceWithId(2)
+                                ->setLogin('referent')
+                                ->setRoleLevel(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL)
+                                ->setPseudo('referent'));
 
     $this->addUserToRightsReferent($user);
 
@@ -881,15 +899,15 @@ abstract class Admin_UsersControllerEditAdminTestCase extends Admin_AbstractCont
                     'libelle' => 'Region Savoie']);
 
     $this->_lib_ac = $this->fixture('Class_Bib',
-                                 ['id' => 7,
-                                  'libelle' => 'AC',
-                                  'id_zone' => 1]);
+                                    ['id' => 7,
+                                     'libelle' => 'AC',
+                                     'id_zone' => 1]);
 
     $this->_lib_po = $this->fixture('Class_Bib',
-                                 ['id' => 15,
-                                  'libelle' => 'PO',
-                                  'id_zone' => 1,
-                                  'redmine_api_key' => '123abc']);
+                                    ['id' => 15,
+                                     'libelle' => 'PO',
+                                     'id_zone' => 1,
+                                     'redmine_api_key' => '123abc']);
 
     $this->fixture('Class_Bib',
                    ['id' => 13,
@@ -946,7 +964,7 @@ class Admin_UsersControllerFormEditAdminTest extends Admin_UsersControllerEditAd
 
   /** @test */
   public function withModoPortailRedmineLibrarySelectorShouldBePresent() {
-      $this->assertXPath('//form//select[@name="redmine_library"]', $this->_response->getBody());
+    $this->assertXPath('//form//select[@name="redmine_library"]', $this->_response->getBody());
   }
 
 
@@ -1165,7 +1183,7 @@ class UsersControllerWithAdminPortalTest extends Admin_AbstractControllerTestCas
   }
 
 
-   /** @test */
+  /** @test */
   public function linkToShowTumPanierShouldNotBePresent() {
     $this->assertNotXPath('//a[contains(@href, "/panier/viewauteur/id/3")]');
   }
@@ -1176,3 +1194,555 @@ class UsersControllerWithAdminPortalTest extends Admin_AbstractControllerTestCas
     $this->assertXPath('//th/a[contains(@href, "/search_order/nom/")]');
   }
 }
+
+
+
+
+abstract class UsersControllerDoubleTestCase extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $mock_sql =
+      $this->mock()
+           ->whenCalled('fetchAll')
+           ->with('select min(id_user) as id_user, password, login, idabon, ordreabon, count(*) as doublon from bib_admin_users where role_level = 2 and id_user > 0 group by idabon, ordreabon, login, password having doublon > 1 order by id_user asc')
+           ->answers([
+                      ['id_user' => '25',
+                       'login' => 'Ret',
+                       'password' => 'urn',
+                       'idabon' => '89',
+                       'ordreabon' => '18',
+                       'doublon' => '2'],
+
+                      ['id_user' => '2',
+                       'login' => 'Ret',
+                       'password' => 'urn',
+                       'idabon' => '49',
+                       'ordreabon' => '1',
+                       'doublon' => '2'],
+
+                      ['id_user' => '100',
+                       'login' => 'rita',
+                       'password' => 'rita',
+                       'idabon' => '89',
+                       'ordreabon' => '18',
+                       'doublon' => '3'],
+
+                      ['id_user' => '103',
+                       'login' => 'rita',
+                       'password' => 'rita',
+                       'idabon' => '90',
+                       'ordreabon' => '18',
+                       'doublon' => '3'],
+
+                      ['id_user' => '106',
+                       'login' => 'rita',
+                       'password' => 'rita',
+                       'idabon' => '91',
+                       'ordreabon' => '18',
+                       'doublon' => '3'],
+
+                      ['id_user' => '110',
+                       'login' => 'rita',
+                       'password' => 'rita',
+                       'idabon' => '100',
+                       'ordreabon' => '18',
+                       'doublon' => '3']
+
+                      ])
+
+           ->whenCalled('fetchOne')
+           ->with('select count(id) from notices_paniers where id_user in (25,654,655)')
+           ->answers(1)
+
+           ->whenCalled('fetchOne')
+           ->with('select count(id) from notices_paniers where id_user in (100,101,102)')
+           ->answers(1)
+
+           ->whenCalled('query')
+           ->with('delete from bib_admin_users where id_user in (2,29)')
+           ->willDo(function()
+                    {
+                      Class_Users::find(2)->delete();
+                      Class_Users::find(29)->delete();
+                    })
+
+           ->whenCalled('fetchOne')
+           ->with('select count(id) from notices_paniers where id_user in (103,104,105)')
+           ->answers(1)
+
+           ->whenCalled('fetchOne')
+           ->with('select count(id) from notices_paniers where id_user in (106,107,108)')
+           ->answers(1)
+
+           ->whenCalled('fetchOne')
+           ->with('select count(id) from notices_paniers where id_user in (110,111)')
+           ->answers(1)
+
+           ->whenCalled('fetchOne')
+           ->answers(0)
+
+           ->whenCalled('query')
+           ->answers(true)
+
+           ->whenCalled('query')
+           ->with('update notices_paniers set id_user=654 where id_user=25')
+           ->willDo(function() {
+                                  return Class_PanierNotice::find(4)->setIdUser(654)->save();
+                                })
+           ->whenCalled('query')
+           ->with('update notices_paniers set id_user=100 where id_user=101')
+           ->willDo(function() {
+                                  return Class_PanierNotice::find(101)->setIdUser(100)->save();
+                                })
+           ->whenCalled('query')
+           ->with('update notices_paniers set id_user=100 where id_user=102')
+           ->willDo(function() {
+                                  return Class_PanierNotice::find(102)->setIdUser(100)->save();
+                                });
+
+    Zend_Registry::set('sql', $mock_sql);
+
+    $this->fixture('Class_Users',
+                   ['id' => 2,
+                    'login' => 'Ret',
+                    'password' => 'urn',
+                    'idabon' => '49',
+                    'ordreabon' => 1]);
+
+    $this->fixture('Class_Users',
+                   ['id' => 29,
+                    'login' => 'Ret',
+                    'password' => 'urn',
+                    'idabon' => '49',
+                    'ordreabon' => 1]);
+
+    $this->fixture('Class_Users',
+                   ['id' => 123,
+                    'login' => 'Ret',
+                    'password' => 'urn',
+                    'idabon' => '49',
+                    'ordreabon' => 2]);
+
+    $this->fixture('Class_Users',
+                   ['id' => 25,
+                    'login' => 'Ret',
+                    'password' => 'urn',
+                    'idabon' => '89',
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_Users',
+                   ['id' => 654,
+                    'login' => 'Ret',
+                    'password' => 'urn',
+                    'idabon' => '89',
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_Users',
+                   ['id' => 655,
+                    'login' => 'Ret',
+                    'password' => 'urn',
+                    'idabon' => '89',
+                    'ordreabon' => 18]);
+
+    $this->createUsersWithStatusDifferentAndSameNames(89, [100,101,102], [0,1,1]);
+    $this->createUsersWithStatusDifferentAndSameNames(90, [103,104,105], [1,1,1]);
+    $this->createUsersWithStatusDifferentAndSameNames(91, [106,107,108], [0,0,1]);
+    $this->createUsersWithEmptyValidityDate();
+    $this->fixture('Class_PanierNotice',
+                   ['id' => 4,
+                    'libelle' => 's',
+                    'id_user' => 25]);
+  }
+
+
+  protected function createUsersWithEmptyValidityDate() {
+    $this->fixture('Class_Users',
+                   ['id' => 110,
+                    'nom' => 'Abatzi',
+                    'prenom' => 'rita',
+                    'statut' => 1,
+                    'login' => 'rita',
+                    'date_fin' => null,
+                    'password' => 'rita',
+                    'idabon' => 100,
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_Users',
+                   ['id' => 111,
+                    'nom' => 'Abatzi',
+                    'prenom' => 'rita',
+                    'statut' => 0,
+                    'date_fin' => null,
+                    'login' => 'rita',
+                    'password' => 'rita',
+                    'idabon' => 100,
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_PanierNotice',
+                   ['id' => 40,
+                    'libelle' => 's',
+                    'id_user' => 110]);
+
+  }
+
+
+  protected function createUsersWithStatusDifferentAndSameNames($id_abon, $ids, $status) {
+    $this->fixture('Class_Users',
+                   ['id' => $ids[0],
+                    'nom' => 'test',
+                    'prenom' => 'test',
+                    'statut' => $status[0],
+                    'login' => 'rita',
+                    'date_fin' => '2017-01-08',
+                    'password' => 'rita',
+                    'idabon' => $id_abon,
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_Users',
+                   ['id' => $ids[1],
+                    'nom' => 'test',
+                    'prenom' => 'test',
+                    'statut' => $status[1],
+                    'date_fin' => '2017-01-07',
+                    'login' => 'rita',
+                    'password' => 'rita',
+                    'idabon' => $id_abon,
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_Users',
+                   ['id' => $ids[2],
+                    'nom' => 'test',
+                    'prenom' => 'test',
+                    'statut' => $status[2],
+                    'date_fin' => '2016-09-07',
+                    'login' => 'rita',
+                    'password' => 'rita',
+                    'idabon' => $id_abon,
+                    'ordreabon' => 18]);
+
+    $this->fixture('Class_PanierNotice',
+                   ['id' => $ids[0],
+                    'libelle' => 'mybasket',
+                    'id_user' => $ids[0]]);
+
+    $this->fixture('Class_PanierNotice',
+                   ['id' => $ids[1],
+                    'libelle' => 'mybasket',
+                    'id_user' => $ids[1]]);
+
+    $this->fixture('Class_PanierNotice',
+                   ['id' => $ids[2],
+                    'libelle' => 'mybasket',
+                    'id_user' => $ids[2]]);
+  }
+}
+
+
+
+
+class UsersControllerManageDoubleTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/manage-double', true);
+  }
+
+
+  /** @test */
+  public function sixDoubleAccountsShouldBeFounded() {
+    $this->assertXPathContentContains('//p', 'Vous avez <b>6</b> comptes abonnés possédant au moins un doublon',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function purgeButtonShouldBePresent() {
+    $this->assertXPathContentContains('//button[contains(@onclick, "/admin/users/delete-double")]', 'Lancer le dédoublonnage automatique');
+  }
+}
+
+
+
+class UsersControllerDeleteDoubleEndCursorTest extends Admin_AbstractControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Zend_Registry::set('sql', $this->mock()
+                       ->whenCalled('fetchAll')
+                       ->answers([]));
+
+    $this->dispatch('/admin/users/delete-double/total/6/cursor/1000', true);
+  }
+
+
+  /** @test */
+  public function jsonShouldBeRendered() {
+    $this->assertEquals(['total' => 6,
+                         'done' => 6,
+                         'cursor' => null,
+                         'run' => false], json_decode($this->_response->getBody(),true));
+  }
+}
+
+
+
+
+
+class UsersControllerDeleteDoubleTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/delete-double/total/6', true);
+  }
+
+
+  /** @test */
+  public function jsonShouldBeRendered() {
+    $this->assertEquals(['total' => 6,
+                         'done' => 0,
+                         'cursor' => 110,
+                         'run' => true], json_decode($this->_response->getBody(),true));
+  }
+
+
+  /** @test */
+  public function userId123456ShouldNotBeDeleted() {
+    $this->assertNotNull(Class_Users::find(123));
+  }
+
+
+  /** @test */
+  public function userId2ShouldHaveBeendDeleted() {
+    $this->assertNull(Class_Users::find(2));
+  }
+
+
+  /** @test */
+  public function userId101And102ShouldHaveBeendDeleted() {
+    $this->assertNull(Class_Users::find(101));
+    $this->assertNull(Class_Users::find(102));
+    $this->assertNotNull(Class_Users::find(100));
+  }
+
+
+  /** @test */
+  public function userId103And104ShouldNotHaveBeendDeleted() {
+    $this->assertNotNull(Class_Users::find(103));
+    $this->assertNotNull(Class_Users::find(104));
+    $this->assertNotNull(Class_Users::find(105));
+  }
+
+
+  /** @test */
+  public function userId106And107ShouldNotHaveBeendDeleted() {
+    $this->assertNotNull(Class_Users::find(106));
+    $this->assertNotNull(Class_Users::find(107));
+    $this->assertNotNull(Class_Users::find(108));
+  }
+
+
+  /** @test */
+  public function userId110And111ShouldNotHaveBeendDeleted() {
+    $this->assertNotNull(Class_Users::find(110));
+    $this->assertNotNull(Class_Users::find(111));
+  }
+
+
+  /** @test */
+  public function userId100ShouldHaveBaskets() {
+    $this->assertEquals(3,Class_PanierNotice::countBy(['id_user' => 100,'id' =>[100,101,102]]));
+  }
+
+
+  /** @test */
+  public function userId29ShouldHaveBeendDeleted() {
+    $this->assertNull(Class_Users::find(29));
+  }
+
+
+  /** @test */
+  public function userId25ShouldNotBeDeleted() {
+    $this->assertNotNull(Class_Users::find(25));
+  }
+
+
+  /** @test */
+  public function userId654ShouldNotBeDeleted() {
+    $this->assertNotNull(Class_Users::find(654));
+  }
+}
+
+
+
+
+class UsersControllerManageDoubleManualTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/manage-double-manual', true);
+  }
+
+
+  /** @test */
+  public function shouldDisplayContinuButton() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double-user/id_user/25")]');
+  }
+}
+
+
+
+
+class UsersControllerManageDoubleUserTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/manage-double-user/id_user/25', true);
+  }
+
+
+  /** @test */
+  public function shouldDisplayUsers() {
+    $this->assertXpath('//div');
+  }
+
+
+  /** @test */
+  public function backButtonShouldBeDisplayed() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double")]',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function buttonMergeShouldBe654to25() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double-merge/id_user/25/id_user_to/25/id_user_from/654")]',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function buttonMergeShouldBe25to654() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double-merge/id_user/25/id_user_to/654/id_user_from/25_655")]',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function buttonIgnoreAndContinueShouldHaveUserId2() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double-user/id_user/2")]',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function buttonPreviousShouldNotBeAvailable() {
+    $this->assertXpathContentContains('//button[contains(@disabled, "disabled")]','Précédent',$this->_response->getBody());
+  }
+}
+
+
+
+
+class UsersControllerManageNextDoubleUserTest extends UsersControllerDoubleTestCase {
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/manage-double-user/id_user/2', true);
+  }
+
+
+  /** @test*/
+  public function buttonMergeShouldBe2to29() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double-merge/id_user/2/id_user_to/29/id_user_from/2")]',$this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function buttonPreviousAndNextShouldBeAvailable() {
+    $this->assertNotXPath('//button[contains(@disabled, "disabled")]');
+  }
+
+
+  /** @test */
+  public function buttonPreviousShouldHaveUserId25() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double-user/id_user/25")]',$this->_response->getBody());
+  }
+}
+
+
+
+
+class UsersControllerManageDoubleMergeTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/manage-double-merge/id_user_from/25/id_user_to/654', true);
+  }
+
+
+  /** @test */
+  public function user25ShouldBeDeleted() {
+    $this->assertNull(Class_Users::find(25));
+  }
+
+
+  /** @test */
+  public function panier4ShouldHaveIdUser654() {
+    $this->assertEquals(654,Class_PanierNotice::find(4)->getIdUser());
+  }
+}
+
+
+
+
+class UsersControllerManageDoubleMergeWithMultipleSourcesTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/manage-double-merge/id_user_from/25_655/id_user_to/654', true);
+  }
+
+
+  /** @test */
+  public function user25ShouldBeDeleted() {
+    $this->assertNull(Class_Users::find(25));
+  }
+
+
+  /** @test */
+  public function user655ShouldBeDeleted() {
+    $this->assertNull(Class_Users::find(655));
+  }
+}
+
+
+
+
+class UsersControllerIndexWithDoubleTest extends UsersControllerDoubleTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/users/index', true);
+  }
+
+
+  /** @test */
+  public function buttonManageDoubleShouldBePresent() {
+    $this->assertXpath('//button[contains(@onclick, "/admin/users/manage-double")]');
+  }
+
+
+  /** @test */
+  public function linkToManageDoubleOfUserId25ShouldBePresent() {
+    $this->assertXpath('//td//a[contains(@href, "/admin/users/manage-double-user/id_user/25")]');
+  }
+
+
+  /** @test */
+  public function idUser123ShouldBePresent() {
+    $this->assertXPath('//td//a[contains(@href, "/admin/users/edit/id/123")]');
+  }
+
+
+  /** @test */
+  public function idUser123ShouldNotHaveLinkToManageDouble() {
+    $this->assertNotXPath('//td//a[contains(@href, "/admin/users/manage-double-user/id_user/123")]');
+  }
+}
diff --git a/tests/library/Class/UserDatasTest.php b/tests/library/Class/UserDatasTest.php
index 8260fc02ff1d15d7d32e50d0d6cbfa2066d5914e..f67537ef647f221f4485a7a0b24f8f374306a0a4 100644
--- a/tests/library/Class/UserDatasTest.php
+++ b/tests/library/Class/UserDatasTest.php
@@ -96,10 +96,12 @@ class UserDatasTest extends UserDatasTestCase {
                 ['notices_avis', 'id_user'],
                 ['cms_avis', 'id_user'],
                 ['suggestion_achat', 'user_id'],
-                ['session_formation_inscriptions', 'stagiaire_id'],
+                ['session_activity_inscriptions', 'stagiaire_id'],
                 ['user_group_memberships', 'user_id'],
                 ['formulaires', 'id_user'],
-                ['multimedia_devicehold', 'id_user']
+                ['multimedia_devicehold', 'id_user'],
+                ['linked_cards', 'parent_id'],
+                ['linked_cards', 'child_id']
     ];
 
     foreach($queries as $query)