From c524bed689e796fffdedf151c83581ce84282b33 Mon Sep 17 00:00:00 2001
From: Alex Arnaud <alex.arnaud@biblibre.com>
Date: Tue, 18 Jun 2024 14:16:09 +0200
Subject: [PATCH] Dev#173687 request availability with items api

---
 FEATURES/189455                               |  10 +
 VERSIONS_WIP/173687                           |   1 +
 VERSIONS_WIP/189455                           |   1 +
 library/Class/Exemplaire.php                  |  22 ++
 library/Class/Notice.php                      |   1 -
 .../Class/WebService/SIGB/AbstractService.php |  11 +
 .../SIGB/Koha/AbstractResponseReader.php      |  48 +++
 .../WebService/SIGB/Koha/CommunityService.php |  48 ++-
 .../SIGB/Koha/GetRecordsResponseReader.php    |  13 +-
 .../SIGB/Koha/ItemResponseReader.php          | 182 ++++++++++++
 .../SIGB/Koha/ListItemsResponseReader.php     |  31 ++
 .../Class/WebService/SIGB/Koha/Service.php    |  74 ++++-
 library/Class/WebService/SIGB/NoticeCache.php |  11 +-
 .../Intonation/Library/Record/Items.php       |   6 +-
 tests/fixtures/MAE_GetRecords273230.xml       | 136 +++++++++
 tests/fixtures/item_340920.json               |  45 +++
 tests/fixtures/item_codification_340920.json  |  46 +++
 tests/fixtures/item_damaged_340920.json       |  45 +++
 tests/fixtures/item_withdrawn_340920.json     |  46 +++
 tests/library/Class/HttpClientForTest.php     |   5 +-
 .../WebService/SIGB/KohaItemResponseTest.php  | 201 +++++++++++++
 tests/scenarios/Journal/JournalTest.php       |   2 +-
 .../TemplatesFakeItemsAvailabilityTest.php    | 131 +++++++++
 ...TemplatesKohaCommunityAvailabilityTest.php | 274 ++++++++++++++++++
 24 files changed, 1365 insertions(+), 25 deletions(-)
 create mode 100644 FEATURES/189455
 create mode 100644 VERSIONS_WIP/173687
 create mode 100644 VERSIONS_WIP/189455
 create mode 100644 library/Class/WebService/SIGB/Koha/AbstractResponseReader.php
 create mode 100644 library/Class/WebService/SIGB/Koha/ItemResponseReader.php
 create mode 100644 library/Class/WebService/SIGB/Koha/ListItemsResponseReader.php
 create mode 100644 tests/fixtures/MAE_GetRecords273230.xml
 create mode 100644 tests/fixtures/item_340920.json
 create mode 100644 tests/fixtures/item_codification_340920.json
 create mode 100644 tests/fixtures/item_damaged_340920.json
 create mode 100644 tests/fixtures/item_withdrawn_340920.json
 create mode 100644 tests/library/Class/WebService/SIGB/KohaItemResponseTest.php
 create mode 100644 tests/scenarios/Templates/TemplatesFakeItemsAvailabilityTest.php
 create mode 100644 tests/scenarios/Templates/TemplatesKohaCommunityAvailabilityTest.php

diff --git a/FEATURES/189455 b/FEATURES/189455
new file mode 100644
index 00000000000..8157c7581e8
--- /dev/null
+++ b/FEATURES/189455
@@ -0,0 +1,10 @@
+        '189455' =>
+            ['Label' => $this->_('Utilisation de l'API items de Koha pour le calcul de la disponibilité des exemplaires'),
+             'Desc' => '',
+             'Image' => '',
+             'Video' => '',
+             'Category' => $this->_('Webservice Koha'),
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => '',
+             'Test' => '',
+             'Date' => '2024-03-25'],
\ No newline at end of file
diff --git a/VERSIONS_WIP/173687 b/VERSIONS_WIP/173687
new file mode 100644
index 00000000000..f31ca69fc48
--- /dev/null
+++ b/VERSIONS_WIP/173687
@@ -0,0 +1 @@
+ - fonctionnalité #173687 : SIGB Koha : Affichage de la disponibilité des exemplaires pour les notices de dépouillement d'article ou mélanges
\ No newline at end of file
diff --git a/VERSIONS_WIP/189455 b/VERSIONS_WIP/189455
new file mode 100644
index 00000000000..273b95f3697
--- /dev/null
+++ b/VERSIONS_WIP/189455
@@ -0,0 +1 @@
+ - fonctionnalité #189455 : Webservice Koha : Utilisation de l'API items de Koha pour la disponibilité
diff --git a/library/Class/Exemplaire.php b/library/Class/Exemplaire.php
index 52cf6b254ee..7d2d05691ce 100644
--- a/library/Class/Exemplaire.php
+++ b/library/Class/Exemplaire.php
@@ -762,4 +762,26 @@ class Class_Exemplaire extends Storm_Model_Abstract {
     return ($bib_id == $this->getIdBib()
             && $this->isNouveaute());
   }
+
+
+  public function isFromKoha(): bool
+  {
+    return 1 === Class_IntBib::query()
+      ->eq('id_bib', $this->getIdIntBib())
+      ->eq('sigb', Class_IntBib::SIGB_KOHA)
+      ->countAll();
+  }
+
+
+  public function hasConcatenedBarcode(): bool
+  {
+    return false !== strpos($this->getCodeBarres(), '-');
+  }
+
+
+  public function barcodeFirstPart(): string
+  {
+    $barcode = explode('-', $this->getCodeBarres());
+    return array_shift($barcode);
+  }
 }
diff --git a/library/Class/Notice.php b/library/Class/Notice.php
index 99248e4c7f9..26a7f778f14 100644
--- a/library/Class/Notice.php
+++ b/library/Class/Notice.php
@@ -1689,6 +1689,5 @@ class Class_Notice extends Storm_Model_Abstract {
       if ($item->isNouveauteForLibrary($bib_id))
         return true;
     return false;
-
   }
 }
diff --git a/library/Class/WebService/SIGB/AbstractService.php b/library/Class/WebService/SIGB/AbstractService.php
index 787897168e2..dcd9051ddeb 100644
--- a/library/Class/WebService/SIGB/AbstractService.php
+++ b/library/Class/WebService/SIGB/AbstractService.php
@@ -162,6 +162,12 @@ abstract class Class_WebService_SIGB_AbstractService {
   }
 
 
+  public function providesItemInformationService() : bool
+  {
+    return false;
+  }
+
+
   public function providesSuggestions() :bool {
     return false;
   }
@@ -358,6 +364,11 @@ abstract class Class_WebService_SIGB_AbstractService {
   }
 
 
+  public function getItem(Class_Exemplaire  $item) : ?Class_WebService_SIGB_Exemplaire
+  {
+    return null;
+  }
+
   abstract public function getServerRoot();
 
   abstract public function getEmprunteur($user);
