diff --git a/VERSIONS_HOTLINE/133561 b/VERSIONS_HOTLINE/133561
new file mode 100644
index 0000000000000000000000000000000000000000..dfaaa883819ffb28ed12681b432ce3733f413ac7
--- /dev/null
+++ b/VERSIONS_HOTLINE/133561
@@ -0,0 +1 @@
+ - ticket #133561 : Intégrations : Maintenance du traitement d'homogénéisation par l'ISBN
\ No newline at end of file
diff --git a/cosmogramme/php/classes/classe_notice_integration.php b/cosmogramme/php/classes/classe_notice_integration.php
index 5646cc2b901c78260341ed2e529ae783832eadde..5bc0689cd30f5c70939141fdd3717f18da59ef66 100644
--- a/cosmogramme/php/classes/classe_notice_integration.php
+++ b/cosmogramme/php/classes/classe_notice_integration.php
@@ -277,45 +277,45 @@ class notice_integration {
   }
 
 
-  public function traiteHomogene($id_notice, $isbn, $ean, $id_commerciale, $no_request) {
-    global $sql;
+  public function traiteHomogene($record) {
+    if (!$record)
+      return Class_WebService_Afi_Status::newNotFound();
+
+    $ret = $this->getServiceRunner()
+                ->getRecordZ3950(['isbn' => $record->getIsbn(),
+                                  'ean' => $record->getEan(),
+                                  'id_commerciale' => $record->getIdCommerciale(),
+                                  'no_request' => getVariable('Z3950_cache_only')]);
 
-    // Appel du service
-    $args["isbn"]=$isbn;
-    $args["ean"]= $ean;
-    $args["id_commerciale"]=$id_commerciale;
-    $args["no_request"]=$no_request;
-    $ret=communication::runService(4,$args);
-
-    // Formatter la reponse
-    $ret["timeout"]=10;
-
-    // Statut not found : Mise a jour nombre de retries
-    if ($ret["statut_z3950"] == '1') {
-      $notice = Class_Notice::find($id_notice);
-      $retry = $notice->getZ3950Retry() + 1;
-      $notice->setZ3950Retry($retry)->save();
+    if ((isset($ret['statut']) && 'erreur' == $ret['statut'])
+        || !isset($ret['statut_z3950'])
+        || '0' === $ret['statut_z3950']) {
+      $record->setZ3950Retry($record->getZ3950Retry() + 1)->save();
+      return Class_WebService_Afi_Status::newError();
     }
 
-    // Statut ok : on remplace la notice
-    if(!$this->analyseur) $this->analyseur=new notice_unimarc();
-    if($ret["statut_z3950"] > "1")
-    {
-      $this->analyseur->ouvrirNotice($ret["unimarc"],
-                                     $args['isbn']
-                                     ? profil_donnees::HOMOGENIZATION_ISBN
-                                     : profil_donnees::HOMOGENIZATION_EAN);
-      $this->notice=$this->analyseur->getNoticeIntegration();
-      $qualite=getVariable("homogene_code_qualite");
-      $this->updateNotice($id_notice,$qualite);
+    if ('1' === $ret['statut_z3950']) {
+      $record->setZ3950Retry($record->getZ3950Retry() + 1)->save();
+      return Class_WebService_Afi_Status::newNotFound();
     }
-    return $ret;
+
+    if (!$this->analyseur)
+      $this->analyseur = new notice_unimarc();
+
+    $this->analyseur->ouvrirNotice($ret['unimarc'],
+                                   $record->getIsbn()
+                                   ? profil_donnees::HOMOGENIZATION_ISBN
+                                   : profil_donnees::HOMOGENIZATION_EAN);
+    $this->notice = $this->analyseur->getNoticeIntegration();
+    $this->updateNotice($record->getId(), getVariable('homogene_code_qualite'));
+
+    return Class_WebService_Afi_Status::newRemote();
   }
 
 
   /**
    * @param $succinte Class_NoticeSuccincte
-   * @return Class_NoticeSuccincte::STATUS_*
+   * @return Class_WebService_Afi_Status
    */
   public function traiteSuccinte($succinte) {
     /**
@@ -325,7 +325,7 @@ class notice_integration {
     $this->id_bib = $this->id_int_bib = $succinte->getIdBib();
 
     return $this->_handleSuccinteLocaly($succinte)
-      ? Class_NoticeSuccincte::STATUS_LOCAL
+      ? Class_WebService_Afi_Status::newLocal()
       : $this->_handleSuccinteRemotely($succinte);
   }
 
@@ -360,12 +360,12 @@ class notice_integration {
 
     if (!isset($ret['statut_z3950']) || '0' === $ret['statut_z3950']) {
       $succinte->incrementRetry()->save();
-      return Class_NoticeSuccincte::STATUS_ERROR;
+      return Class_WebService_Afi_Status::newError();
     }
 
     if ('1' === $ret['statut_z3950']) {
       $succinte->incrementRetry()->save();
-      return Class_NoticeSuccincte::STATUS_NOTFOUND;
+      return Class_WebService_Afi_Status::newNotFound();
     }
 
     if (!$this->analyseur)
@@ -381,11 +381,11 @@ class notice_integration {
       $this->ecrireExemplaires($id_notice);
       $succinte->delete();
 
-      return Class_NoticeSuccincte::STATUS_REMOTE;
+      return Class_WebService_Afi_Status::newRemote();
     }
 
     $succinte->incrementRetry()->save();
-    return Class_NoticeSuccincte::STATUS_NOTFOUND;
+    return Class_WebService_Afi_Status::newNotFound();
   }
 
 
diff --git a/cosmogramme/php/classes/classe_profil_donnees.php b/cosmogramme/php/classes/classe_profil_donnees.php
index fb146b63fd0608bdb563c0c7dfbdf98fb171b0dc..0c3b0d19db4f66dda89b3226b32587e14526f173 100644
--- a/cosmogramme/php/classes/classe_profil_donnees.php
+++ b/cosmogramme/php/classes/classe_profil_donnees.php
@@ -111,7 +111,10 @@ class profil_donnees {
     $this->attributs = unserialize($this->_data_profile->getAttributs());
 
     // Decompacter et consolider les types de docs
-    $td = $this->attributs[0]["type_doc"];
+    $td = isset($this->attributs[0]["type_doc"])
+      ? $this->attributs[0]["type_doc"]
+      : [];
+
     foreach($this->_getTypeDocs() as $i => $type_doc)  {
       $this->attributs[0]["type_doc"][$i]["code"] = $type_doc->getId();
       $this->attributs[0]["type_doc"][$i]["libelle"] = $type_doc->getLibelle();
diff --git a/cosmogramme/php/classes/classe_unimarc.php b/cosmogramme/php/classes/classe_unimarc.php
index e2576dc2d88a293426addec33d611d32c67d270d..3881ddcc0bc7b2d3104d3bdc4c27eab5c651d41c 100644
--- a/cosmogramme/php/classes/classe_unimarc.php
+++ b/cosmogramme/php/classes/classe_unimarc.php
@@ -1735,6 +1735,8 @@ class notice_unimarc extends iso2709_record {
 
 
   protected function getBarcode() {
-    return trim($this->profil['attributs'][0]['champ_code_barres']);
+    return isset($this->profil['attributs'][0]['champ_code_barres'])
+      ? trim($this->profil['attributs'][0]['champ_code_barres'])
+      : '';
   }
 }
\ No newline at end of file
diff --git a/cosmogramme/php/integre_traite_main.php b/cosmogramme/php/integre_traite_main.php
index 3b575bdf67e4126aa4fd964951fe3ff860c93198..e56a5b4e69b269511d30ee61b895ec4b943180c1 100644
--- a/cosmogramme/php/integre_traite_main.php
+++ b/cosmogramme/php/integre_traite_main.php
@@ -231,226 +231,20 @@ if (!$should_skip_records) {
   // Traiter les notices succintes (PHASE 3)
   // ----------------------------------------------------------------
   if ($phase == 3) {
-     startIntegrationPhase('IncompleteRecord');
+    startIntegrationPhase('IncompleteRecord');
+    $phase = 4;
   }
 
   // ----------------------------------------------------------------
   // Traiter l'homogénéisation des notices par ISBN (PHASE 4)
   // ----------------------------------------------------------------
-  setVariable("traitement_phase", "Homogénéisation par l'ISBN");
-  $homogene_actif = getVariable("homogene");
-  $homogene_cache_only = getVariable("Z3950_cache_only");
-  $frequence = getVariable("homogene_frequence");
-  $date_homogene = getVariable("homogene_date");
-  $Z3950_retry_level = getVariable("Z3950_retry_level");
-  $Z3950_max_reconnect = getVariable("Z3950_max_reconnect");
-  $qualite_homogene = getVariable("homogene_code_qualite");
-  $ecart = ecartDates($date, $date_homogene);
-  if ($phase == 3)
-    {
-      $log->log("<h4>Homogénéisation Z39.50 des notices par l'ISBN</h4>");
-      unset($phase_data);
-      $phase_data["nombre"] = 0;
-      $phase_data["timeStart"] = time();
-      $chrono100notices->start();
-      $phase = 4;
-      if ($homogene_actif == 0)
-        {
-          $log->log('<span class="rouge">Le processus d\'homogénéisation des notices est désactivé</span>' . BR);
-        } else
-        {
-          if ($ecart < $frequence) $log->log('<span class="vert">Sera fait dans ' . ($frequence - $ecart) . ' jour(s)</span>');
-          else if (!$mode_cron) sauveContexte();
-        }
-    }
-  if ($phase == 4 and $ecart >= $frequence and $homogene_actif == 1)
-    {
-      if (!$mode_cron) print("<h4>Homogénéisation Z39.50 des notices par l'ISBN</h4>");
-      $chrono->start();
-      $ret["timeout"] = 10;
-      if (!$phase_data["nombre"])
-        {
-          $log->log('<span class="vert">Niveau de tentatives d\'homogénéisation : ' . $Z3950_retry_level . '</span>' . BR);
-          $log->log('<span class="vert">Nombre maxi de tentatives de reconnexions : ' . $Z3950_max_reconnect . '</span>' . BR);
-          $log->log('<span class="vert">Mode de recherche sur les serveurs z39.50 : ' . getLibCodifVariable("Z3950_cache_only", $homogene_cache_only) . '</span>' . BR);
-        }
-      $result = $sql->prepareListe("select id_notice,isbn from notices where isbn > '" . $phase_data["isbn"] . "' and qualite < $qualite_homogene and z3950_retry <= $Z3950_retry_level order by isbn");
-      try {
-        while ($data = $sql->fetchNext($result))
-          {
-            while (true)
-              {
-                if (!$mode_cron and ($chrono->tempsPasse() + $ret["timeout"]) > $timeout) sauveContexte();
-                $ret = $notice->traiteHomogene($data["id_notice"], $data["isbn"], "", "", $homogene_cache_only);
-                traceHomogene($data["id_notice"], $data["isbn"], "", "");
-                if ($ret["statut"] != "erreur" and $ret["statut_z3950"] > 0)
-                  {
-                    $phase_data["nb_reconnexions"] = 0;
-                    break;
-                  }
-
-                // Tentatives de reconnexion
-                $phase_data["nb_reconnexions"]++;
-                if ($phase_data["nb_reconnexions"] >= $Z3950_max_reconnect)
-                  {
-                    $log->log(BR . '<span class="rouge">Abandon du traitement : maximum de tentatives de reconnexions atteint</span>' . BR);
-                    $fin = true;
-                    break;
-                  }
-                sleep(5);
-              }
-            if ($fin == true) break;
-            $phase_data["isbn"] = $data["isbn"];
-            $phase_data["nombre"]++;
-          }
-      } catch (PDOException $e) {
-        $log->log('PDOException'.$e);
-        $cosmo_path = new CosmoPaths();
-        $site = '/' . $cosmo_path->getSite() . '/';
-        $cfgfile = $cosmo_path->getConfigPath();
-        $cfg=lireConfig($cfgfile);
-        $sql = new sql($cfg["integration_server"],$cfg["integration_user"],$cfg["integration_pwd"],$cfg["integration_base"]);
-
-      }
-      afficherRecapHomogene($phase_data, $chrono);
-    }
-  // ----------------------------------------------------------------
-  // Traiter l'homogénéisation des notices par EAN (PHASE 5)
-  // ----------------------------------------------------------------
-  setVariable("traitement_phase", "Homogénéisation par l'EAN");
-  $fin = false;
-  if ($phase == 4)
-    {
-      $log->log("<h4>Homogénéisation Z39.50 des notices par l'EAN</h4>");
-      unset($phase_data);
-      $phase_data["nombre"] = 0;
-      $phase_data["timeStart"] = time();
-      $phase_data["ean"] = "";
-      $chrono100notices->start();
-      $phase = 5;
-      if ($homogene_actif == 0)
-        {
-          $log->log('<span class="rouge">Le processus d\'homogénéisation des notices est désactivé</span>' . BR);
-        } else
-        {
-          if ($ecart < $frequence) $log->log('<span class="vert">Sera fait dans ' . ($frequence - $ecart) . ' jour(s)</span>');
-          else if (!$mode_cron) sauveContexte();
-        }
-    }
-  if ($phase == 5 and $ecart >= $frequence and $homogene_actif == 1)
-    {
-      if (!$mode_cron) print("<h4>Homogénéisation Z39.50 des notices par l'EAN</h4>");
-      $chrono->start();
-      $ret["timeout"] = 10;
-      if (!$phase_data["nombre"])
-        {
-          $log->log('<span class="vert">Niveau de tentatives d\'homogénéisation : ' . $Z3950_retry_level . '</span>' . BR);
-          $log->log('<span class="vert">Nombre maxi de tentatives de reconnexions : ' . ($Z3950_max_reconnect) . '</span>' . BR);
-          $log->log('<span class="vert">Mode de recherche sur les serveurs z39.50 : ' . getLibCodifVariable("Z3950_cache_only", $homogene_cache_only) . '</span>' . BR);
-        }
-      $result = $sql->prepareListe("select id_notice,ean from notices where ean > '" . $phase_data["ean"] . "' and qualite < $qualite_homogene and z3950_retry <= $Z3950_retry_level order by ean");
-      while ($data = $sql->fetchNext($result))
-        {
-          while (true)
-            {
-              if (!$mode_cron and ($chrono->tempsPasse() + $ret["timeout"]) > $timeout) sauveContexte();
-              $ret = $notice->traiteHomogene($data["id_notice"], "", $data["ean"], "", $homogene_cache_only);
-              traceHomogene($data["id_notice"], "", $data["ean"], "");
-              if ($ret["statut"] != "erreur" and $ret["statut_z3950"] > 0)
-                {
-                  $phase_data["nb_reconnexions"] = 0;
-                  break;
-                }
-
-              // Tentatives de reconnexion
-              $phase_data["nb_reconnexions"]++;
-              if ($phase_data["nb_reconnexions"] >= $Z3950_max_reconnect)
-                {
-                  $log->log(BR . '<span class="rouge">Abandon du traitement : maximum de tentatives de reconnexions atteint</span>' . BR);
-                  $fin = true;
-                  break;
-                }
-              sleep(5);
-            }
-          if ($fin == true) break;
-          $phase_data["ean"] = $data["ean"];
-          $phase_data["nombre"]++;
-        }
-      afficherRecapHomogene($phase_data, $chrono);
-    }
-
-  // --------------------------------------------------------------------
-  // Traiter l'homogénéisation des notices par ID_COMMERCIALE (PHASE 6)
-  // --------------------------------------------------------------------
-  setVariable("traitement_phase", "Homogénéisation par le numéro commercial");
-  $fin = false;
-  if ($phase == 5)
-    {
-      $log->log("<h4>Homogénéisation Z39.50 des notices par le numéro commercial</h4>");
-      unset($phase_data);
-      $phase_data["nombre"] = 0;
-      $phase_data["timeStart"] = time();
-      $phase_data["ean"] = "";
-      $chrono100notices->start();
-      $phase = 6;
-      if ($homogene_actif == 0)
-        {
-          $log->log('<span class="rouge">Le processus d\'homogénéisation des notices est désactivé</span>' . BR);
-        } else
-        {
-          if ($ecart < $frequence) $log->log('<span class="vert">Sera fait dans ' . ($frequence - $ecart) . ' jour(s)</span>');
-          else if (!$mode_cron) sauveContexte();
-        }
-    }
-  if ($phase == 6 and $ecart >= $frequence and $homogene_actif == 1)
-    {
-      if (!$mode_cron) print("<h4>Homogénéisation Z39.50 des notices par le numéro commercial</h4>");
-      $chrono->start();
-      $ret["timeout"] = 10;
-      if (!$phase_data["nombre"])
-        {
-          $log->log('<span class="vert">Niveau de tentatives d\'homogénéisation : ' . $Z3950_retry_level . '</span>' . BR);
-          $log->log('<span class="vert">Nombre maxi de tentatives de reconnexions : ' . ($Z3950_max_reconnect) . '</span>' . BR);
-          $log->log('<span class="vert">Mode de recherche sur les serveurs z39.50 : ' . getLibCodifVariable("Z3950_cache_only", $homogene_cache_only) . '</span>' . BR);
-        }
-      $result = $sql->prepareListe("select id_notice,id_commerciale from notices where id_commerciale > '" . $phase_data["id_commerciale"] . "' and qualite < $qualite_homogene and z3950_retry <= $Z3950_retry_level order by id_commerciale");
-      while ($data = $sql->fetchNext($result))
-        {
-          while (true)
-            {
-              if (!$mode_cron and ($chrono->tempsPasse() + $ret["timeout"]) > $timeout) sauveContexte();
-              $ret = $notice->traiteHomogene($data["id_notice"], "", "", $data["id_commerciale"], $homogene_cache_only);
-              traceHomogene($data["id_notice"], "", "", $data["id_commerciale"]);
-              if ($ret["statut"] != "erreur" and $ret["statut_z3950"] > 0)
-                {
-                  $phase_data["nb_reconnexions"] = 0;
-                  break;
-                }
-
-              // Tentatives de reconnexion
-              $phase_data["nb_reconnexions"]++;
-              if ($phase_data["nb_reconnexions"] >= $Z3950_max_reconnect)
-                {
-                  $log->log(BR . '<span class="rouge">Abandon du traitement : maximum de tentatives de reconnexions atteint</span>' . BR);
-                  $fin = true;
-                  break;
-                }
-              sleep(5);
-            }
-          if ($fin == true) break;
-          $phase_data["id_commerciale"] = $data["id_commerciale"];
-          $phase_data["nombre"]++;
-        }
-      afficherRecapHomogene($phase_data, $chrono);
-      setVariable("homogene_date", $date);
-    };
+  if ($phase == 4) {
+    startIntegrationPhase('HomogenizeRecord');
+  }
 
   // ----------------------------------------------------------------
   // Recalcul des facettes bibliothèque (phases 7 et 7.1)
   // ----------------------------------------------------------------
-  if (!$mode_cron and $chrono->tempsPasse() > 5)
-    sauveContexte();
-
   startIntegrationPhase('ItemFacets');
 }
 $phase = 7.5;
@@ -654,52 +448,6 @@ function traceTraitementNotice()
 	}
 }
 
-// ----------------------------------------------------------------
-// Affichage detail pour l'homogeneisation
-// ----------------------------------------------------------------
-function traceHomogene($id_notice, $isbn, $ean, $id_commerciale)
-{
-	global $debug_level, $ret, $phase_data, $log, $chrono100notices;
-
-	$phase_data[$ret["statut_z3950"]]++;
-	if ($debug_level == 0)
-	{
-		if ($ret["statut"] == "erreur") $log->log('<span class="vert">Connexion au serveur : ' . $ret["serveur"] . ' ---> </span><span class="rouge">' . $ret["erreur"] . '</span>' . BR);
-		elseif (!$phase_data["nombre"]) $log->log('<span class="vert">Connexion au serveur : ' . $ret["serveur"] . ' ---> ok</span>' . BR);
-		if ($phase_data["nombre"] and $phase_data["nombre"] % 100 == 0)
-		{
-			$log->log("notice " . $phase_data["nombre"] . " (" . $chrono100notices->tempsPasse() . " secondes)" . BR);
-			$chrono100notices->start();
-		}
-		return;
-	}
-	$log->log('<a class="notice" href="' . URL_BASE . "php/analyse_afficher_notice_full.php?id_notice=" . $id_notice . '">Notice n° ' . $id_notice . '</a>' . BR);
-	$log->log('<table class="blank" cellspacing="0" cellpadding="5px" style="margin-left:15px;margin-bottom:10px">');
-	if ($isbn) $log->log('<tr><td class="blank">Isbn</td><td class="blank">' . $isbn . '</td></tr>');
-	if ($ean) $log->log('<tr><td class="blank">Ean</td><td class="blank">' . $ean . '</td></tr>');
-	if ($id_commerciale) $log->log('<tr><td class="blank">No commercial</td><td class="blank">' . $id_commerciale . '</td></tr>');
-	$log->log('<tr><td class="blank">Serveur</td><td class="blank">' . $ret["serveur"] . '</td></tr>');
-	if ($ret["statut_z3950"] > 1) $class = "vert";
-	$log->log('<tr><td class="blank">Statut</td><td class="blank"><span class="' . $class . '">' . communication::getLibelleStatutZ3950($ret["statut_z3950"]) . '</span></td></tr>');
-	if ($ret["erreur"]) $log->log('<tr><td class="blank">Erreur</td><td class="blank"><span class="rouge">' . $ret["erreur"] . '</span></td></tr>');
-	$log->log('</table>');
-}
-
-function afficherRecapHomogene($phase_data, $chrono)
-{
-	global $log, $compteur;
-	$compteur[6]+=$phase_data[2] + $phase_data[3]; // Incrementer compteur global d'homogeneisation
-	for ($i = 0; $i < 4; $i++) if (!$phase_data[$i]) $phase_data[$i] = "0";
-
-	$log->log(BR . '<span class="vert">' . ($phase_data["nombre"]) . ' notices traitées</span>' . BR);
-	$chrono->timeStart = $phase_data["timeStart"];
-	$log->log('<span class="vert">Temps de traitement : ' . $chrono->end() . " (" . $chrono->moyenne($phase_data["nombre"], "notices") . ")</span>" . BR);
-	$log->log('<table class="blank" cellspacing="0" cellpadding="5px" style="margin-left:25px;margin-bottom:10px;margin-top:10px">');
-	$log->log('<tr><td class="blank">Notices homogenéisées</td><td class="blank" align="right">' . ($phase_data[2] + $phase_data[3]) . '</td></tr>');
-	$log->log('<tr><td class="blank">Notices non trouvées</td><td class="blank" align="right">' . $phase_data[1] . '</td></tr>');
-	$log->log('<tr><td class="blank">Echecs de connexions</td><td class="blank" align="right">' . $phase_data[0] . '</td></tr>');
-	$log->log('</table>');
-}
 
 
 // ----------------------------------------------------------------
diff --git a/cosmogramme/tests/php/classes/CarthameIntegrationTest.php b/cosmogramme/tests/php/classes/CarthameIntegrationTest.php
index ab61d0fbc0166953901e831766b45384989967b4..6e41201b134b4de421e5024d088c5020e3488fdb 100644
--- a/cosmogramme/tests/php/classes/CarthameIntegrationTest.php
+++ b/cosmogramme/tests/php/classes/CarthameIntegrationTest.php
@@ -28,7 +28,7 @@ abstract class CarthameIntegrationTestCase extends NoticeIntegrationTestCase {
 						 'homogene_code_qualite' => 10,
 						 'homogene' => 0,
 						 'mode_doublon'=> 0,
-						 'tracer_accents_iso'=>1,
+						 'tracer_accents_iso'=> 1,
 						 'non_exportable'=> 'electre;decitre;gam;zebris',
 						 'controle_codes_barres'=> 0,
 						 'unimarc_zone_titre' => '200$a;461$t',
@@ -86,17 +86,23 @@ class TangoMangoCarthameIntegrationTest extends CarthameIntegrationTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		$this->fixture('Class_Notice', ['id' => 7939934,
-																		'type_doc' => 1,
-																		'facettes' => 'Lfre G3 T1 B7 B2 V2 2015-09',
-																		'isbn' => '978-2-359-10416-5',
-																		'clef_alpha' => '------1',
-																		'clef_oeuvre' => '---',
-																		'qualite' => 10,
-																		'exportable' => 1,
-																		'date_creation' => '2015-09-08 00:00:00',
-																		'date_maj' => '2015-03-13 02:00:12',
-																		'unimarc' => '01041cam0 2200289   450 001002100000003004700021010003900068020001700107021002700124073001800151100004100169101000800210102000700218105001800225106000600243200000500249210000100254215000100255225000100256300000100257330048800258461000100746686000100747700000100748801000100749930000100750FRBNF437016350000001http://catalogue.bnf.fr/ark:/12148/cb43701635n  a978-2-35910-416-5brel.d12,90 EUR  aFRb01364260  aFRbDLE-20131029-62048 0a9782359104165  a20131029d2013    m  y0frey50      ba0 afre  aFR  a||||t   00|a|  ar1 aaaAn 982, alors qu\' Elaine et Encre Noire continuent de réparer leur bateau, un évènement inattendu bouleverse les Eaux Tièdes : le retour de la Gazette du Pirate !aLa Gazette du pirate annonce le retour du grand tournoi, le rendez-vous incontournable des écumeurs des mers. Encre Noire et Elaine décident de voler des trésors et d\'amasser de l\'argent pour acheter un nouveau gouvernail afin de mettre toutes les chances de leur côté pour remporter l\'épreuve. ­Electre 2015']);
+
+    Class_CosmoVar::setValueOf('url_services',
+                               'https://websvc.afi-sa.net/afi_opac_services/main.php');
+
+		$this->fixture(Class_Notice::class,
+                   ['id' => 7939934,
+                    'type_doc' => 1,
+                    'facettes' => 'Lfre G3 T1 B7 B2 V2 2015-09',
+                    'isbn' => '978-2-359-10416-5',
+                    'id_commerciale' => '',
+                    'clef_alpha' => '------1',
+                    'clef_oeuvre' => '---',
+                    'qualite' => 10,
+                    'exportable' => 1,
+                    'date_creation' => '2015-09-08 00:00:00',
+                    'date_maj' => '2015-03-13 02:00:12',
+                    'unimarc' => '01041cam0 2200289   450 001002100000003004700021010003900068020001700107021002700124073001800151100004100169101000800210102000700218105001800225106000600243200000500249210000100254215000100255225000100256300000100257330048800258461000100746686000100747700000100748801000100749930000100750FRBNF437016350000001http://catalogue.bnf.fr/ark:/12148/cb43701635n  a978-2-35910-416-5brel.d12,90 EUR  aFRb01364260  aFRbDLE-20131029-62048 0a9782359104165  a20131029d2013    m  y0frey50      ba0 afre  aFR  a||||t   00|a|  ar1 aaaAn 982, alors qu\' Elaine et Encre Noire continuent de réparer leur bateau, un évènement inattendu bouleverse les Eaux Tièdes : le retour de la Gazette du Pirate !aLa Gazette du pirate annonce le retour du grand tournoi, le rendez-vous incontournable des écumeurs des mers. Encre Noire et Elaine décident de voler des trésors et d\'amasser de l\'argent pour acheter un nouveau gouvernail afin de mettre toutes les chances de leur côté pour remporter l\'épreuve. ­Electre 2015']);
 	}
 
 
@@ -118,15 +124,14 @@ class TangoMangoCarthameIntegrationTest extends CarthameIntegrationTestCase {
 	}
 
 
-	/** @test */
+	/**
+   * @test
+   * @group real_network_call
+   */
 	public function homogenizeShouldUpdateNotice() {
 		$notice = Class_Notice::find(7939934);
 		$notice_integration = new notice_integration();
-		$notice_integration->traiteHomogene($notice->getId(),
-																				$notice->getIsbn(),
-																				'',
-																				'',
-																				3);
+		$notice_integration->traiteHomogene($notice);
 
 		Class_Notice::clearCache();
 		$notice = Class_Notice::find(7939934);
diff --git a/library/Class/Cosmogramme/Integration/PhaseAbstract.php b/library/Class/Cosmogramme/Integration/PhaseAbstract.php
index c28775839f55112e45295970d4014cae07defc5b..e5bce908476fa3e4932cd8e0221c912793c2f4a6 100644
--- a/library/Class/Cosmogramme/Integration/PhaseAbstract.php
+++ b/library/Class/Cosmogramme/Integration/PhaseAbstract.php
@@ -205,4 +205,12 @@ abstract class Class_Cosmogramme_Integration_PhaseAbstract {
 
     return $this;
   }
+
+
+  protected function _logError($message) {
+    if ($this->_log)
+      $this->_log->error($message);
+
+    return $this;
+  }
 }
diff --git a/library/Class/Cosmogramme/Integration/PhaseHomogenizeRecord.php b/library/Class/Cosmogramme/Integration/PhaseHomogenizeRecord.php
new file mode 100644
index 0000000000000000000000000000000000000000..e1d7f301ccc9cf458b8e3215a823c37f759dd974
--- /dev/null
+++ b/library/Class/Cosmogramme/Integration/PhaseHomogenizeRecord.php
@@ -0,0 +1,229 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+require_once 'cosmogramme/php/fonctions/variables.php';
+require_once 'cosmogramme/php/classes/classe_notice_integration.php';
+
+
+class Class_Cosmogramme_Integration_PhaseHomogenizeRecord
+  extends Class_Cosmogramme_Integration_PhaseAbstract {
+  use Trait_ServiceRunnerAware;
+
+  const MY_ID = 4;
+
+  protected static $_should_sleep = true;
+
+  protected
+    $_integrator,
+    $_remotly = 0,
+    $_not_found = 0,
+    $_error = 0,
+    $_max_retry,
+    $_is_max_retry = false;
+
+
+  public static function setShouldSleep($flag) {
+    static::$_should_sleep = (bool)$flag;
+  }
+
+
+  public function __construct($phase, $log, $chrono) {
+    parent::__construct($phase, $log, $chrono);
+    $this->_label = $this->_('Homogénéisation Z39.50 des notices par l\'ISBN, l\'EAN ou le numéro commercial');
+    $this->_integrator = (new notice_integration)->setServiceRunner(static::$_service_runner);
+  }
+
+
+  protected function _init($phase) {
+    $phase
+      ->resetDatas()
+      ->setData('nombre', 0)
+      ->setData('id', 0);
+
+    $this->_chrono
+        ->startOnFile()
+        ->startOnRecords();
+
+    return $this;
+  }
+
+
+  /** @return array **/
+  protected function _previousPhaseIds() {
+    return [4];
+  }
+
+
+  protected function _wasRunning() {
+    return $this->_getData('nombre') > 0;
+  }
+
+
+  protected function _execute() {
+    if (!$this->_phase->isCron()) {
+      $this->_logMessage($this->_('L\'homogénéisation des notices ne se lance qu\'en mode cron.'));
+      return;
+    }
+
+    if (!Class_CosmoVar::getValueOf('homogene')) {
+      $this->_logError($this->_('Le processus d\'homogénéisation des notices est désactivé'));
+      return;
+    }
+
+    $frequence = Class_CosmoVar::getValueOf('homogene_frequence');
+    $date = Class_CosmoVar::getValueOf('homogene_date');
+    $ecart = $this->getTimeSource()->daysFrom(strtotime($date));
+    if ($ecart < $frequence) {
+      $this->_logSuccess($this->_('Sera fait dans %s jours', $frequence - $ecart));
+      return;
+    }
+
+    $retry_level = (int)Class_CosmoVar::getValueOf('Z3950_retry_level');
+    $this->_max_retry = Class_CosmoVar::getValueOf('Z3950_max_reconnect');
+    $cache_only = Class_CosmoVar::getValueOf('Z3950_cache_only');
+
+    $this
+      ->_logSuccess($this->_('Niveau de tentatives d\'homogénéisation : %s', $retry_level))
+      ->_logSuccess($this->_('Nombre maxi de tentatives de reconnexions : %s', $this->_max_retry))
+      ->_logSuccess($this->_('Mode de recherche sur les serveurs z39.50 : %s',
+                             Class_CosmoVar::getLabelInList('Z3950_cache_only', $cache_only)))
+      ;
+
+    $quality = (int)Class_CosmoVar::getValueOf('homogene_code_qualite');
+
+    while (!$this->_is_max_retry
+           && ($records = $this->_findPage($retry_level, $quality))) {
+      $this->_runPage($records);
+      $this->_cleanMemory();
+    }
+
+    if ($this->_is_max_retry)
+      $this
+        ->_logError($this->_('Abandon du traitement : maximum de tentatives de reconnexions atteint'));
+
+    $processed = $this->_getData('nombre');
+    $this->_logSuccess($this->_plural($processed,
+                                      'aucune notice traitée',
+                                      '%d notice traitée',
+                                      '%d notices traitées',
+                                      $processed));
+
+    $this->_logSuccess($this->_('Temps de traitement : %s (%s)',
+                                $this->_chrono->endFile(),
+                                $this->_chrono->meanOnFile($processed, $this->_('notices'))));
+
+    $this->_summarize();
+
+    Class_CosmoVar::setValueOf('homogene_date', static::getCurrentDate());
+  }
+
+
+  protected function _findPage($retry_level, $quality) {
+    $where = ['z3950_retry <= ' . (int) $retry_level,
+              'id_notice > ' . (int) $this->_getData('id'),
+              'qualite < ' . (int) $quality,
+              '(isbn > "" or ean > "" or id_commerciale > "")'];
+
+    $params = ['where' => implode(' and ', $where),
+               'order' => 'id_notice',
+               'limit' => 100];
+
+    return Class_Notice::findAllBy($params);
+  }
+
+
+  protected function _runPage($records) {
+    $this->_logPage();
+    foreach($records as $record) {
+      if ($this->_is_max_retry)
+        break;
+
+      $this->_runOne($record);
+    }
+  }
+
+
+  protected function _logPage() {
+    if ((!$current = $this->_getData('nombre'))
+        || (0 != $current % 100))
+      return $this;
+
+    $this->_logSuccess($this->_('notice %s (%s secondes)',
+                                $current,
+                                $this->_chrono->elapsedOnRecords()));
+    $this->_chrono->startOnRecords();
+
+    return $this;
+  }
+
+
+  protected function _runOne($record) {
+    $this
+      ->_incrementData('nombre')
+      ->_setData('id', $record->getId());
+
+    $try = 0;
+    while ($try < $this->_max_retry) {
+      $try++;
+      $result = $this->_integrator->traiteHomogene($record);
+
+      if ($result->isNotFound()) {
+        $this->_not_found++;
+        return $this;
+      }
+
+      if ($result->isRemote()) {
+        $this->_remotly++;
+        return $this;
+      }
+
+      $this->_sleep(5);
+    }
+
+    $this->_error++;
+    $this->_is_max_retry = true;
+
+    return $this;
+  }
+
+
+  protected function _summarize() {
+    $this->_logMessage('<table class="blank" cellspacing="0" cellpadding="5px" style="margin-left:25px;margin-bottom:10px;margin-top:10px">');
+    $map = [$this->_('Notices homogénéisées') => $this->_remotly,
+            $this->_('Notices non trouvées') => $this->_not_found,
+            $this->_('Échecs de connexions') => $this->_error];
+
+    foreach($map as $label => $count)
+      $this
+      ->_logMessage(sprintf('<tr><td class="blank">%s</td><td class="blank" align="right">%d</td></tr>',
+                            $label, $count));
+
+    $this->_logMessage('</table>');
+  }
+
+
+  protected function _sleep($seconds) {
+    if (static::$_should_sleep)
+      sleep($seconds);
+
+    return $this;
+  }
+}
diff --git a/library/Class/Cosmogramme/Integration/PhaseIncompleteRecord.php b/library/Class/Cosmogramme/Integration/PhaseIncompleteRecord.php
index 0b3bd64572fdc6598c81870355bccfcd07d266a3..336cbd95cd0f9feeeb7d58183918e36660633439 100644
--- a/library/Class/Cosmogramme/Integration/PhaseIncompleteRecord.php
+++ b/library/Class/Cosmogramme/Integration/PhaseIncompleteRecord.php
@@ -145,22 +145,22 @@ class Class_Cosmogramme_Integration_PhaseIncompleteRecord
       ->_setData('id', $record->getId());
 
     $result = $this->_integrator->traiteSuccinte($record);
-    if (Class_NoticeSuccincte::STATUS_ERROR === $result) {
+    if ($result->isError()) {
       $this->_error++;
       return $this;
     }
 
-    if (Class_NoticeSuccincte::STATUS_NOTFOUND === $result) {
+    if ($result->isNotFound()) {
       $this->_not_found++;
       return $this;
     }
 
-    if (Class_NoticeSuccincte::STATUS_REMOTE === $result) {
+    if ($result->isRemote()) {
       $this->_remotly++;
       return $this;
     }
 
-    if (Class_NoticeSuccincte::STATUS_LOCAL === $result)
+    if ($result->isLocal())
       $this->_locally++;
 
     return $this;
diff --git a/library/Class/NoticeSuccincte.php b/library/Class/NoticeSuccincte.php
index 68536c39e09e5c1f36b535e343f16a929bf1607d..7331c83b61b57336cfc68adabb5c1b3956b4cf26 100644
--- a/library/Class/NoticeSuccincte.php
+++ b/library/Class/NoticeSuccincte.php
@@ -20,12 +20,6 @@
  */
 
 class Class_NoticeSuccincte extends Storm_Model_Abstract {
-  const
-    STATUS_LOCAL    = 'local',
-    STATUS_REMOTE   = 'remote',
-    STATUS_NOTFOUND = 'notfound',
-    STATUS_ERROR    = 'error';
-
   protected
     $_table_name = 'notices_succintes',
     $_belongs_to = ['bib' => ['model' => 'Class_IntBib',
diff --git a/library/Class/NoticeUnimarc/Writer.php b/library/Class/NoticeUnimarc/Writer.php
index 8f6d30ef989925e031858802c15c3e13b2906d04..dabfb9d0c3717c631fc95330d8ad8cebe6764650 100644
--- a/library/Class/NoticeUnimarc/Writer.php
+++ b/library/Class/NoticeUnimarc/Writer.php
@@ -220,8 +220,14 @@ class Class_NoticeUnimarc_Writer extends Class_NoticeUnimarc {
 
   public function delete_items() {
     $this->delete_field("995");
-    if($this->profil["attributs"][0]["champ_code_barres"] == "997") $this->delete_field("997"); // astrolabe
-    if($this->profil["attributs"][0]["champ_code_barres"] == "852") $this->delete_field("852"); // Moulins
+    if (!isset($this->profil["attributs"][0]["champ_code_barres"]))
+      return;
+
+    if ($this->profil["attributs"][0]["champ_code_barres"] == "997")
+      $this->delete_field("997"); // astrolabe
+
+    if ($this->profil["attributs"][0]["champ_code_barres"] == "852")
+      $this->delete_field("852"); // Moulins
   }
 
 
@@ -499,7 +505,7 @@ class Class_NoticeUnimarc_Writer extends Class_NoticeUnimarc {
       chr(0xec) =>'þ',
       chr(0xf1) =>'æ',
       chr(0xf3) =>'ð',
-      chr(0xf8) =>'°'.chr($char2),
+      chr(0xf8) =>'°',
       chr(0xf9) =>'ø',
       chr(0xfb) =>'ß',
       chr(0x80) =>'€',
diff --git a/library/Class/WebService/Afi/Status.php b/library/Class/WebService/Afi/Status.php
new file mode 100644
index 0000000000000000000000000000000000000000..8178bfbec1cc871ca2bc5135271204f48fd7f5da
--- /dev/null
+++ b/library/Class/WebService/Afi/Status.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_WebService_Afi_Status {
+  public static function newLocal() {
+    return new Class_WebService_Afi_StatusLocal;
+  }
+
+
+  public static function newRemote() {
+    return new Class_WebService_Afi_StatusRemote;
+  }
+
+
+  public static function newNotFound() {
+    return new Class_WebService_Afi_StatusNotFound;
+  }
+
+
+  public static function newError() {
+    return new Class_WebService_Afi_StatusError;
+  }
+
+
+  public function isLocal() {
+    return false;
+  }
+
+
+  public function isRemote() {
+    return false;
+  }
+
+
+  public function isNotFound() {
+    return false;
+  }
+
+
+  public function isError() {
+    return false;
+  }
+}
+
+
+
+class Class_WebService_Afi_StatusLocal extends Class_WebService_Afi_Status {
+  public function isLocal() {
+    return true;
+  }
+}
+
+
+
+class Class_WebService_Afi_StatusRemote extends Class_WebService_Afi_Status {
+  public function isRemote() {
+    return true;
+  }
+}
+
+
+
+class Class_WebService_Afi_StatusNotFound extends Class_WebService_Afi_Status {
+  public function isNotFound() {
+    return true;
+  }
+}
+
+
+
+class Class_WebService_Afi_StatusError extends Class_WebService_Afi_Status {
+  public function isError() {
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/InspectorGadget.php b/library/ZendAfi/Controller/Plugin/InspectorGadget.php
index ac2b36bb1f8ef7174724f9df0e8cd9a9bebaa26c..6112e5ed284b959b18e7f49d7e8a3d42663710c7 100644
--- a/library/ZendAfi/Controller/Plugin/InspectorGadget.php
+++ b/library/ZendAfi/Controller/Plugin/InspectorGadget.php
@@ -225,6 +225,9 @@ create: function(event, ui) { if (ui.panel.hasClass(\'ig-accordion\')) ui.panel.
 
 
   public function logError($url, $message) {
+    if (!$this->isEnabled())
+      return $this;
+
     $call = new ZendAfi_Controller_Plugin_InspectorGadget_ServiceCall($url,
                                                                       '',
                                                                       '',
diff --git a/tests/library/Class/Cosmogramme/Integration/PhaseHomogenizeRecordTest.php b/tests/library/Class/Cosmogramme/Integration/PhaseHomogenizeRecordTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3765cfc49a3d8a57c9852adf9203a9cd98ed792b
--- /dev/null
+++ b/tests/library/Class/Cosmogramme/Integration/PhaseHomogenizeRecordTest.php
@@ -0,0 +1,296 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+abstract class PhaseHomogenizeRecordTestCase extends Class_Cosmogramme_Integration_PhaseTestCase {
+  protected $_http_client;
+
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    Class_CosmoVar::setValueOf('url_services', 'https://cache.myserver.org/api');
+    Class_CosmoVar::setValueOf('unimarc_zone_titre', '200$a;461$t');
+    Class_CosmoVar::setValueOf('Z3950_cache_only', 0);
+    Class_CosmoVar::setValueOf('Z3950_retry_level', 0);
+    Class_CosmoVar::setValueOf('Z3950_max_reconnect', 3);
+    Class_CosmoVar::setValueOf('homogene_code_qualite', 10);
+    Class_CosmoVar::setValueOf('homogene_frequence', 2);
+    Class_CosmoVar::setValueOf('homogene', 1);
+
+    $this->_http_client = $this->mock();
+    Class_WebService_Afi::setHttpClient($this->_http_client);
+
+    Class_Cosmogramme_Integration_PhaseHomogenizeRecord::setShouldSleep(false);
+  }
+
+
+  protected function _getPreviousPhase() {
+    return (new Class_Cosmogramme_Integration_Phase(4))->beCron();
+  }
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_phase = $this->_buildPhase('HomogenizeRecord')->run();
+    Class_Notice::clearCache();
+  }
+
+
+  public function tearDown() {
+    Class_WebService_Afi::setHttpClient(null);
+    Class_Cosmogramme_Integration_PhaseHomogenizeRecord::setTimeSource(null);
+    Class_Cosmogramme_Integration_PhaseHomogenizeRecord::setShouldSleep(true);
+
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function logShouldContainsHomogeneisationParIsbn() {
+    $this->assertLogContains('Homogénéisation Z39.50 des notices par l\'ISBN, l\'EAN ou le numéro commercial');
+  }
+}
+
+
+
+
+class PhaseHomogenizeRecordDisabledTest extends PhaseHomogenizeRecordTestCase {
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    Class_CosmoVar::setValueOf('homogene', 0);
+  }
+
+
+  /** @test */
+  public function logShouldContainsProcessusDesactive() {
+    $this->assertLogContains('Le processus d\'homogénéisation des notices est désactivé');
+  }
+}
+
+
+
+
+class PhaseHomogenizeRecordDelayNotExpiredTest extends PhaseHomogenizeRecordTestCase {
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    Class_CosmoVar::setValueOf('homogene_date', '2021-09-08');
+    Class_Cosmogramme_Integration_PhaseHomogenizeRecord::setTimeSource(new TimeSourceForTest('2021-09-09'));
+  }
+
+
+  /** @test */
+  public function logShouldContainsSeraFaitDans2Jours() {
+    $this->assertLogContains('Sera fait dans 2 jours');
+  }
+}
+
+
+
+
+abstract class PhaseHomogenizeRecordAtTimeOfRunning extends PhaseHomogenizeRecordTestCase {
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    Class_CosmoVar::setValueOf('homogene_date', '2021-09-01');
+    Class_Cosmogramme_Integration_PhaseHomogenizeRecord::setTimeSource(new TimeSourceForTest('2021-09-09'));
+
+    $this->onLoaderOfModel(Class_Notice::class)
+         ->whenCalled('findAllBy')
+         ->with(['where' => 'z3950_retry <= 0 and id_notice > 0 and qualite < 10 and (isbn > "" or ean > "" or id_commerciale > "")',
+                 'order' => 'id_notice',
+                 'limit' => 100])
+         ->answers([$this->fixture(Class_Notice::class,
+                                   ['id' => 1,
+                                    'isbn' => '978-2-290-03548-1',
+                                    'id_commerciale' => '']),
+                    $this->fixture(Class_Notice::class,
+                                   ['id' => 2,
+                                    'ean' => '4029759158189',
+                                    'id_commerciale' => ''])])
+
+         ->whenCalled('findAllBy')
+         ->answers([]);
+  }
+
+
+  protected function assertLogContainsReportLine($label, $count) {
+    $this->assertLogContains(sprintf('<td class="blank">%s</td><td class="blank" align="right">%s</td>',
+                                     $label, $count));
+  }
+
+
+  /**
+   * @test
+   * @dataProvider messages
+   */
+  public function logShouldContainsReportMessage($label, $count) {
+    $this->assertLogContainsReportLine($label, $count);
+  }
+}
+
+
+
+
+class PhaseHomogenizeRecordHttpErrorTest extends PhaseHomogenizeRecordAtTimeOfRunning {
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    $this->_http_client
+      ->whenCalled('open_url')
+      ->willDo(function()
+               {
+                 throw new RuntimeException('timed out');
+               });
+  }
+
+
+  /** @test */
+  public function logShouldContainsAbandonTraitement() {
+    $this->assertLogContains('Abandon du traitement : maximum de tentatives de reconnexions atteint');
+  }
+
+
+  /** @test */
+  public function logShouldContains1NoticeTraitee() {
+    $this->assertLogContains('1 notice traitée');
+  }
+
+
+  public function messages() {
+    return [['Notices homogénéisées', 0],
+            ['Notices non trouvées', 0],
+            ['Échecs de connexions', 1]];
+  }
+}
+
+
+
+
+class PhaseHomogenizeRecordFoundRemotlyTest extends PhaseHomogenizeRecordAtTimeOfRunning {
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    $blade_runner = (new Class_NoticeUnimarc_Fluent)
+      ->zoneWithContent('001', 'FRBNF427661630000001')
+      ->zoneWithContent('003', 'http://catalogue.bnf.fr/ark:/12148/cb42766163t')
+      ->zoneWithChildren('010', ['a' => '978-2-290-03548-1'])
+      ->zoneWithChildren('200', ['a' => 'Blade runner'])
+      ->zoneWithChildren('700', ['a' => 'Dick', 'b' => 'Philip K.']);
+
+    $baby_metal = (new Class_NoticeUnimarc_Fluent)
+      ->zoneWithContent('001', 'FRBNF467745090000000')
+      ->zoneWithContent('003', 'http://catalogue.bnf.fr/ark:/12148/cb46774509d')
+      ->zoneWithChildren('073', ['a' => '4029759158189'])
+      ->zoneWithChildren('200', ['a' => '10 Baby Metal years'])
+      ->zoneWithChildren('710', ['a' => 'Babymetal']);
+
+    $this->_http_client
+      ->whenCalled('open_url')
+      ->willDo(function($url) use($blade_runner, $baby_metal)
+               {
+                 parse_str(parse_url($url, PHP_URL_QUERY), $params);
+                 $response = ['statut=OK',
+                              'statut_z3950=3',
+                              'serveur=Bnf',
+                              'erreur='];
+
+                 if (isset($params['isbn']) && '978-2-290-03548-1' == $params['isbn']) {
+                   $response[] = 'unimarc=' . $blade_runner->render();
+                   return implode("\t", $response);
+                 }
+
+                 if (isset($params['ean']) && '4029759158189' == $params['ean']) {
+                   $response[] = 'unimarc=' . $baby_metal->render();
+                   return implode("\t", $response);
+                 }
+               });
+  }
+
+
+  /** @test */
+  public function logShouldContains2NoticesTraitees() {
+    $this->assertLogContains('2 notices traitées');
+  }
+
+
+  /** @test */
+  public function firstRecordTitleShouldBeBladeRunner() {
+    $this->assertEquals('Blade runner', Class_Notice::find(1)->getTitrePrincipal());
+  }
+
+
+  /** @test */
+  public function firstRecordQualiteShouldBecome10() {
+    $this->assertEquals(10, Class_Notice::find(1)->getQualite());
+  }
+
+
+  /** @test */
+  public function secondRecordTitleShouldBe10BabyMetalYears() {
+    $this->assertEquals('10 Baby Metal years', Class_Notice::find(2)->getTitrePrincipal());
+  }
+
+
+  public function messages() {
+    return [['Notices homogénéisées', 2],
+            ['Notices non trouvées', 0],
+            ['Échecs de connexions', 0]];
+  }
+}
+
+
+
+
+class PhaseHomogenizeRecordNotFoundTest extends PhaseHomogenizeRecordAtTimeOfRunning {
+  protected function _prepareFixtures() {
+    parent::_prepareFixtures();
+    $this->_http_client
+      ->whenCalled('open_url')
+      ->answers(implode("\t", ['statut=OK', 'statut_z3950=1']));
+  }
+
+
+  /** @test */
+  public function logShouldContains2NoticesTraitees() {
+    $this->assertLogContains('2 notices traitées');
+  }
+
+
+  /** @test */
+  public function bladeRunnerUnimarcShouldStayEmpty() {
+    $this->assertEquals('', Class_Notice::find(1)->getUnimarc());
+  }
+
+
+  /** @test */
+  public function bladeRunnerQualiteShouldStay0() {
+    $this->assertEquals(0, Class_Notice::find(1)->getQualite());
+  }
+
+
+  /** @test */
+  public function bladeRunnerRetryLevelShouldBe1() {
+    $this->assertEquals(1, Class_Notice::find(1)->getZ3950Retry());
+  }
+
+
+  public function messages() {
+    return [['Notices homogénéisées', 0],
+            ['Notices non trouvées', 2],
+            ['Échecs de connexions', 0]];
+  }
+}