diff --git a/library/Class/WebService/SIGB/Koha/AbstractResponseReader.php b/library/Class/WebService/SIGB/Koha/AbstractResponseReader.php
new file mode 100644
index 00000000000..0d107007baf
--- /dev/null
+++ b/library/Class/WebService/SIGB/Koha/AbstractResponseReader.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright (c) 2012-2023, 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_SIGB_Koha_AbstractResponseReader {
+  protected $_service;
+
+  protected array $_not_for_loan_status = [];
+  protected array $_withdrawn_mapping = [];
+  protected array $_grouped_holds_itypes = [];
+  protected bool $_interdire_resa_doc_dispo = false;
+
+  public function __construct(Class_WebService_SIGB_AbstractService $service)
+  {
+    $this->_service = $service;
+    $this->_setNotForLoanStatus($service);
+    $this->_withdrawn_mapping = $service->withdrawnMapping();
+    $this->_interdire_resa_doc_dispo = $service->interdireResaDocDispo();
+    $this->_grouped_holds_itypes = $service->groupedHoldsItypes();
+  }
+
+
+  protected function _setNotForLoanStatus(Class_WebService_SIGB_AbstractService $service)
+  {
+    $this->_not_for_loan_status = $service->notForLoanStatus();
+    foreach ($service->codificationDisponibilites() as $status => $status_label)
+      $this->_not_for_loan_status[$status] = $status_label;
+    return $this;
+  }
+}
diff --git a/library/Class/WebService/SIGB/Koha/CommunityService.php b/library/Class/WebService/SIGB/Koha/CommunityService.php
index 4ee4329afc7..ac8a9cd3429 100644
--- a/library/Class/WebService/SIGB/Koha/CommunityService.php
+++ b/library/Class/WebService/SIGB/Koha/CommunityService.php
@@ -25,9 +25,9 @@ class Class_WebService_SIGB_Koha_CommunityService
 
   const JSON_ENCODED = 'application/json';
 
-  protected
-    $_ilsdi_service,
-    $_current_action;
+  protected Class_WebService_SIGB_Koha_Service $_ilsdi_service;
+
+  protected $_current_action;
 
   public static function newFromIlsdi($ilsdi_service) {
     return new static($ilsdi_service);
@@ -470,4 +470,46 @@ class Class_WebService_SIGB_Koha_CommunityService
   protected function _forwardToIlsdi($name, $params) {
     return call_user_func_array([$this->_ilsdi_service, $name], $params);
   }
+
+
+  public function getIlsdiService() : Class_WebService_SIGB_Koha_Service
+  {
+    return $this->_ilsdi_service ??= new Class_WebService_SIGB_Koha_Service();
+  }
+
+
+  public function getItem(Class_Exemplaire $item) : ?Class_WebService_SIGB_Koha_Exemplaire
+  {
+    $ils_item = null;
+
+    if ($sigb_uniq_id = $item->getUniqItemNumber())
+      $ils_item = $this->getItemById($sigb_uniq_id);
+
+    if (!$ils_item && $barcode = $item->getCodeBarres())
+      $ils_item = $this->getItemByBarcode($barcode);
+
+    return $ils_item;
+  }
+
+
+  protected function getItemById (string $unique_sigb_itemnumber) : ?Class_WebService_SIGB_Koha_Exemplaire
+  {
+    $url = $this->_buildUrlForEndpointWithParams('items/'.$unique_sigb_itemnumber);
+
+    $json = $this->httpGetWithParams($url, $this->_getAuth());
+
+    return (new Class_WebService_SIGB_Koha_ItemResponseReader($this->_ilsdi_service))
+      ->parse($json);
+  }
+
+
+  protected function getItemByBarcode (string $barcode) : ?Class_WebService_SIGB_Koha_Exemplaire
+  {
+    $url = $this->_buildUrlForEndpointWithParams('items/', ['q' => json_encode(['barcode' => $barcode])]);
+
+    $json = $this->httpGetWithParams($url, $this->_getAuth());
+
+    return (new Class_WebService_SIGB_Koha_ListItemsResponseReader($this->_ilsdi_service))
+      ->parse($json);
+  }
 }
diff --git a/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php b/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php
index f1bd5a6861e..c4105c6ad0a 100644
--- a/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php
+++ b/library/Class/WebService/SIGB/Koha/GetRecordsResponseReader.php
@@ -19,7 +19,8 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
+class Class_WebService_SIGB_Koha_GetRecordsResponseReader
+extends Class_WebService_SIGB_Koha_AbstractResponseReader {
   use Class_WebService_SIGB_Koha_TraitFormat;
   use Trait_Translator;
 
@@ -29,8 +30,6 @@ class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
     $_record,
     $_item,
     $_holds = [],
-    $_not_for_loan_status = [],
-    $_grouped_holds_itypes = [],
     $_cannot_hold_available = false;
 
 
@@ -57,12 +56,6 @@ class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
   }
 
 
-  public function setInterdireResaDocDispo($dispo) {
-    $this->_cannot_hold_available = $dispo;
-    return $this;
-  }
-
-
   public function setCodificationDisponibilites($codif) {
     foreach($codif as $status => $libelle)
       $this->_not_for_loan_status[$status] = $libelle;
@@ -84,7 +77,7 @@ class Class_WebService_SIGB_Koha_GetRecordsResponseReader {
 
 
   public function allowAvailableDocumentReservation() {
-    return !$this->_cannot_hold_available;
+    return !$this->_interdire_resa_doc_dispo;
   }
 
 
diff --git a/library/Class/WebService/SIGB/Koha/ItemResponseReader.php b/library/Class/WebService/SIGB/Koha/ItemResponseReader.php
new file mode 100644
index 00000000000..e49b0bfb158
--- /dev/null
+++ b/library/Class/WebService/SIGB/Koha/ItemResponseReader.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Copyright (c) 2012-2023, 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_SIGB_Koha_ItemResponseReader
+  extends Class_WebService_SIGB_Koha_AbstractResponseReader {
+  use Class_WebService_SIGB_Koha_TraitFormat;
+  use Trait_Translator;
+
+  protected Class_WebService_SIGB_Koha_Exemplaire $_item;
+  protected array $_data;
+
+
+  public function parse(string $json) : ?Class_WebService_SIGB_Koha_Exemplaire
+  {
+    $this->_item = new Class_WebService_SIGB_Koha_Exemplaire(null);
+
+    $this->_data = json_decode($json, true);
+
+    if ($this->_isResponseError())
+      return null;
+
+    $this->_getIlsItemData();
+
+    $this->_item
+      ->setId($this->_data['item_id']?? 0)
+      ->setNoNotice($this->_data['biblio_id'] ?? 0)
+      ->setCodeBarre($this->_data['barcode'] ?? '')
+      ->setNbReservations($this->_data['holds_count'] ?? 0)
+      ->setCote($this->_data['callnumber'] ?? '')
+      ->setDisponibiliteLibre()
+      ->beReservable();
+
+    $this->_handleWithdrawn()
+         ->_handleDateDue()
+         ->_handleLost()
+         ->_handleDamaged()
+         ->_handleIType()
+         ->_handleNotForLoan();
+
+    return $this->_item;
+  }
+
+
+  protected function _getIlsItemData() : ?array
+  {
+    return $this->_data;
+  }
+
+
+  protected function _isResponseError() : bool
+  {
+    return isset($this->_data['error']);
+  }
+
+
+  protected function _handleDateDue() : self
+  {
+    $date_due = '';
+
+    if ($value = $this->_data['checked_out_date'] ?? '')
+      $this
+        ->_item
+        // ATTENTION !!!! We donot have return_date in item,
+        // and we cannot set checkoutDate for this object
+        ->setDateRetour($value)
+        ->setDisponibiliteEnPret();
+
+    return $this;
+  }
+
+
+  protected function _handleNotForLoan() : self
+  {
+    if (!($value = $this->_data['effective_not_for_loan_status'] ?? $this->_data['not_for_loan_status']))
+      return $this;
+
+    if ($this->_item->isPiege()) {
+      $this->_item->setReservable(false);
+      return $this;
+    }
+
+    if (0 < (int)$value)
+      $this->_item->notForLoan()
+                  ->setReservable(false);
+
+    if (0 > (int)$value)
+      $this->_item->notForLoan()
+                  ->setReservable(true);
+
+    $this->_item->setDisponibilite($this->_getNotForLoanLabel($value));
+
+    return $this;
+  }
+
+
+  protected function _getNotForLoanLabel(string $not_for_loan) : string
+  {
+    return ($label = $this->_not_for_loan_status[$not_for_loan])
+      ? $label
+      : '';
+
+  }
+
+
+  protected function _handleDamaged() : self
+  {
+    if (!($value = $this->_data['damaged_status']))
+      return $this;
+
+    $this
+      ->_item
+      ->setEndommage(true)
+      ->setDisponibilite($this->_item->message('DISPO_ENDOMMAGE'));
+
+    return $this;
+  }
+
+
+  protected function _handleLost() : self
+  {
+    if (!($value = $this->_data['lost_status']))
+      return $this;
+
+    $this
+      ->_item
+      ->setPerdu(true)
+      ->setDisponibilite($this->_item->message('DISPO_PERDU'));
+
+    return $this;
+  }
+
+
+  protected function _handleIType() : self
+  {
+    if (!$itype = $this->_data['item_type_id'] ?? $this->_data['effective_item_type_id'])
+      return $this;
+
+    $itypes = $this->_service->getGroupedHoldsITypes();
+
+    if (in_array($itype, $itypes))
+      $this->_item->doRequiresCalendarHold();
+
+    return $this;
+  }
+
+
+  protected function _handleWithdrawn() : self
+  {
+    $withdrawn = $this->_data['withdrawn'];
+
+    if (0 == $withdrawn)
+      return $this;
+
+    $this->_item->setRetire(true);
+    $this->_item->setDisponibilitePilonne();
+
+    if ($value = $this->_withdrawn_mapping[$withdrawn] ?? '')
+      $this->_item->setDisponibilite($value);
+
+    return $this;
+  }
+
+}
diff --git a/library/Class/WebService/SIGB/Koha/ListItemsResponseReader.php b/library/Class/WebService/SIGB/Koha/ListItemsResponseReader.php
new file mode 100644
index 00000000000..a62f99ebd87
--- /dev/null
+++ b/library/Class/WebService/SIGB/Koha/ListItemsResponseReader.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright (c) 2012-2023, 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_SIGB_Koha_ListItemsResponseReader
+  extends Class_WebService_SIGB_Koha_ItemResponseReader {
+
+  protected function _getIlsItemData() : ?array
+  {
+    $ils_items = $this->_data;
+    return $this->_data = reset($ils_items);
+  }
+}
diff --git a/library/Class/WebService/SIGB/Koha/Service.php b/library/Class/WebService/SIGB/Koha/Service.php
index e3060f0e30b..ff5462176d0 100644
--- a/library/Class/WebService/SIGB/Koha/Service.php
+++ b/library/Class/WebService/SIGB/Koha/Service.php
@@ -144,6 +144,25 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   }
 
 
+  public function notForLoanStatus(){
+      return
+        [0 => Class_WebService_SIGB_Exemplaire::newInstance()->message('DISPO_LIBRE'),
+         1 => $this->_('Exclu du prêt'),
+         2 => $this->_('En traitement'),
+         3 => $this->_('Consultation sur place'),
+         4 => $this->_('En réserve'),
+         5 => $this->_('En réparation'),
+         6 => $this->_('En reliure'),
+         7 => $this->_('Exclu du prêt temporairement')];
+  }
+
+
+  public function getGroupedHoldsITypes() : array
+  {
+    return $this->_grouped_holds_itypes;
+  }
+
+
   public function setCreateCategoryUsergroup($should_create) {
     $this->create_category_usergroup = $should_create;
     return $this;
@@ -282,6 +301,16 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   }
 
 
+  public function getILSWsItemFromCacheOrRequest(Class_Exemplaire $item) : Class_WebService_SIGB_Exemplaire {
+
+    if ($alternative_item = Class_Exemplaire::findFirstBy(['code_barres' => $item->barcodeFirstPart(),
+                                                           'id_int_bib' => $item->getIdIntBib()]))
+      $item = $alternative_item;
+
+    return $this->getNoticeCache()->getItemFromCacheOrILSWs($item);
+  }
+
+
   protected function _getIlsIdThroughAuthenticateService(Class_Users $user) : string {
     if ( ! $password = $user->getPassword())
       return '';
@@ -533,11 +562,7 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
 
 
   protected function _getReader() {
-    return Class_WebService_SIGB_Koha_GetRecordsResponseReader::newInstance()
-      ->setCodificationDisponibilites($this->codification_disponibilites)
-      ->setWithdrawnMapping($this->_withdrawn_mapping)
-      ->setInterdireResaDocDispo($this->interdire_resa_doc_dispo)
-      ->setGroupedHoldsITypes($this->_grouped_holds_itypes);
+    return new Class_WebService_SIGB_Koha_GetRecordsResponseReader($this);
   }
 
 
@@ -618,4 +643,43 @@ class Class_WebService_SIGB_Koha_Service extends Class_WebService_SIGB_AbstractR
   public function getItemsThreshold() : int {
     return (int) $this->getDisableItemsAvailabilityThreshold();
   }
+
+
+  public function getItem(Class_Exemplaire $item) : ?Class_WebService_SIGB_Koha_Exemplaire
+  {
+    if (!$community_service = $this->getNewCommunityService())
+      return null;
+
+    return $community_service->getItem($item);
+  }
+
+
+  public function providesItemInformationService() : bool
+  {
+    return true;
+  }
+
+
+  public function codificationDisponibilites() : array
+  {
+    return $this->codification_disponibilites ?? [];
+  }
+
+
+  public function withdrawnMapping() : array
+  {
+    return $this->_withdrawn_mapping ?? [];
+  }
+
+
+  public function interdireResaDocDispo() : bool
+  {
+    return $this->interdire_resa_doc_dispo ?? false;
+  }
+
+
+  public function groupedHoldsItypes() : array
+  {
+    return $this->_grouped_holds_itypes ?? [];
+  }
 }
diff --git a/library/Class/WebService/SIGB/NoticeCache.php b/library/Class/WebService/SIGB/NoticeCache.php
index 0d47227890f..4d06eb5fc2c 100644
--- a/library/Class/WebService/SIGB/NoticeCache.php
+++ b/library/Class/WebService/SIGB/NoticeCache.php
@@ -75,7 +75,9 @@ class Class_WebService_SIGB_NoticeCache {
 
 
   protected function _getFirstItemFromILSDIUsing(Class_Exemplaire $item) : Class_WebService_SIGB_Exemplaire {
-    return $this->_findILSDIRecord($item)->getFirstILSItemLike($item);
+    return ($this->_provider->providesItemInformationService())
+      ? $this->_getItemFromWs($item)
+      : $this->_findILSDIRecord($item)->getFirstILSItemLike($item);
   }
 
 
@@ -89,4 +91,11 @@ class Class_WebService_SIGB_NoticeCache {
     return $this->_cache[$origin_id] =
       ($this->_provider->getNotice($origin_id) ?? new Class_WebService_SIGB_Notice(''));
   }
+
+  protected function _getItemFromWs(Class_Exemplaire $item) : Class_WebService_SIGB_Exemplaire
+  {
+    return ($sigb_exemplaire = $this->_provider->getItem($item))
+      ? $sigb_exemplaire
+      : $this->_findILSDIRecord($item)->getFirstILSItemLike($item);
+  }
 }
diff --git a/library/templates/Intonation/Library/Record/Items.php b/library/templates/Intonation/Library/Record/Items.php
index 3a8dbf48c15..11f212e8cd1 100644
--- a/library/templates/Intonation/Library/Record/Items.php
+++ b/library/templates/Intonation/Library/Record/Items.php
@@ -72,13 +72,15 @@ class Intonation_Library_Record_Items {
 
 
 
-  protected function _findItems() : array {
+  protected function _findItems(): array
+  {
     if (empty($this->_records))
       return [];
 
     $session = Zend_Registry::get('session');
 
-    $items = new Storm_Model_Collection();
+    $items = new Storm_Model_Collection;
+
     foreach($this->_records as $record)
       $items->addAll($record->getExemplaires());
 
diff --git a/tests/fixtures/MAE_GetRecords273230.xml b/tests/fixtures/MAE_GetRecords273230.xml
new file mode 100644
index 00000000000..c2d2bbdfa90
--- /dev/null
+++ b/tests/fixtures/MAE_GetRecords273230.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<GetRecords>
+  <record>
+    <timestamp>2019-07-30 13:48:26</timestamp>
+    <biblionumber>123415</biblionumber>
+    <pages>9 pièces</pages>
+    <volume>mélanges</volume>
+    <issues>
+    </issues>
+    <reserves>
+    </reserves>
+    <cn_sort></cn_sort>
+    <publishercode>Association française de science politique</publishercode>
+    <size>33 cm</size>
+    <biblioitemnumber>123415</biblioitemnumber>
+    <marcxml>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;record
+    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
+    xsi:schemaLocation=&quot;http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd&quot;
+    xmlns=&quot;http://www.loc.gov/MARC21/slim&quot;&gt;
+
+  &lt;leader&gt;00684nam a2200205   4500&lt;/leader&gt;
+  &lt;controlfield tag=&quot;001&quot;&gt;123415&lt;/controlfield&gt;
+  &lt;datafield tag=&quot;090&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;123415&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;099&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;t&quot;&gt;OUVRAGE&lt;/subfield&gt;
+    &lt;subfield code=&quot;c&quot;&gt;2019-06-28&lt;/subfield&gt;
+    &lt;subfield code=&quot;d&quot;&gt;2019-06-28&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;100&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;20190628d1961    u||y0frey50      ba&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;101&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;fre&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;102&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;FR&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;200&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;Association française de science politique&lt;/subfield&gt;
+    &lt;subfield code=&quot;e&quot;&gt;mélanges&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;210&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;Paris&lt;/subfield&gt;
+    &lt;subfield code=&quot;c&quot;&gt;Association française de science politique&lt;/subfield&gt;
+    &lt;subfield code=&quot;d&quot;&gt;1961&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;215&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;9 pièces&lt;/subfield&gt;
+    &lt;subfield code=&quot;d&quot;&gt;33 cm&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;311&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;Ouvrage dépouillé&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;503&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;Mélanges&lt;/subfield&gt;
+    &lt;subfield code=&quot;b&quot;&gt;Association française de science politique&lt;/subfield&gt;
+    &lt;subfield code=&quot;j&quot;&gt;1961&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;606&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;9&quot;&gt;187527&lt;/subfield&gt;
+    &lt;subfield code=&quot;a&quot;&gt;SCIENCES POLITIQUES&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;606&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;9&quot;&gt;186874&lt;/subfield&gt;
+    &lt;subfield code=&quot;a&quot;&gt;PARTIS POLITIQUES&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;710&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;9&quot;&gt;111125&lt;/subfield&gt;
+    &lt;subfield code=&quot;a&quot;&gt;ASSOCIATION FRANCAISE DE SCIENCE POLITIQUE&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;801&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;a&quot;&gt;FR&lt;/subfield&gt;
+    &lt;subfield code=&quot;b&quot;&gt;MAEDI&lt;/subfield&gt;
+    &lt;subfield code=&quot;g&quot;&gt;AFNOR&lt;/subfield&gt;
+  &lt;/datafield&gt;
+  &lt;datafield tag=&quot;995&quot; ind1=&quot; &quot; ind2=&quot; &quot;&gt;
+    &lt;subfield code=&quot;1&quot;&gt;0&lt;/subfield&gt;
+    &lt;subfield code=&quot;2&quot;&gt;0&lt;/subfield&gt;
+    &lt;subfield code=&quot;3&quot;&gt;0&lt;/subfield&gt;
+    &lt;subfield code=&quot;5&quot;&gt;2019-06-28&lt;/subfield&gt;
+    &lt;subfield code=&quot;9&quot;&gt;340920&lt;/subfield&gt;
+    &lt;subfield code=&quot;b&quot;&gt;CADLC&lt;/subfield&gt;
+    &lt;subfield code=&quot;c&quot;&gt;CADLC&lt;/subfield&gt;
+    &lt;subfield code=&quot;e&quot;&gt;DEP104&lt;/subfield&gt;
+    &lt;subfield code=&quot;f&quot;&gt;435194&lt;/subfield&gt;
+    &lt;subfield code=&quot;i&quot;&gt;2019-06-28&lt;/subfield&gt;
+    &lt;subfield code=&quot;j&quot;&gt;435194&lt;/subfield&gt;
+    &lt;subfield code=&quot;k&quot;&gt;MB 1166&lt;/subfield&gt;
+    &lt;subfield code=&quot;o&quot;&gt;0&lt;/subfield&gt;
+    &lt;subfield code=&quot;r&quot;&gt;LIVRE&lt;/subfield&gt;
+    &lt;subfield code=&quot;s&quot;&gt;MB_1166_000000000000000&lt;/subfield&gt;
+    &lt;subfield code=&quot;u&quot;&gt;feuillets mobiles&lt;/subfield&gt;
+    &lt;subfield code=&quot;w&quot;&gt;0&lt;/subfield&gt;
+    &lt;subfield code=&quot;y&quot;&gt;2019-06-28&lt;/subfield&gt;
+    &lt;subfield code=&quot;G&quot;&gt;Baumont&lt;/subfield&gt;
+    &lt;subfield code=&quot;I&quot;&gt;3&lt;/subfield&gt;
+  &lt;/datafield&gt;
+&lt;/record&gt;
+</marcxml>
+    <publicationyear>1961</publicationyear>
+    <itemtype>OUVRAGE</itemtype>
+    <items>
+      <item>
+        <itemcallnumber>MB 1166</itemcallnumber>
+        <itemnotes>feuillets mobiles</itemnotes>
+        <stocknumber>435194</stocknumber>
+        <biblioitemnumber>123415</biblioitemnumber>
+        <itemnumber>340920</itemnumber>
+        <location_description>Dépôt A-104</location_description>
+        <holdingbranch>CADLC</holdingbranch>
+        <location>DEP104</location>
+        <homebranchname>La Courneuve</homebranchname>
+        <permanent_location>DEP104</permanent_location>
+        <itemlost>0</itemlost>
+        <replacementpricedate>2019-06-28</replacementpricedate>
+        <dateaccessioned>2019-06-28</dateaccessioned>
+        <biblionumber>123415</biblionumber>
+        <barcode>987988</barcode>
+        <damaged>0</damaged>
+        <timestamp>2021-02-12 20:05:51</timestamp>
+        <withdrawn>0</withdrawn>
+        <holdingbranchname>La Courneuve</holdingbranchname>
+        <issues>0</issues>
+        <notforloan>0</notforloan>
+        <itype_description>Livre</itype_description>
+        <homebranch>CADLC</homebranch>
+        <cn_sort>MB_1166_000000000000000</cn_sort>
+        <itype>LIVRE</itype>
+        <datelastseen>2019-06-28</datelastseen>
+      </item>
+    </items>
+  </record>
+</GetRecords>
diff --git a/tests/fixtures/item_340920.json b/tests/fixtures/item_340920.json
new file mode 100644
index 00000000000..aceeca6179d
--- /dev/null
+++ b/tests/fixtures/item_340920.json
@@ -0,0 +1,45 @@
+{
+  "acquisition_date": "2017-12-07",
+  "acquisition_source": null,
+  "biblio_id": 2251,
+  "call_number_sort": "COOP_32140_000000000000000",
+  "call_number_source": null,
+  "callnumber": "COOP32140",
+  "checked_out_date": null,
+  "checkouts_count": 0,
+  "coded_location_qualifier": null,
+  "collection_code": null,
+  "copy_number": null,
+  "damaged_date": null,
+  "damaged_status": 0,
+  "exclude_from_local_holds_priority": false,
+  "extended_subfields": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<collection\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\"\n  xmlns=\"http://www.loc.gov/MARC21/slim\">\n\n<record>\n  <leader>         a              </leader>\n  <datafield tag=\"999\" ind1=\" \" ind2=\" \">\n    <subfield code=\"B\"></subfield>\n    <subfield code=\"C\"></subfield>\n    <subfield code=\"D\"></subfield>\n    <subfield code=\"E\"></subfield>\n    <subfield code=\"F\"></subfield>\n    <subfield code=\"G\"></subfield>\n    <subfield code=\"H\"></subfield>\n    <subfield code=\"I\"></subfield>\n    <subfield code=\"J\"></subfield>\n    <subfield code=\"L\">1</subfield>\n    <subfield code=\"M\"></subfield>\n  </datafield>\n</record>\n\n</collection>",
+  "external_id": "416844",
+  "holding_library_id": "CADLC",
+  "holds_count": null,
+  "home_library_id": "CADLC",
+  "internal_notes": null,
+  "inventory_number": "416844",
+  "item_id": 340920,
+  "item_type_id": "LIVRE",
+  "last_checkout_date": null,
+  "last_seen_date": "2023-01-18",
+  "location": "DEP102",
+  "lost_date": null,
+  "lost_status": 0,
+  "materials_notes": null,
+  "new_status": null,
+  "not_for_loan_status": 0,
+  "permanent_location": "DEP102",
+  "public_notes": null,
+  "purchase_price": null,
+  "renewals_count": null,
+  "replacement_price": null,
+  "replacement_price_date": "2017-12-07",
+  "restricted_status": null,
+  "serial_issue_number": null,
+  "timestamp": "2023-01-18T11:01:48+01:00",
+  "uri": null,
+  "withdrawn": 0,
+  "withdrawn_date": null
+}
diff --git a/tests/fixtures/item_codification_340920.json b/tests/fixtures/item_codification_340920.json
new file mode 100644
index 00000000000..b6be6716078
--- /dev/null
+++ b/tests/fixtures/item_codification_340920.json
@@ -0,0 +1,46 @@
+{
+  "acquisition_date": "2017-12-07",
+  "acquisition_source": null,
+  "biblio_id": 2251,
+  "call_number_sort": "COOP_32140_000000000000000",
+  "call_number_source": null,
+  "callnumber": "COOP32140",
+  "checked_out_date": null,
+  "checkouts_count": 0,
+  "coded_location_qualifier": null,
+  "collection_code": null,
+  "copy_number": null,
+  "damaged_date": null,
+  "damaged_status": 0,
+  "exclude_from_local_holds_priority": false,
+  "extended_subfields": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<collection\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\"\n  xmlns=\"http://www.loc.gov/MARC21/slim\">\n\n<record>\n  <leader>         a              </leader>\n  <datafield tag=\"999\" ind1=\" \" ind2=\" \">\n    <subfield code=\"B\"></subfield>\n    <subfield code=\"C\"></subfield>\n    <subfield code=\"D\"></subfield>\n    <subfield code=\"E\"></subfield>\n    <subfield code=\"F\"></subfield>\n    <subfield code=\"G\"></subfield>\n    <subfield code=\"H\"></subfield>\n    <subfield code=\"I\"></subfield>\n    <subfield code=\"J\"></subfield>\n    <subfield code=\"L\">1</subfield>\n    <subfield code=\"M\"></subfield>\n  </datafield>\n</record>\n\n</collection>",
+  "external_id": "416844",
+  "holding_library_id": "CADLC",
+  "holds_count": null,
+  "home_library_id": "CADLC",
+  "internal_notes": null,
+  "inventory_number": "416844",
+  "item_id": 340920,
+  "item_type_id": "LIVRE",
+  "last_checkout_date": null,
+  "last_seen_date": "2023-01-18",
+  "location": "DEP102",
+  "lost_date": null,
+  "lost_status": 0,
+  "materials_notes": null,
+  "new_status": null,
+  "effective_not_for_loan_status": 4,
+  "not_for_loan_status": 4,
+  "permanent_location": "DEP102",
+  "public_notes": null,
+  "purchase_price": null,
+  "renewals_count": null,
+  "replacement_price": null,
+  "replacement_price_date": "2017-12-07",
+  "restricted_status": null,
+  "serial_issue_number": null,
+  "timestamp": "2023-01-18T11:01:48+01:00",
+  "uri": null,
+  "withdrawn": 0,
+  "withdrawn_date": null
+}
diff --git a/tests/fixtures/item_damaged_340920.json b/tests/fixtures/item_damaged_340920.json
new file mode 100644
index 00000000000..b69fee2545c
--- /dev/null
+++ b/tests/fixtures/item_damaged_340920.json
@@ -0,0 +1,45 @@
+{
+  "acquisition_date": "2017-12-07",
+  "acquisition_source": null,
+  "biblio_id": 2251,
+  "call_number_sort": "COOP_32140_000000000000000",
+  "call_number_source": null,
+  "callnumber": "COOP32140",
+  "checked_out_date": null,
+  "checkouts_count": 0,
+  "coded_location_qualifier": null,
+  "collection_code": null,
+  "copy_number": null,
+  "damaged_date": null,
+  "damaged_status": 1,
+  "exclude_from_local_holds_priority": false,
+  "extended_subfields": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<collection\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\"\n  xmlns=\"http://www.loc.gov/MARC21/slim\">\n\n<record>\n  <leader>         a              </leader>\n  <datafield tag=\"999\" ind1=\" \" ind2=\" \">\n    <subfield code=\"B\"></subfield>\n    <subfield code=\"C\"></subfield>\n    <subfield code=\"D\"></subfield>\n    <subfield code=\"E\"></subfield>\n    <subfield code=\"F\"></subfield>\n    <subfield code=\"G\"></subfield>\n    <subfield code=\"H\"></subfield>\n    <subfield code=\"I\"></subfield>\n    <subfield code=\"J\"></subfield>\n    <subfield code=\"L\">1</subfield>\n    <subfield code=\"M\"></subfield>\n  </datafield>\n</record>\n\n</collection>",
+  "external_id": "416844",
+  "holding_library_id": "CADLC",
+  "holds_count": null,
+  "home_library_id": "CADLC",
+  "internal_notes": null,
+  "inventory_number": "416844",
+  "item_id": 340920,
+  "item_type_id": "LIVRE",
+  "last_checkout_date": null,
+  "last_seen_date": "2023-01-18",
+  "location": "DEP102",
+  "lost_date": null,
+  "lost_status": 0,
+  "materials_notes": null,
+  "new_status": null,
+  "not_for_loan_status": 0,
+  "permanent_location": "DEP102",
+  "public_notes": null,
+  "purchase_price": null,
+  "renewals_count": null,
+  "replacement_price": null,
+  "replacement_price_date": "2017-12-07",
+  "restricted_status": null,
+  "serial_issue_number": null,
+  "timestamp": "2023-01-18T11:01:48+01:00",
+  "uri": null,
+  "withdrawn": 0,
+  "withdrawn_date": null
+}
diff --git a/tests/fixtures/item_withdrawn_340920.json b/tests/fixtures/item_withdrawn_340920.json
new file mode 100644
index 00000000000..83e947529f5
--- /dev/null
+++ b/tests/fixtures/item_withdrawn_340920.json
@@ -0,0 +1,46 @@
+{
+  "acquisition_date": "2017-12-07",
+  "acquisition_source": null,
+  "biblio_id": 2251,
+  "call_number_sort": "COOP_32140_000000000000000",
+  "call_number_source": null,
+  "callnumber": "COOP32140",
+  "checked_out_date": null,
+  "checkouts_count": 0,
+  "coded_location_qualifier": null,
+  "collection_code": null,
+  "copy_number": null,
+  "damaged_date": null,
+  "damaged_status": 0,
+  "exclude_from_local_holds_priority": false,
+  "extended_subfields": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<collection\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\"\n  xmlns=\"http://www.loc.gov/MARC21/slim\">\n\n<record>\n  <leader>         a              </leader>\n  <datafield tag=\"999\" ind1=\" \" ind2=\" \">\n    <subfield code=\"B\"></subfield>\n    <subfield code=\"C\"></subfield>\n    <subfield code=\"D\"></subfield>\n    <subfield code=\"E\"></subfield>\n    <subfield code=\"F\"></subfield>\n    <subfield code=\"G\"></subfield>\n    <subfield code=\"H\"></subfield>\n    <subfield code=\"I\"></subfield>\n    <subfield code=\"J\"></subfield>\n    <subfield code=\"L\">1</subfield>\n    <subfield code=\"M\"></subfield>\n  </datafield>\n</record>\n\n</collection>",
+  "external_id": "416844",
+  "holding_library_id": "CADLC",
+  "holds_count": null,
+  "home_library_id": "CADLC",
+  "internal_notes": null,
+  "inventory_number": "416844",
+  "item_id": 340920,
+  "item_type_id": "LIVRE",
+  "last_checkout_date": null,
+  "last_seen_date": "2023-01-18",
+  "location": "DEP102",
+  "lost_date": null,
+  "lost_status": 0,
+  "materials_notes": null,
+  "new_status": null,
+  "effective_not_for_loan_status": 0,
+  "not_for_loan_status": 0,
+  "permanent_location": "DEP102",
+  "public_notes": null,
+  "purchase_price": null,
+  "renewals_count": null,
+  "replacement_price": null,
+  "replacement_price_date": "2017-12-07",
+  "restricted_status": null,
+  "serial_issue_number": null,
+  "timestamp": "2023-01-18T11:01:48+01:00",
+  "uri": null,
+  "withdrawn": 12,
+  "withdrawn_date": null
+}
diff --git a/tests/library/Class/HttpClientForTest.php b/tests/library/Class/HttpClientForTest.php
index defa76cab25..f82e379284d 100644
--- a/tests/library/Class/HttpClientForTest.php
+++ b/tests/library/Class/HttpClientForTest.php
@@ -80,7 +80,7 @@ class HttpClientForTest extends Zend_Http_Client
          ->answers(new Zend_Http_Response(200, [], $response));
 
     $this->_expected_calls [$request] = $this->_expected_calls_eated [$request] = ['RESPONSE' => new Zend_Http_Response(200, [], $response),
-                                                                                   'HEADERS' =>  $headers ?  $headers[0].': '.$headers[1] : []];
+                                                                                   'HEADERS' =>  $headers ];
     return $this;
   }
 
@@ -145,7 +145,8 @@ class HttpClientForTest extends Zend_Http_Client
       foreach ($slice as  $param)
       {
         $count ++;
-        if ($param == $mock_params[$count])
+
+        if (!isset($mock_params[ $count]) || $param == $mock_params[$count])
           continue;
 
         if ( !is_array( $param) )
diff --git a/tests/library/Class/WebService/SIGB/KohaItemResponseTest.php b/tests/library/Class/WebService/SIGB/KohaItemResponseTest.php
new file mode 100644
index 00000000000..1d6dd4b0617
--- /dev/null
+++ b/tests/library/Class/WebService/SIGB/KohaItemResponseTest.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * Copyright (c) 2012-2023, 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 'tests/fixtures/KohaFixtures.php';
+
+abstract class KohaItemResponseTestCase extends AbstractControllerTestCase
+{
+
+  protected $_web_client;
+
+  protected Class_WebService_SIGB_Koha_Exemplaire $_item;
+
+  public function setUp()
+  {
+    parent::setUp();
+
+    $this->_buildTemplateProfil(['id' => 8]);
+    $comm_params = $this->_commParams();
+    ksort($comm_params);
+
+    $profil = Class_IntProfilDonnees::forKoha();
+    $profil->save();
+
+    $intbib = $this->fixture(Class_IntBib::class,
+                             ['id' => 77,
+                              'comm_sigb' => Class_IntBib::COM_KOHA,
+                              'comm_params' => $comm_params]);
+
+    $this->fixture(Class_Bib::class,
+                   ['id' => 77,
+                    'libelle' => 'Mazargues',
+                    'int_bib' => $intbib]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 23,
+                    'titre' => 'Association française de science politique',
+                    'type_doc' => Class_TypeDoc::LIVRE]);
+
+    $opac_item = $this->fixture(Class_Exemplaire::class,
+                                ['id' => 231,
+                                 'code_barres' => '9879882314',
+                                 'id_origine' => 2251,
+                                 'id_notice' => 23,
+                                 'int_bib' => $intbib,
+                                 'id_bib' => 77,
+                                 'id_data_profile' => $profil->getId(),
+                                 'zone_995' => serialize([['code'=>'9', 'valeur' =>'340920']])]);
+
+    $this->_web_client = $this
+      ->mock()
+
+      ->whenCalled('open_url')
+      ->with('http://catalog.meae.fr/items/340920',
+             ['auth' => ['user' => 'koha', 'password' => 'pass']])
+      ->answers(file_get_contents('tests/fixtures/'.$this->_itemJsonFile()))
+      ->beStrict();
+
+    Class_WebService_SIGB_Koha_Service::shouldThrowError(true);
+    $service = Class_WebService_SIGB_Koha::getService($comm_params)
+      ->setWebClient($this->_web_client);
+
+    $intbib->getSIGBComm()->setWebClient($this->_web_client);
+
+    $this->_item = $service->getItem($opac_item);
+  }
+
+
+  protected function _commParams()
+  {
+    return ['url_serveur' => 'http://catalog.meae.fr',
+            'api_user' => 'koha',
+            'Interdire_reservation_doc_dispo' => 0,
+            'preregistration_category' => 0,
+            'loans_per_page' => 200,
+            'create_category_usergroup' => false,
+            'disable_items_availability_threshold' => 200,
+            'api_pass' => 'pass'];
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_340920.json';
+  }
+}
+
+
+
+
+class KohaItemResponseAvailableTest
+  extends KohaItemResponseTestCase {
+
+  public function ItemData() : array
+  {
+    return [[fn($item) => $item->getId(), 340920],
+            [fn($item) => $item->getNoNotice(), 2251],
+            [fn($item) => $item->getNbReservations(), 0],
+            [fn($item) => $item->getCote(), 'COOP32140'],
+            [fn($item) => $item->getDisponibilite(), 'Disponible'],
+            [fn($item) => $item->isReservable(), true]
+    ];
+  }
+
+
+  /** @test
+   *  @dataProvider ItemData
+   */
+  public function exemplaireDataShouldBeConformToDataInjected($closure, $expected)
+  {
+    $this->assertEquals($closure($this->_item), $expected);
+  }
+}
+
+
+
+
+class KohaItemResponseCodificationDisponibilitesTest
+  extends KohaItemResponseTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+
+    $this->dispatch('/opac/noticeajax/resources/id/23');
+  }
+
+
+  protected function _commParams() : array
+  {
+    $comm_params = parent::_commParams();
+    $comm_params['Codification_disponibilites']=
+     "1:Disponible\n4:En transit";
+    return $comm_params;
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_codification_340920.json';
+  }
+
+
+  /** @test */
+  public function exemplaireDisponibiliteShouldBeEntransit()
+  {
+    $this->assertXPathContentContains('//span[contains(@class, "item_availability")]', 'En transit');
+  }
+}
+
+
+
+
+class KohaItemResponseWithdrawnTest
+  extends KohaItemResponseTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+
+    $this->dispatch('/opac/noticeajax/resources/id/23');
+  }
+
+
+  protected function _commParams() : array
+  {
+    $comm_params = parent::_commParams();
+    $comm_params['withdrawn_mapping']= '12: En reliure';
+    return $comm_params;
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_withdrawn_340920.json';
+  }
+
+
+  /** @test */
+  public function exemplaireDisponibiliteShouldBeEnReliure()
+  {
+    $this->assertXPathContentContains('//span[contains(@class, "item_availability")]', 'En reliure');
+  }
+}
diff --git a/tests/scenarios/Journal/JournalTest.php b/tests/scenarios/Journal/JournalTest.php
index 4acbe90d37f..95002517632 100644
--- a/tests/scenarios/Journal/JournalTest.php
+++ b/tests/scenarios/Journal/JournalTest.php
@@ -177,7 +177,7 @@ class JournalIndexActionTest extends JournalWithEntriesTestCase {
 
 
   /** @test */
-  public function unknowUserForSecondEventShouldHaveLabelInconnuAModifié() {
+  public function unknowUserForSecondEventShouldHaveLabelInconnuAModifier() {
     $this->assertXPath('//td[contains(text(), "Inconnu a modifié l\'article")]');
   }
 }
diff --git a/tests/scenarios/Templates/TemplatesFakeItemsAvailabilityTest.php b/tests/scenarios/Templates/TemplatesFakeItemsAvailabilityTest.php
new file mode 100644
index 00000000000..778c7c23421
--- /dev/null
+++ b/tests/scenarios/Templates/TemplatesFakeItemsAvailabilityTest.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Copyright (c) 2012-2023, 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 TemplatesFakeItemsAvailabilityTestCase extends AbstractControllerTestCase
+{
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_HttpClientFactory::forTest();
+
+    $this->_buildTemplateProfil(['id' => 1]);
+
+    $comm_params = ['url_serveur' => 'http://catalog.meae.fr',
+                    'Interdire_reservation_doc_dispo' => 0,
+                    'disable_items_availability_threshold' => 300,
+                    'items_threshold' => 300,
+                    'Codification_disponibilites' => '',
+                    'restful' => '',
+                    'api_community' => '',
+                    'pre-registration' => '',
+                    'create_category_usergroup' => 0,
+                    'use_card_number' => '',
+                    'loans_per_page' => 20,
+                    'withdrawn_mapping' => '',
+                    'grouped_holds_itypes' => '',
+                    'bundled_holds_minimal_duration' => 0,
+                    'bundled_holds_maximal_duration' => 0];
+
+    $profil = Class_IntProfilDonnees::forKoha();
+    $profil->save();
+
+    $intbib =
+      $this->fixture(Class_IntBib::class,
+                     ['id' => 11,
+                      'id_bib' => 11,
+                      'label' => 'Meae',
+                      'sigb' => Class_IntBib::SIGB_KOHA,
+                      'comm_sigb' => Class_IntBib::COM_KOHA,
+                      'comm_params' => $comm_params]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 23,
+                    'titre' => 'Association française de science politique',
+                    'type_doc' => Class_TypeDoc::PERIODIQUE_ARTICLE]);
+
+    $this->fixture(Class_Exemplaire::class,
+                   ['id' => 231,
+                    'code_barres' => '987988-2314',
+                    'id_notice' => 23,
+                    'int_bib' => $intbib,
+                    'id_bib' => 11,
+                    'id_origine' => '340921',
+                    'id_data_profile' => $profil->getId(),]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 24,
+                    'id_sigb' => '',
+                    'type_doc' => Class_TypeDoc::PERIODIQUE]);
+
+    $this->fixture(Class_Exemplaire::class,
+                   ['id' => 241,
+                    'code_barres' => '987988',
+                    'id_notice' => 24,
+                    'id_int_bib' => 11,
+                    'int_bib' => $intbib,
+                    'ib_bib' => 1,
+                    'id_data_profile' => $profil->getId(),
+                    'id_origine' => '340920']);
+
+    $this->fixture(Class_Notice::class, ['id' => 25,
+                                         'titre' => 'Bonjour !',
+
+                                         'type_doc' => Class_TypeDoc::PERIODIQUE_ARTICLE]);
+
+    $this->fixture(Class_Exemplaire::class,
+                   ['id' => 251,
+                    'code_barres' => '',
+                    'id_notice' => 25,
+                    'id_int_bib' => 11,
+                    'int_bib' => $intbib,
+                    'ib_bib' => 1,
+                    'id_data_profile' => $profil->getId(),
+                    'zone_995' => serialize([['code'=>'9', 'valeur' =>'340920']]),
+                    'id_origine' => '340920']);
+
+
+    Class_HttpClientFactory::getInstance()
+      ->getLastHttpClient()
+      ->addRequestWithResponse('http://catalog.meae.fr:80?service=GetRecords&id=340920',
+                               file_get_contents(__DIR__.'/../../fixtures/MAE_GetRecords273230.xml'));
+
+  }
+
+
+  /** @test */
+  public function item231ShouldBeAvailable() {
+    $this->dispatch('/opac/noticeajax/availability/id/23');
+    $this->assertXPathContentContains('//span[contains(@class, "badge_record_availability")]',
+                                      'Disponible');
+  }
+
+
+  /** @test */
+  public function item251ShouldBeAvailable()
+  {
+    $this->dispatch('/opac/noticeajax/availability/id/25');
+    $this->assertXPathContentContains('//span[contains(@class, "badge_record_availability")]',
+                                      'Disponible', $this->_response->getBody());
+
+  }
+}
diff --git a/tests/scenarios/Templates/TemplatesKohaCommunityAvailabilityTest.php b/tests/scenarios/Templates/TemplatesKohaCommunityAvailabilityTest.php
new file mode 100644
index 00000000000..d9c6891f96e
--- /dev/null
+++ b/tests/scenarios/Templates/TemplatesKohaCommunityAvailabilityTest.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Copyright (c) 2012-2023, 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 'tests/fixtures/KohaFixtures.php';
+
+abstract class TemplatesKohaCommunityAvailabilityTestCase extends AbstractControllerTestCase {
+
+  protected $_http_client;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_buildTemplateProfil(['id' => 1]);
+
+    $comm_params = $this->_commParams();
+
+    $profil = Class_IntProfilDonnees::forKoha();
+    $profil->save();
+
+    $intbib = $this->fixture(Class_IntBib::class,
+                             ['id' => 77,
+                              'comm_sigb' => Class_IntBib::COM_KOHA,
+                              'comm_params' => $comm_params]);
+
+    $this->fixture(Class_Bib::class,
+                   ['id' => 77,
+                    'libelle' => 'Mazargues',
+                    'int_bib' => $intbib]);
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 23,
+                    'titre' => 'Association française de science politique',
+                    'type_doc' => Class_TypeDoc::LIVRE]);
+
+    $this->fixture(Class_Exemplaire::class,
+                   ['id' => 231,
+                    'code_barres' => '9879882314',
+                    'id_origine' => 2251,
+                    'id_notice' => 23,
+                    'int_bib' => $intbib,
+                    'id_bib' => 77,
+                    'id_data_profile' => $profil->getId(),
+                    'zone_995' => serialize([['code'=>'9', 'valeur' =>'340920']])]);
+    $this->_http_client = Class_HttpClientFactory::forTest();
+    $this->_manageRequests();
+  }
+
+
+  protected function _manageRequests()
+  {
+    $this->_http_client
+      ->addRequestWithResponse('http://catalog.meae.fr:80/items/340920',
+                               file_get_contents(__DIR__.'/../../fixtures/'.$this->_itemJsonFile()),
+                               [ 'Authorization' => 'Basic a29oYTpwYXNz']);
+  }
+
+
+  protected function _commParams()
+  {
+    return ['url_serveur' => 'http://catalog.meae.fr',
+            'api_user' => 'koha',
+            'Interdire_reservation_doc_dispo' => 0,
+            'preregistration_category' => 0,
+            'loans_per_page' => 200,
+            'create_category_usergroup' => false,
+            'disable_items_availability_threshold' => 200,
+            'api_pass' => 'pass'];
+  }
+
+  protected function _itemJsonFile()
+  {
+    return 'item_340920.json';
+  }
+
+
+  /** @test */
+  public function allHttpCallsShouldHaveBeenCalled() {
+    Class_HttpClientFactory::getInstance()
+      ->getLastHttpClient()
+      ->checkCalls($this);
+  }
+}
+
+
+
+
+class TemplatesKohaCommunityAvailabilityAvailableTest
+  extends TemplatesKohaCommunityAvailabilityTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->dispatch('/noticeajax/availability/id/23');
+  }
+
+
+  /** @test */
+  public function availabilityShouldBeDisponible()
+  {
+    $this->assertXPathContentContains('//span[contains(@class, "record_id_23")]/span',
+                                      'Disponible');
+  }
+}
+
+
+
+
+class TemplatesKohaCommunityAvailabilityDamagedTest
+  extends TemplatesKohaCommunityAvailabilityTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->dispatch('/noticeajax/resources/id/23');
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_damaged_340920.json';
+  }
+
+
+  /** @test */
+  public function availabilityShouldBeEndommag()
+  {
+    $this->assertXPathContentContains('//span[contains(@class,"item_availability")]',
+                                      'Endommag');
+  }
+
+
+  /** @test */
+  public function allHttpCallsShouldHaveBeenCalled() {
+    Class_HttpClientFactory::getInstance()
+      ->getLastHttpClient()
+      ->checkCalls($this);
+  }
+}
+
+
+
+
+class TemplatesKohaCommunityAvailabilityNotForLoanSpecifiedTest
+  extends TemplatesKohaCommunityAvailabilityTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->dispatch('/noticeajax/resources/id/23');
+  }
+
+
+  protected function _commParams() :array{
+    $comm_params = parent::_commParams();
+    $comm_params['Codification_disponibilites']=
+     "1:Disponible\n4:En transit";
+    return $comm_params;
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_codification_340920.json';
+  }
+
+
+  /** @test */
+  public function availabilityShouldBeEndommag()
+  {
+    $this->assertXPathContentContains('//span[contains(@class,"item_availability")]',
+                                      'En transit');
+  }
+}
+
+
+
+
+class TemplatesKohaCommunityAvailabilityBarcodeFallbackTest
+  extends TemplatesKohaCommunityAvailabilityTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->dispatch('/noticeajax/resources/id/23');
+  }
+
+
+  protected function _manageRequests()
+  {
+    $this->_http_client
+      ->addRequestWithResponse('http://catalog.meae.fr:80/items/340920',
+                               '{"error":"item not found"}',
+                               [ 'Authorization' => 'Basic a29oYTpwYXNz'])
+      ->addRequestWithResponse('http://catalog.meae.fr:80/items/?q=%7B%22barcode%22%3A%229879882314%22%7D',
+                               '['.file_get_contents('tests/fixtures/'.$this->_itemJsonFile()).']',
+                               [ 'Authorization' => 'Basic a29oYTpwYXNz']);
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_340920.json';
+  }
+
+
+  /** @test */
+  public function availabilityShouldBeDisponible()
+  {
+    $this->assertXPathContentContains('//body','Disponible');
+  }
+}
+
+
+
+
+class TemplatesKohaCommunityAvailabilityGetRecordsFallbackTest
+  extends TemplatesKohaCommunityAvailabilityTestCase {
+
+  public function setUp()
+  {
+    parent::setUp();
+    Class_Exemplaire::find(231)
+      ->setZone995( serialize([['code'=>'9', 'valeur' =>'1']]))
+      ->save();
+    $this->dispatch('/noticeajax/resources/id/23');
+  }
+
+
+  protected function _manageRequests()
+  {
+    $this->_http_client
+      ->addRequestWithResponse('http://catalog.meae.fr:80/items/1',
+                               '{"error":"item not found"}',
+                               [ 'Authorization' => 'Basic a29oYTpwYXNz'])
+      ->addRequestWithResponse('http://catalog.meae.fr:80/items/?q=%7B%22barcode%22%3A%229879882314%22%7D',
+                               '{"error":"item not found"}',
+                               [ 'Authorization' => 'Basic a29oYTpwYXNz'])
+      ->addRequestWithResponse('http://catalog.meae.fr:80?service=GetRecords&id=2251',
+                               KohaFixtures::xmlGetRecordOneJardinEnfanceWithItemOnTop(),
+                               [ 'Authorization' => 'Basic a29oYTpwYXNz']);
+  }
+
+
+  protected function _itemJsonFile()
+  {
+    return 'item_340920.json';
+  }
+
+
+  /** @test */
+  public function availabilityShouldBeDisponible()
+  {
+    $this->assertXPathCount('//div[contains(@class,"card_body card_body_Intonation_Library_View_Wrapper_Item card-body")]',1);
+  }
+}
-- 
GitLab