Bug 19919: Stop using paidfor altogether
[koha-equinox.git] / C4 / Items.pm
index 81a8263..e8251d2 100644 (file)
@@ -21,83 +21,65 @@ package C4::Items;
 use strict;
 #use warnings; FIXME - Bug 2505
 
-use Carp;
-use C4::Context;
-use C4::Koha;
-use C4::Biblio;
-use Koha::DateUtils;
-use MARC::Record;
-use C4::ClassSource;
-use C4::Log;
-use List::MoreUtils qw/any/;
-use YAML qw/Load/;
-use DateTime::Format::MySQL;
-use Data::Dumper; # used as part of logging item record changes, not just for
-                  # debugging; so please don't remove this
-use Koha::DateUtils qw/dt_from_string/;
-use Koha::Database;
-
-use Koha::Biblioitems;
-use Koha::Items;
-use Koha::SearchEngine;
-use Koha::SearchEngine::Search;
-
 use vars qw(@ISA @EXPORT);
-
 BEGIN {
+    require Exporter;
+    @ISA = qw(Exporter);
 
-       require Exporter;
-    @ISA = qw( Exporter );
-
-    # function exports
     @EXPORT = qw(
-        GetItem
         AddItemFromMarc
         AddItem
         AddItemBatchFromMarc
         ModItemFromMarc
-    Item2Marc
+        Item2Marc
         ModItem
         ModDateLastSeen
         ModItemTransfer
         DelItem
-    
         CheckItemPreSave
-    
-        GetItemStatus
-        GetItemLocation
-        GetLostItems
         GetItemsForInventory
-        GetItemsCount
-        GetItemInfosOf
-        GetItemsByBiblioitemnumber
         GetItemsInfo
-       GetItemsLocationInfo
-       GetHostItemsInfo
-        GetItemnumbersForBiblio
-        get_itemnumbers_of
-       get_hostitemnumbers_of
-        GetItemnumberFromBarcode
-        GetBarcodeFromItemnumber
+        GetItemsLocationInfo
+        GetHostItemsInfo
+        get_hostitemnumbers_of
         GetHiddenItemnumbers
+        ItemSafeToDelete
         DelItemCheck
-    MoveItemFromBiblio
-    GetLatestAcquisitions
-
+        MoveItemFromBiblio
         CartToShelf
-        ShelfToCart
-
-       GetAnalyticsCount
-        GetItemHolds
-
+        GetAnalyticsCount
         SearchItemsByField
         SearchItems
-
         PrepareItemrecordDisplay
-
     );
 }
 
+use Carp;
+use Try::Tiny;
+use C4::Context;
+use C4::Koha;
+use C4::Biblio;
+use Koha::DateUtils;
+use MARC::Record;
+use C4::ClassSource;
+use C4::Log;
+use List::MoreUtils qw(any);
+use YAML qw(Load);
+use DateTime::Format::MySQL;
+use Data::Dumper; # used as part of logging item record changes, not just for
+                  # debugging; so please don't remove this
+
+use Koha::AuthorisedValues;
+use Koha::DateUtils qw(dt_from_string);
+use Koha::Database;
+
+use Koha::Biblioitems;
+use Koha::Items;
+use Koha::ItemTypes;
+use Koha::SearchEngine;
+use Koha::SearchEngine::Search;
+use Koha::Libraries;
+
 =head1 NAME
 
 C4::Items - item management functions
@@ -108,6 +90,7 @@ This module contains an API for manipulating item
 records in Koha, and is used by cataloguing, circulation,
 acquisitions, and serials management.
 
+# FIXME This POD is not up-to-date
 A Koha item record is stored in two places: the
 items table and embedded in a MARC tag in the XML
 version of the associated bib record in C<biblioitems.marcxml>.
@@ -116,12 +99,6 @@ indexed (e.g., by Zebra), but means that each item
 modification transaction must keep the items table
 and the MARC XML in sync at all times.
 
-Consequently, all code that creates, modifies, or deletes
-item records B<must> use an appropriate function from 
-C<C4::Items>.  If no existing function is suitable, it is
-better to add one to C<C4::Items> than to use add
-one-off SQL statements to add or modify items.
-
 The items table will be considered authoritative.  In other
 words, if there is ever a discrepancy between the items
 table and the MARC XML, the items table should be considered
@@ -139,54 +116,6 @@ of C<C4::Items>
 
 =cut
 
-=head2 GetItem
-
-  $item = GetItem($itemnumber,$barcode,$serial);
-
-Return item information, for a given itemnumber or barcode.
-The return value is a hashref mapping item column
-names to values.  If C<$serial> is true, include serial publication data.
-
-=cut
-
-sub GetItem {
-    my ($itemnumber,$barcode, $serial) = @_;
-    my $dbh = C4::Context->dbh;
-       my $data;
-
-    if ($itemnumber) {
-        my $sth = $dbh->prepare("
-            SELECT * FROM items 
-            WHERE itemnumber = ?");
-        $sth->execute($itemnumber);
-        $data = $sth->fetchrow_hashref;
-    } else {
-        my $sth = $dbh->prepare("
-            SELECT * FROM items 
-            WHERE barcode = ?"
-            );
-        $sth->execute($barcode);               
-        $data = $sth->fetchrow_hashref;
-    }
-
-    return unless ( $data );
-
-    if ( $serial) {      
-    my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
-        $ssth->execute($data->{'itemnumber'}) ;
-        ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
-    }
-       #if we don't have an items.itype, use biblioitems.itemtype.
-    # FIXME this should respect the itypes systempreference
-    # if (C4::Context->preference('item-level_itypes')) {
-       if( ! $data->{'itype'} ) {
-               my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems  WHERE biblionumber = ?");
-               $sth->execute($data->{'biblionumber'});
-               ($data->{'itype'}) = $sth->fetchrow_array;
-       }
-    return $data;
-}    # sub GetItem
-
 =head2 CartToShelf
 
   CartToShelf($itemnumber);
@@ -206,32 +135,10 @@ sub CartToShelf {
         croak "FAILED CartToShelf() - no itemnumber supplied";
     }
 
-    my $item = GetItem($itemnumber);
-    if ( $item->{location} eq 'CART' ) {
-        $item->{location} = $item->{permanent_location};
-        ModItem($item, undef, $itemnumber);
-    }
-}
-
-=head2 ShelfToCart
-
-  ShelfToCart($itemnumber);
-
-Set the current shelving location of the item
-to shelving cart ('CART').
-
-=cut
-
-sub ShelfToCart {
-    my ( $itemnumber ) = @_;
-
-    unless ( $itemnumber ) {
-        croak "FAILED ShelfToCart() - no itemnumber supplied";
+    my $item = Koha::Items->find($itemnumber);
+    if ( $item->location eq 'CART' ) {
+        ModItem({ location => $item->permanent_location}, undef, $itemnumber);
     }
-
-    my $item = GetItem($itemnumber);
-    $item->{'location'} = 'CART';
-    ModItem($item, undef, $itemnumber);
 }
 
 =head2 AddItemFromMarc
@@ -249,14 +156,14 @@ sub AddItemFromMarc {
     my $dbh = C4::Context->dbh;
 
     # parse item hash from MARC
-    my $frameworkcode = GetFrameworkCode( $biblionumber );
-       my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
-       
-       my $localitemmarc=MARC::Record->new;
-       $localitemmarc->append_fields($source_item_marc->field($itemtag));
-    my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
-    my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
-    return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
+    my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
+    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+
+    my $localitemmarc = MARC::Record->new;
+    $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
+    my $item = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
+    my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+    return AddItem( $item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields );
 }
 
 =head2 AddItem
@@ -281,40 +188,46 @@ the biblio items tag for display and indexing.
 =cut
 
 sub AddItem {
-    my $item = shift;
+    my $item         = shift;
     my $biblionumber = shift;
 
     my $dbh           = @_ ? shift : C4::Context->dbh;
-    my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
-    my $unlinked_item_subfields;  
+    my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
+    my $unlinked_item_subfields;
     if (@_) {
-        $unlinked_item_subfields = shift
-    };
+        $unlinked_item_subfields = shift;
+    }
 
     # needs old biblionumber and biblioitemnumber
     $item->{'biblionumber'} = $biblionumber;
     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
     $sth->execute( $item->{'biblionumber'} );
-    ($item->{'biblioitemnumber'}) = $sth->fetchrow;
+    ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
 
     _set_defaults_for_add($item);
     _set_derived_columns_for_add($item);
     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
+
     # FIXME - checks here
-    unless ( $item->{itype} ) {  # default to biblioitem.itemtype if no itype
+    unless ( $item->{itype} ) {    # default to biblioitem.itemtype if no itype
         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
         $itype_sth->execute( $item->{'biblionumber'} );
         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
     }
 
-       my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
+    my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
+    return if $error;
+
     $item->{'itemnumber'} = $itemnumber;
 
-    ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
-   
-    logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
-    
-    return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
+    C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
+
+    logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
+      if C4::Context->preference("CataloguingLog");
+
+    _after_item_action_hooks({ action => 'create', item_id => $itemnumber });
+
+    return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
 }
 
 =head2 AddItemBatchFromMarc
@@ -327,8 +240,8 @@ embedded item fields.  This routine is suitable for batch jobs.
 
 This API assumes that the bib record has already been
 saved to the C<biblio> and C<biblioitems> tables.  It does
-not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
-are populated, but it will do so via a call to ModBibiloMarc.
+not expect that C<biblio_metadata.metadata> is populated, but it
+will do so via a call to ModBibiloMarc.
 
 The goal of this API is to have a similar effect to using AddBiblio
 and AddItems in succession, but without inefficient repeated
@@ -374,7 +287,7 @@ sub AddItemBatchFromMarc {
     $record = $record->clone();
     # loop through the item tags and start creating items
     my @bad_item_fields = ();
-    my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
+    my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField("items.itemnumber",'');
     my $item_sequence_num = 0;
     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
         $item_sequence_num++;
@@ -451,10 +364,11 @@ Returns item record
 =cut
 
 sub _build_default_values_for_mod_marc {
-    my ($frameworkcode) = @_;
+    # Has no framework parameter anymore, since Default is authoritative
+    # for Koha to MARC mappings.
 
-    my $cache     = Koha::Cache->get_instance();
-    my $cache_key = "default_value_for_mod_marc-$frameworkcode";
+    my $cache     = Koha::Caches->get_instance();
+    my $cache_key = "default_value_for_mod_marc-";
     my $cached    = $cache->get_from_cache($cache_key);
     return $cached if $cached;
 
@@ -479,7 +393,6 @@ sub _build_default_values_for_mod_marc {
         materials                => undef,
         new_status               => undef,
         notforloan               => 0,
-        # paidfor => undef, # commented, see bug 12817
         price                    => undef,
         replacementprice         => undef,
         replacementpricedate     => undef,
@@ -493,10 +406,8 @@ sub _build_default_values_for_mod_marc {
     while ( my ( $field, $default_value ) = each %$default_values ) {
         my $kohafield = $field;
         $kohafield =~ s|^([^\.]+)$|items.$1|;
-        $default_values_for_mod_from_marc{$field} =
-          $default_value
-          if C4::Koha::IsKohaFieldLinked(
-            { kohafield => $kohafield, frameworkcode => $frameworkcode } );
+        $default_values_for_mod_from_marc{$field} = $default_value
+            if C4::Biblio::GetMarcFromKohaField( $kohafield );
     }
 
     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
@@ -508,37 +419,45 @@ sub ModItemFromMarc {
     my $biblionumber = shift;
     my $itemnumber = shift;
 
-    my $dbh           = C4::Context->dbh;
-    my $frameworkcode = GetFrameworkCode($biblionumber);
-    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+    my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
+    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
 
     my $localitemmarc = MARC::Record->new;
     $localitemmarc->append_fields( $item_marc->field($itemtag) );
-    my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
-    my $default_values = _build_default_values_for_mod_marc($frameworkcode);
+    my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
+    my $default_values = _build_default_values_for_mod_marc();
     foreach my $item_field ( keys %$default_values ) {
         $item->{$item_field} = $default_values->{$item_field}
           unless exists $item->{$item_field};
     }
     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
 
-    ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
+    ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
     return $item;
 }
 
 =head2 ModItem
 
-  ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
+ModItem(
+    { column => $newvalue },
+    $biblionumber,
+    $itemnumber,
+    {
+        [ unlinked_item_subfields => $unlinked_item_subfields, ]
+        [ log_action => 1, ]
+    }
+);
 
-Change one or more columns in an item record and update
-the MARC representation of the item.
+Change one or more columns in an item record.
 
 The first argument is a hashref mapping from item column
 names to the new values.  The second and third arguments
 are the biblionumber and itemnumber, respectively.
+The fourth, optional parameter (additional_params) may contain the keys
+unlinked_item_subfields and log_action.
 
-The fourth, optional parameter, C<$unlinked_item_subfields>, contains
-an arrayref containing subfields present in the original MARC
+C<$unlinked_item_subfields> contains an arrayref containing
+subfields present in the original MARC
 representation of the item (e.g., from the item editor) that are
 not mapped to C<items> columns directly but should instead
 be stored in C<items.more_subfields_xml> and included in 
@@ -549,37 +468,36 @@ the derived value of a column such as C<items.cn_sort>,
 this routine will perform the necessary calculation
 and set the value.
 
+If log_action is set to false, the action will not be logged.
+If log_action is true or undefined, the action will be logged.
+
 =cut
 
 sub ModItem {
-    my $item = shift;
-    my $biblionumber = shift;
-    my $itemnumber = shift;
+    my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
+    my $log_action = $additional_params->{log_action} // 1;
+    my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
+
+    return unless %$item;
+    $item->{'itemnumber'} = $itemnumber or return;
 
     # if $biblionumber is undefined, get it from the current item
     unless (defined $biblionumber) {
         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
     }
 
-    my $dbh           = @_ ? shift : C4::Context->dbh;
-    my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
-    
-    my $unlinked_item_subfields;  
-    if (@_) {
-        $unlinked_item_subfields = shift;
+    if ($unlinked_item_subfields) {
         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
     };
 
-    $item->{'itemnumber'} = $itemnumber or return;
-
-    my @fields = qw( itemlost withdrawn );
+    my @fields = qw( itemlost withdrawn damaged );
 
-    # Only call GetItem if we need to set an "on" date field
-    if ( $item->{itemlost} || $item->{withdrawn} ) {
-        my $pre_mod_item = GetItem( $item->{'itemnumber'} );
+    # Only retrieve the item if we need to set an "on" date field
+    if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
+        my $pre_mod_item = Koha::Items->find( $item->{'itemnumber'} );
         for my $field (@fields) {
             if (    defined( $item->{$field} )
-                and not $pre_mod_item->{$field}
+                and not $pre_mod_item->$field
                 and $item->{$field} )
             {
                 $item->{ $field . '_on' } =
@@ -613,7 +531,10 @@ sub ModItem {
     # item status is possible
     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
 
-    logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
+    _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
+
+    logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
+      if $log_action && C4::Context->preference("CataloguingLog");
 }
 
 =head2 ModItemTransfer
@@ -629,9 +550,12 @@ sub ModItemTransfer {
     my ( $itemnumber, $frombranch, $tobranch ) = @_;
 
     my $dbh = C4::Context->dbh;
+    my $item = Koha::Items->find( $itemnumber );
 
     # Remove the 'shelving cart' location status if it is being used.
-    CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
+    CartToShelf( $itemnumber ) if ( $item->location eq 'CART' && $item->permanent_location ne 'CART' );
+
+    $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
 
     #new entry in branchtransfers....
     my $sth = $dbh->prepare(
@@ -639,25 +563,31 @@ sub ModItemTransfer {
         VALUES (?, ?, NOW(), ?)");
     $sth->execute($itemnumber, $frombranch, $tobranch);
 
-    ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
+    ModItem({ holdingbranch => $tobranch }, undef, $itemnumber, { log_action => 0 });
     ModDateLastSeen($itemnumber);
     return;
 }
 
 =head2 ModDateLastSeen
 
-  ModDateLastSeen($itemnum);
+ModDateLastSeen( $itemnumber, $leave_item_lost );
 
 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
-C<$itemnum> is the item number
+C<$itemnumber> is the item number
+C<$leave_item_lost> determines if a lost item will be found or remain lost
 
 =cut
 
 sub ModDateLastSeen {
-    my ($itemnumber) = @_;
-    
+    my ( $itemnumber, $leave_item_lost ) = @_;
+
     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
-    ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
+
+    my $params;
+    $params->{datelastseen} = $today;
+    $params->{itemlost} = 0 unless $leave_item_lost;
+
+    ModItem( $params, undef, $itemnumber, { log_action => 0 } );
 }
 
 =head2 DelItem
@@ -675,7 +605,8 @@ sub DelItem {
     my $biblionumber = $params->{biblionumber};
 
     unless ($biblionumber) {
-        $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
+        my $item = Koha::Items->find( $itemnumber );
+        $biblionumber = $item ? $item->biblio->biblionumber : undef;
     }
 
     # If there is no biblionumber for the given itemnumber, there is nothing to delete
@@ -684,10 +615,10 @@ sub DelItem {
     # FIXME check the item has no current issues
     my $deleted = _koha_delete_item( $itemnumber );
 
-    # get the MARC record
-    my $record = GetMarcBiblio($biblionumber);
     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
 
+    _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
+
     #search item field code
     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
     return $deleted;
@@ -737,16 +668,15 @@ item that has a given branch code.
 
 sub CheckItemPreSave {
     my $item_ref = shift;
-    require C4::Branch;
 
     my %errors = ();
 
     # check for duplicate barcode
     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
-        my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
-        if ($existing_itemnumber) {
+        my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
+        if ($existing_item) {
             if (!exists $item_ref->{'itemnumber'}                       # new item
-                or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
+                or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
             }
         }
@@ -754,20 +684,16 @@ sub CheckItemPreSave {
 
     # check for valid home branch
     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
-        my $branch_name = C4::Branch::GetBranchName($item_ref->{'homebranch'});
-        unless (defined $branch_name) {
-            # relies on fact that branches.branchname is a non-NULL column,
-            # so GetBranchName returns undef only if branch does not exist
+        my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
+        unless (defined $home_library) {
             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
         }
     }
 
     # check for valid holding branch
     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
-        my $branch_name = C4::Branch::GetBranchName($item_ref->{'holdingbranch'});
-        unless (defined $branch_name) {
-            # relies on fact that branches.branchname is a non-NULL column,
-            # so GetBranchName returns undef only if branch does not exist
+        my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
+        unless (defined $holding_library) {
             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
         }
     }
@@ -782,254 +708,8 @@ The following functions provide various ways of
 getting an item record, a set of item records, or
 lists of authorized values for certain item fields.
 
-Some of the functions in this group are candidates
-for refactoring -- for example, some of the code
-in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
-has copy-and-paste work.
-
-=cut
-
-=head2 GetItemStatus
-
-  $itemstatushash = GetItemStatus($fwkcode);
-
-Returns a list of valid values for the
-C<items.notforloan> field.
-
-NOTE: does B<not> return an individual item's
-status.
-
-Can be MARC dependent.
-fwkcode is optional.
-But basically could be can be loan or not
-Create a status selector with the following code
-
-=head3 in PERL SCRIPT
-
- my $itemstatushash = getitemstatus;
- my @itemstatusloop;
- foreach my $thisstatus (keys %$itemstatushash) {
-     my %row =(value => $thisstatus,
-                 statusname => $itemstatushash->{$thisstatus}->{'statusname'},
-             );
-     push @itemstatusloop, \%row;
- }
- $template->param(statusloop=>\@itemstatusloop);
-
-=head3 in TEMPLATE
-
-<select name="statusloop" id="statusloop">
-    <option value="">Default</option>
-    [% FOREACH statusloo IN statusloop %]
-        [% IF ( statusloo.selected ) %]
-            <option value="[% statusloo.value %]" selected="selected">[% statusloo.statusname %]</option>
-        [% ELSE %]
-            <option value="[% statusloo.value %]">[% statusloo.statusname %]</option>
-        [% END %]
-    [% END %]
-</select>
-
-=cut
-
-sub GetItemStatus {
-
-    # returns a reference to a hash of references to status...
-    my ($fwk) = @_;
-    my %itemstatus;
-    my $dbh = C4::Context->dbh;
-    my $sth;
-    $fwk = '' unless ($fwk);
-    my ( $tag, $subfield ) =
-      GetMarcFromKohaField( "items.notforloan", $fwk );
-    if ( $tag and $subfield ) {
-        my $sth =
-          $dbh->prepare(
-            "SELECT authorised_value
-            FROM marc_subfield_structure
-            WHERE tagfield=?
-                AND tagsubfield=?
-                AND frameworkcode=?
-            "
-          );
-        $sth->execute( $tag, $subfield, $fwk );
-        if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
-            my $authvalsth =
-              $dbh->prepare(
-                "SELECT authorised_value,lib
-                FROM authorised_values 
-                WHERE category=? 
-                ORDER BY lib
-                "
-              );
-            $authvalsth->execute($authorisedvaluecat);
-            while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
-                $itemstatus{$authorisedvalue} = $lib;
-            }
-            return \%itemstatus;
-            exit 1;
-        }
-        else {
-
-            #No authvalue list
-            # build default
-        }
-    }
-
-    #No authvalue list
-    #build default
-    $itemstatus{"1"} = "Not For Loan";
-    return \%itemstatus;
-}
-
-=head2 GetItemLocation
-
-  $itemlochash = GetItemLocation($fwk);
-
-Returns a list of valid values for the
-C<items.location> field.
-
-NOTE: does B<not> return an individual item's
-location.
-
-where fwk stands for an optional framework code.
-Create a location selector with the following code
-
-=head3 in PERL SCRIPT
-
-  my $itemlochash = getitemlocation;
-  my @itemlocloop;
-  foreach my $thisloc (keys %$itemlochash) {
-      my $selected = 1 if $thisbranch eq $branch;
-      my %row =(locval => $thisloc,
-                  selected => $selected,
-                  locname => $itemlochash->{$thisloc},
-               );
-      push @itemlocloop, \%row;
-  }
-  $template->param(itemlocationloop => \@itemlocloop);
-
-=head3 in TEMPLATE
-
-  <select name="location">
-      <option value="">Default</option>
-  <!-- TMPL_LOOP name="itemlocationloop" -->
-      <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
-  <!-- /TMPL_LOOP -->
-  </select>
-
 =cut
 
-sub GetItemLocation {
-
-    # returns a reference to a hash of references to location...
-    my ($fwk) = @_;
-    my %itemlocation;
-    my $dbh = C4::Context->dbh;
-    my $sth;
-    $fwk = '' unless ($fwk);
-    my ( $tag, $subfield ) =
-      GetMarcFromKohaField( "items.location", $fwk );
-    if ( $tag and $subfield ) {
-        my $sth =
-          $dbh->prepare(
-            "SELECT authorised_value
-            FROM marc_subfield_structure 
-            WHERE tagfield=? 
-                AND tagsubfield=? 
-                AND frameworkcode=?"
-          );
-        $sth->execute( $tag, $subfield, $fwk );
-        if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
-            my $authvalsth =
-              $dbh->prepare(
-                "SELECT authorised_value,lib
-                FROM authorised_values
-                WHERE category=?
-                ORDER BY lib"
-              );
-            $authvalsth->execute($authorisedvaluecat);
-            while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
-                $itemlocation{$authorisedvalue} = $lib;
-            }
-            return \%itemlocation;
-            exit 1;
-        }
-        else {
-
-            #No authvalue list
-            # build default
-        }
-    }
-
-    #No authvalue list
-    #build default
-    $itemlocation{"1"} = "Not For Loan";
-    return \%itemlocation;
-}
-
-=head2 GetLostItems
-
-  $items = GetLostItems( $where );
-
-This function gets a list of lost items.
-
-=over 2
-
-=item input:
-
-C<$where> is a hashref. it containts a field of the items table as key
-and the value to match as value. For example:
-
-{ barcode    => 'abc123',
-  homebranch => 'CPL',    }
-
-=item return:
-
-C<$items> is a reference to an array full of hashrefs with columns
-from the "items" table as keys.
-
-=item usage in the perl script:
-
-  my $where = { barcode => '0001548' };
-  my $items = GetLostItems( $where );
-  $template->param( itemsloop => $items );
-
-=back
-
-=cut
-
-sub GetLostItems {
-    # Getting input args.
-    my $where   = shift;
-    my $dbh     = C4::Context->dbh;
-
-    my $query   = "
-        SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
-               itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
-        FROM   items
-            LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
-            LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
-            LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
-        WHERE
-               authorised_values.category = 'LOST'
-               AND itemlost IS NOT NULL
-               AND itemlost <> 0
-    ";
-    my @query_parameters;
-    foreach my $key (keys %$where) {
-        $query .= " AND $key LIKE ?";
-        push @query_parameters, "%$where->{$key}%";
-    }
-
-    my $sth = $dbh->prepare($query);
-    $sth->execute( @query_parameters );
-    my $items = [];
-    while ( my $row = $sth->fetchrow_hashref ){
-        push @$items, $row;
-    }
-    return $items;
-}
-
 =head2 GetItemsForInventory
 
 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
@@ -1044,7 +724,6 @@ sub GetLostItems {
   offset       => $offset,
   size         => $size,
   statushash   => $statushash,
-  interface    => $interface,
 } );
 
 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
@@ -1066,6 +745,7 @@ sub GetItemsForInventory {
     my ( $parameters ) = @_;
     my $minlocation  = $parameters->{'minlocation'}  // '';
     my $maxlocation  = $parameters->{'maxlocation'}  // '';
+    my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
     my $location     = $parameters->{'location'}     // '';
     my $itemtype     = $parameters->{'itemtype'}     // '';
     my $ignoreissued = $parameters->{'ignoreissued'} // '';
@@ -1075,15 +755,18 @@ sub GetItemsForInventory {
     my $offset       = $parameters->{'offset'}       // '';
     my $size         = $parameters->{'size'}         // '';
     my $statushash   = $parameters->{'statushash'}   // '';
-    my $interface    = $parameters->{'interface'}    // '';
+    my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
 
     my $dbh = C4::Context->dbh;
     my ( @bind_params, @where_strings );
 
+    my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
+    my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
+
     my $select_columns = q{
-        SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
+        SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
     };
-    my $select_count = q{SELECT COUNT(*)};
+    my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
     my $query = q{
         FROM items
         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
@@ -1099,13 +782,13 @@ sub GetItemsForInventory {
     }
 
     if ($minlocation) {
-        push @where_strings, 'itemcallnumber >= ?';
-        push @bind_params, $minlocation;
+        push @where_strings, 'items.cn_sort >= ?';
+        push @bind_params, $min_cnsort;
     }
 
     if ($maxlocation) {
-        push @where_strings, 'itemcallnumber <= ?';
-        push @bind_params, $maxlocation;
+        push @where_strings, 'items.cn_sort <= ?';
+        push @bind_params, $max_cnsort;
     }
 
     if ($datelastseen) {
@@ -1138,12 +821,17 @@ sub GetItemsForInventory {
         push @where_strings, 'issues.date_due IS NULL';
     }
 
+    if ( $ignore_waiting_holds ) {
+        $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
+        push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
+    }
+
     if ( @where_strings ) {
         $query .= 'WHERE ';
         $query .= join ' AND ', @where_strings;
     }
-    $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
     my $count_query = $select_count . $query;
+    $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
     $query .= " LIMIT $offset, $size" if ($offset and $size);
     $query = $select_columns . $query;
     my $sth = $dbh->prepare($query);
@@ -1155,9 +843,19 @@ sub GetItemsForInventory {
     $sth->execute( @bind_params );
     my ($iTotalRecords) = $sth->fetchrow_array();
 
-    my $avmapping = C4::Koha::GetKohaAuthorisedValuesMapping( {
-                      interface => $interface
-                    } );
+    my @avs = Koha::AuthorisedValues->search(
+        {   'marc_subfield_structures.kohafield' => { '>' => '' },
+            'me.authorised_value'                => { '>' => '' },
+        },
+        {   join     => { category => 'marc_subfield_structures' },
+            distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
+            '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
+            '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
+        }
+    );
+
+    my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
+
     foreach my $row (@$tmpresults) {
 
         # Auth values
@@ -1172,97 +870,6 @@ sub GetItemsForInventory {
     return (\@results, $iTotalRecords);
 }
 
-=head2 GetItemsCount
-
-  $count = &GetItemsCount( $biblionumber);
-
-This function return count of item with $biblionumber
-
-=cut
-
-sub GetItemsCount {
-    my ( $biblionumber ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $query = "SELECT count(*)
-          FROM  items 
-          WHERE biblionumber=?";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($biblionumber);
-    my $count = $sth->fetchrow;  
-    return ($count);
-}
-
-=head2 GetItemInfosOf
-
-  GetItemInfosOf(@itemnumbers);
-
-=cut
-
-sub GetItemInfosOf {
-    my @itemnumbers = @_;
-
-    my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
-
-    my $query = "
-        SELECT *
-        FROM items
-        WHERE itemnumber IN ($itemnumber_values)
-    ";
-    return get_infos_of( $query, 'itemnumber' );
-}
-
-=head2 GetItemsByBiblioitemnumber
-
-  GetItemsByBiblioitemnumber($biblioitemnumber);
-
-Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
-Called by C<C4::XISBN>
-
-=cut
-
-sub GetItemsByBiblioitemnumber {
-    my ( $bibitem ) = @_;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
-    # Get all items attached to a biblioitem
-    my $i = 0;
-    my @results; 
-    $sth->execute($bibitem) || die $sth->errstr;
-    while ( my $data = $sth->fetchrow_hashref ) {  
-        # Foreach item, get circulation information
-        my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
-                                   WHERE itemnumber = ?
-                                   AND issues.borrowernumber = borrowers.borrowernumber"
-        );
-        $sth2->execute( $data->{'itemnumber'} );
-        if ( my $data2 = $sth2->fetchrow_hashref ) {
-            # if item is out, set the due date and who it is out too
-            $data->{'date_due'}   = $data2->{'date_due'};
-            $data->{'cardnumber'} = $data2->{'cardnumber'};
-            $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
-        }
-        else {
-            # set date_due to blank, so in the template we check itemlost, and withdrawn
-            $data->{'date_due'} = '';                                                                                                         
-        }    # else         
-        # Find the last 3 people who borrowed this item.                  
-        my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
-                      AND old_issues.borrowernumber = borrowers.borrowernumber
-                      ORDER BY returndate desc,timestamp desc LIMIT 3";
-        $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
-        $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
-        my $i2 = 0;
-        while ( my $data2 = $sth2->fetchrow_hashref ) {
-            $data->{"timestamp$i2"} = $data2->{'timestamp'};
-            $data->{"card$i2"}      = $data2->{'cardnumber'};
-            $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
-            $i2++;
-        }
-        push(@results,$data);
-    } 
-    return (\@results); 
-}
-
 =head2 GetItemsInfo
 
   @results = GetItemsInfo($biblionumber);
@@ -1308,7 +915,6 @@ If this is set, it is set to C<One Order>.
 sub GetItemsInfo {
     my ( $biblionumber ) = @_;
     my $dbh   = C4::Context->dbh;
-    # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
     require C4::Languages;
     my $language = C4::Languages::getlanguage();
     my $query = "
@@ -1339,10 +945,12 @@ sub GetItemsInfo {
            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
            itemtypes.notforloan as notforloan_per_itemtype,
            holding.branchurl,
+           holding.branchcode,
            holding.branchname,
            holding.opac_info as holding_branch_opac_info,
            home.opac_info as home_branch_opac_info
     ";
+    $query .= ",IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold" if !C4::Context->preference('AllowItemsOnHoldCheckout');
     $query .= "
      FROM items
      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
@@ -1355,6 +963,8 @@ sub GetItemsInfo {
      LEFT JOIN serial USING (serialid)
      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
+    $query .= "
+    LEFT JOIN tmp_holdsqueue USING (itemnumber)" if !C4::Context->preference('AllowItemsOnHoldCheckout');
     $query .= q|
     LEFT JOIN localization ON itemtypes.itemtype = localization.code
         AND localization.entity = 'itemtypes'
@@ -1377,22 +987,20 @@ sub GetItemsInfo {
 
         $serial ||= $data->{'serial'};
 
+        my $descriptions;
         # get notforloan complete status if applicable
-        if ( my $code = C4::Koha::GetAuthValCode( 'items.notforloan', $data->{frameworkcode} ) ) {
-            $data->{notforloanvalue}     = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan} );
-            $data->{notforloanvalueopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan}, 1 );
-        }
+        $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
+        $data->{notforloanvalue}     = $descriptions->{lib} // '';
+        $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
 
         # get restricted status and description if applicable
-        if ( my $code = C4::Koha::GetAuthValCode( 'items.restricted', $data->{frameworkcode} ) ) {
-            $data->{restrictedopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted}, 1 );
-            $data->{restricted}     = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted} );
-        }
+        $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
+        $data->{restrictedvalue}     = $descriptions->{lib} // '';
+        $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
 
         # my stack procedures
-        if ( my $code = C4::Koha::GetAuthValCode( 'items.stack', $data->{frameworkcode} ) ) {
-            $data->{stack}          = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{stack} );
-        }
+        $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
+        $data->{stack}          = $descriptions->{lib} // '';
 
         # Find the last 3 people who borrowed this item.
         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
@@ -1476,8 +1084,10 @@ sub GetItemsLocationInfo {
         $sth->execute($biblionumber);
 
         while ( my $data = $sth->fetchrow_hashref ) {
-             $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
-             $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
+             my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
+             $av = $av->count ? $av->next : undef;
+             $data->{location_intranet} = $av ? $av->lib : '';
+             $data->{location_opac}     = $av ? $av->opac_description : '';
             push @results, $data;
        }
        return @results;
@@ -1485,149 +1095,39 @@ sub GetItemsLocationInfo {
 
 =head2 GetHostItemsInfo
 
-       $hostiteminfo = GetHostItemsInfo($hostfield);
-       Returns the iteminfo for items linked to records via a host field
+    $hostiteminfo = GetHostItemsInfo($hostfield);
+    Returns the iteminfo for items linked to records via a host field
 
 =cut
 
 sub GetHostItemsInfo {
-       my ($record) = @_;
-       my @returnitemsInfo;
-
-       if (C4::Context->preference('marcflavour') eq 'MARC21' ||
-        C4::Context->preference('marcflavour') eq 'NORMARC'){
-           foreach my $hostfield ( $record->field('773') ) {
-               my $hostbiblionumber = $hostfield->subfield("0");
-               my $linkeditemnumber = $hostfield->subfield("9");
-               my @hostitemInfos = GetItemsInfo($hostbiblionumber);
-               foreach my $hostitemInfo (@hostitemInfos){
-                       if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
-                               push (@returnitemsInfo,$hostitemInfo);
-                               last;
-                       }
-               }
-           }
-       } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
-           foreach my $hostfield ( $record->field('461') ) {
-               my $hostbiblionumber = $hostfield->subfield("0");
-               my $linkeditemnumber = $hostfield->subfield("9");
-               my @hostitemInfos = GetItemsInfo($hostbiblionumber);
-               foreach my $hostitemInfo (@hostitemInfos){
-                       if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
-                               push (@returnitemsInfo,$hostitemInfo);
-                               last;
-                       }
-               }
-           }
-       }
-       return @returnitemsInfo;
-}
-
-
-=head2 GetLastAcquisitions
-
-  my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
-                                    'itemtypes' => ('BK','BD')}, 10);
-
-=cut
-
-sub  GetLastAcquisitions {
-       my ($data,$max) = @_;
-
-       my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
-       
-       my $number_of_branches = @{$data->{branches}};
-       my $number_of_itemtypes   = @{$data->{itemtypes}};
-       
-       
-       my @where = ('WHERE 1 '); 
-       $number_of_branches and push @where
-          , 'AND holdingbranch IN (' 
-          , join(',', ('?') x $number_of_branches )
-          , ')'
-        ;
-       
-       $number_of_itemtypes and push @where
-          , "AND $itemtype IN (" 
-          , join(',', ('?') x $number_of_itemtypes )
-          , ')'
-        ;
-
-       my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
-                                FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
-                                   RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
-                                   @where
-                                   GROUP BY biblio.biblionumber 
-                                   ORDER BY dateaccessioned DESC LIMIT $max";
+    my ($record) = @_;
+    my @returnitemsInfo;
 
-       my $dbh = C4::Context->dbh;
-       my $sth = $dbh->prepare($query);
-    
-    $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
-       
-       my @results;
-       while( my $row = $sth->fetchrow_hashref){
-               push @results, {date => $row->{dateaccessioned} 
-                                               , biblionumber => $row->{biblionumber}
-                                               , title => $row->{title}};
-       }
-       
-       return @results;
-}
-
-=head2 GetItemnumbersForBiblio
-
-  my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
-
-Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
-
-=cut
-
-sub GetItemnumbersForBiblio {
-    my $biblionumber = shift;
-    my @items;
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
-    $sth->execute($biblionumber);
-    while (my $result = $sth->fetchrow_hashref) {
-        push @items, $result->{'itemnumber'};
+    if( !C4::Context->preference('EasyAnalyticalRecords') ) {
+        return @returnitemsInfo;
     }
-    return \@items;
-}
-
-=head2 get_itemnumbers_of
-
-  my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
-
-Given a list of biblionumbers, return the list of corresponding itemnumbers
-for each biblionumber.
-
-Return a reference on a hash where keys are biblionumbers and values are
-references on array of itemnumbers.
-
-=cut
-
-sub get_itemnumbers_of {
-    my @biblionumbers = @_;
-
-    my $dbh = C4::Context->dbh;
-
-    my $query = '
-        SELECT itemnumber,
-            biblionumber
-        FROM items
-        WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
-    ';
-    my $sth = $dbh->prepare($query);
-    $sth->execute(@biblionumbers);
 
-    my %itemnumbers_of;
-
-    while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
-        push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
+    my @fields;
+    if( C4::Context->preference('marcflavour') eq 'MARC21' ||
+      C4::Context->preference('marcflavour') eq 'NORMARC') {
+        @fields = $record->field('773');
+    } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
+        @fields = $record->field('461');
     }
 
-    return \%itemnumbers_of;
+    foreach my $hostfield ( @fields ) {
+        my $hostbiblionumber = $hostfield->subfield("0");
+        my $linkeditemnumber = $hostfield->subfield("9");
+        my @hostitemInfos = GetItemsInfo($hostbiblionumber);
+        foreach my $hostitemInfo (@hostitemInfos) {
+            if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
+                push @returnitemsInfo, $hostitemInfo;
+                last;
+            }
+        }
+    }
+    return @returnitemsInfo;
 }
 
 =head2 get_hostitemnumbers_of
@@ -1643,86 +1143,66 @@ references on array of itemnumbers.
 
 
 sub get_hostitemnumbers_of {
-       my ($biblionumber) = @_;
-       my $marcrecord = GetMarcBiblio($biblionumber);
-        my (@returnhostitemnumbers,$tag, $biblio_s, $item_s);
-       
-       my $marcflavor = C4::Context->preference('marcflavour');
-       if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') {
-        $tag='773';
-        $biblio_s='0';
-        $item_s='9';
-    } elsif ($marcflavor eq 'UNIMARC') {
-        $tag='461';
-        $biblio_s='0';
-        $item_s='9';
+    my ($biblionumber) = @_;
+
+    if( !C4::Context->preference('EasyAnalyticalRecords') ) {
+        return ();
+    }
+
+    my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
+    return unless $marcrecord;
+
+    my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
+
+    my $marcflavor = C4::Context->preference('marcflavour');
+    if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
+        $tag      = '773';
+        $biblio_s = '0';
+        $item_s   = '9';
+    }
+    elsif ( $marcflavor eq 'UNIMARC' ) {
+        $tag      = '461';
+        $biblio_s = '0';
+        $item_s   = '9';
     }
 
     foreach my $hostfield ( $marcrecord->field($tag) ) {
         my $hostbiblionumber = $hostfield->subfield($biblio_s);
+        next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
         my $linkeditemnumber = $hostfield->subfield($item_s);
-        my @itemnumbers;
-        if (my $itemnumbers = get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber})
-        {
-            @itemnumbers = @$itemnumbers;
-        }
-        foreach my $itemnumber (@itemnumbers){
-            if ($itemnumber eq $linkeditemnumber){
-                push (@returnhostitemnumbers,$itemnumber);
-                last;
-            }
+        if ( ! $linkeditemnumber ) {
+            warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
+            next;
         }
+        my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
+        push @returnhostitemnumbers, $linkeditemnumber
+          if $is_from_biblio;
     }
-    return @returnhostitemnumbers;
-}
-
-
-=head2 GetItemnumberFromBarcode
-
-  $result = GetItemnumberFromBarcode($barcode);
-
-=cut
-
-sub GetItemnumberFromBarcode {
-    my ($barcode) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $rq =
-      $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
-    $rq->execute($barcode);
-    my ($result) = $rq->fetchrow;
-    return ($result);
-}
-
-=head2 GetBarcodeFromItemnumber
-
-  $result = GetBarcodeFromItemnumber($itemnumber);
 
-=cut
-
-sub GetBarcodeFromItemnumber {
-    my ($itemnumber) = @_;
-    my $dbh = C4::Context->dbh;
-
-    my $rq =
-      $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
-    $rq->execute($itemnumber);
-    my ($result) = $rq->fetchrow;
-    return ($result);
+    return @returnhostitemnumbers;
 }
 
 =head2 GetHiddenItemnumbers
 
-    my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
+    my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
 
 Given a list of items it checks which should be hidden from the OPAC given
 the current configuration. Returns a list of itemnumbers corresponding to
-those that should be hidden.
+those that should be hidden. Optionally takes a borcat parameter for certain borrower types
+to be excluded
 
 =cut
 
 sub GetHiddenItemnumbers {
-    my (@items) = @_;
+    my $params = shift;
+    my $items = $params->{items};
+    if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
+        foreach my $except (split(/\|/, $exceptions)){
+            if ($params->{'borcat'} eq $except){
+                return; # we don't hide anything for this borrower category
+            }
+        }
+    }
     my @resultitems;
 
     my $yaml = C4::Context->preference('OpacHiddenItems');
@@ -1739,7 +1219,7 @@ sub GetHiddenItemnumbers {
     my $dbh = C4::Context->dbh;
 
     # For each item
-    foreach my $item (@items) {
+    foreach my $item (@$items) {
 
         # We check each rule
         foreach my $field (keys %$hidingrules) {
@@ -1803,13 +1283,12 @@ sub GetMarcItem {
     # while the other treats the MARC representation as authoritative
     # under certain circumstances.
 
-    my $itemrecord = GetItem($itemnumber);
+    my $item = Koha::Items->find($itemnumber) or return;
 
-    # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
+    # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
     # Also, don't emit a subfield if the underlying field is blank.
 
-    
-    return Item2Marc($itemrecord,$biblionumber);
+    return Item2Marc($item->unblessed, $biblionumber);
 
 }
 sub Item2Marc {
@@ -1819,8 +1298,11 @@ sub Item2Marc {
             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
         } keys %{ $itemrecord } 
     };
-    my $itemmarc = TransformKohaToMarc($mungeditem);
-    my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
+    my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
+    my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
+    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
+        "items.itemnumber", $framework,
+    );
 
     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
@@ -2068,6 +1550,7 @@ sub _koha_new_item {
     my $dbh=C4::Context->dbh;  
     my $error;
     $item->{permanent_location} //= $item->{location};
+    _mod_item_dates( $item );
     my $query =
            "INSERT INTO items SET
             biblionumber        = ?,
@@ -2092,7 +1575,6 @@ sub _koha_new_item {
             itemnotes           = ?,
             itemnotes_nonpublic = ?,
             holdingbranch       = ?,
-            paidfor             = ?,
             location            = ?,
             permanent_location  = ?,
             onloan              = ?,
@@ -2136,7 +1618,6 @@ sub _koha_new_item {
             $item->{'itemnotes'},
             $item->{'itemnotes_nonpublic'},
             $item->{'holdingbranch'},
-            $item->{'paidfor'},
             $item->{'location'},
             $item->{'permanent_location'},
             $item->{'onloan'},
@@ -2217,68 +1698,92 @@ sub MoveItemFromBiblio {
     return;
 }
 
-=head2 DelItemCheck
+=head2 ItemSafeToDelete
 
-   DelItemCheck($dbh, $biblionumber, $itemnumber);
+   ItemSafeToDelete( $biblionumber, $itemnumber);
 
-Exported function (core API) for deleting an item record in Koha if there no current issue.
+Exported function (core API) for checking whether an item record is safe to delete.
 
-=cut
+returns 1 if the item is safe to delete,
 
-sub DelItemCheck {
-    my ( $dbh, $biblionumber, $itemnumber ) = @_;
+"book_on_loan" if the item is checked out,
 
-    $dbh ||= C4::Context->dbh;
+"not_same_branch" if the item is blocked by independent branches,
 
-    my $error;
+"book_reserved" if the there are holds aganst the item, or
+
+"linked_analytics" if the item has linked analytic records.
 
-        my $countanalytics=GetAnalyticsCount($itemnumber);
+=cut
 
+sub ItemSafeToDelete {
+    my ( $biblionumber, $itemnumber ) = @_;
+    my $status;
+    my $dbh = C4::Context->dbh;
 
-    # check that there is no issue on this item before deletion.
-    my $sth = $dbh->prepare(q{
-        SELECT COUNT(*) FROM issues
-        WHERE itemnumber = ?
-    });
-    $sth->execute($itemnumber);
-    my ($onloan) = $sth->fetchrow;
+    my $error;
 
-    my $item = GetItem($itemnumber);
+    my $countanalytics = GetAnalyticsCount($itemnumber);
 
-    if ($onloan){
-        $error = "book_on_loan" 
+    my $item = Koha::Items->find($itemnumber) or return;
+
+    if ($item->checkout) {
+        $status = "book_on_loan";
     }
     elsif ( defined C4::Context->userenv
         and !C4::Context->IsSuperLibrarian()
         and C4::Context->preference("IndependentBranches")
-        and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
+        and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
     {
-        $error = "not_same_branch";
+        $status = "not_same_branch";
     }
-       else{
+    else {
         # check it doesn't have a waiting reserve
-        $sth = $dbh->prepare(q{
+        my $sth = $dbh->prepare(
+            q{
             SELECT COUNT(*) FROM reserves
             WHERE (found = 'W' OR found = 'T')
             AND itemnumber = ?
-        });
+        }
+        );
         $sth->execute($itemnumber);
         my ($reserve) = $sth->fetchrow;
-        if ($reserve){
-            $error = "book_reserved";
-        } elsif ($countanalytics > 0){
-               $error = "linked_analytics";
-       } else {
-            DelItem(
-                {
-                    biblionumber => $biblionumber,
-                    itemnumber   => $itemnumber
-                }
-            );
-            return 1;
+        if ($reserve) {
+            $status = "book_reserved";
+        }
+        elsif ( $countanalytics > 0 ) {
+            $status = "linked_analytics";
+        }
+        else {
+            $status = 1;
         }
     }
-    return $error;
+    return $status;
+}
+
+=head2 DelItemCheck
+
+   DelItemCheck( $biblionumber, $itemnumber);
+
+Exported function (core API) for deleting an item record in Koha if there no current issue.
+
+DelItemCheck wraps ItemSafeToDelete around DelItem.
+
+=cut
+
+sub DelItemCheck {
+    my ( $biblionumber, $itemnumber ) = @_;
+    my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
+
+    if ( $status == 1 ) {
+        DelItem(
+            {
+                biblionumber => $biblionumber,
+                itemnumber   => $itemnumber
+            }
+        );
+    }
+    return $status;
 }
 
 =head2 _koha_modify_item
@@ -2297,6 +1802,7 @@ sub _koha_modify_item {
 
     my $query = "UPDATE items SET ";
     my @bind;
+    _mod_item_dates( $item );
     for my $key ( keys %$item ) {
         next if ( $key eq 'itemnumber' );
         $query.="$key=?,";
@@ -2314,6 +1820,34 @@ sub _koha_modify_item {
     return ($item->{'itemnumber'},$error);
 }
 
+sub _mod_item_dates { # date formatting for date fields in item hash
+    my ( $item ) = @_;
+    return if !$item || ref($item) ne 'HASH';
+
+    my @keys = grep
+        { $_ =~ /^onloan$|^date|date$|datetime$/ }
+        keys %$item;
+    # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
+    # NOTE: We do not (yet) have items fields ending with datetime
+    # Fields with _on$ have been handled already
+
+    foreach my $key ( @keys ) {
+        next if !defined $item->{$key}; # skip undefs
+        my $dt = eval { dt_from_string( $item->{$key} ) };
+            # eval: dt_from_string will die on us if we pass illegal dates
+
+        my $newstr;
+        if( defined $dt  && ref($dt) eq 'DateTime' ) {
+            if( $key =~ /datetime/ ) {
+                $newstr = DateTime::Format::MySQL->format_datetime($dt);
+            } else {
+                $newstr = DateTime::Format::MySQL->format_date($dt);
+            }
+        }
+        $item->{$key} = $newstr; # might be undef to clear garbage
+    }
+}
+
 =head2 _koha_delete_item
 
   _koha_delete_item( $itemnum );
@@ -2382,7 +1916,7 @@ sub _marc_from_item_hash {
 
     my $item_marc = MARC::Record->new();
     foreach my $item_field ( keys %{$mungeditem} ) {
-        my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
+        my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode );
         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
         foreach my $value (@values){
@@ -2437,7 +1971,7 @@ sub _get_unlinked_item_subfields {
     my $original_item_marc = shift;
     my $frameworkcode = shift;
 
-    my $marcstructure = GetMarcStructure(1, $frameworkcode);
+    my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
 
     # assume that this record has only one field, and that that
     # field contains only the item information
@@ -2519,27 +2053,6 @@ sub GetAnalyticsCount {
     return ($result);
 }
 
-=head2 GetItemHolds
-
-  $holds = &GetItemHolds($biblionumber, $itemnumber);
-
-This function return the count of holds with $biblionumber and $itemnumber
-
-=cut
-
-sub GetItemHolds {
-    my ($biblionumber, $itemnumber) = @_;
-    my $holds;
-    my $dbh            = C4::Context->dbh;
-    my $query          = "SELECT count(*)
-        FROM  reserves
-        WHERE biblionumber=? AND itemnumber=?";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($biblionumber, $itemnumber);
-    $holds = $sth->fetchrow;
-    return $holds;
-}
-
 =head2 SearchItemsByField
 
     my $items = SearchItemsByField($field, $value);
@@ -2613,16 +2126,16 @@ sub _SearchItems_build_where_fragment {
                     $column = $kohafield;
                 } else {
                     # MARC field is not linked to a DB field so we need to use
-                    # ExtractValue on biblioitems.marcxml or
+                    # ExtractValue on marcxml from biblio_metadata or
                     # items.more_subfields_xml, depending on the MARC field.
                     my $xpath;
                     my $sqlfield;
-                    my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
+                    my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
                     if ($marcfield eq $itemfield) {
                         $sqlfield = 'more_subfields_xml';
                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
                     } else {
-                        $sqlfield = 'marcxml';
+                        $sqlfield = 'metadata'; # From biblio_metadata
                         if ($marcfield < 10) {
                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
                         } else {
@@ -2731,11 +2244,16 @@ sub SearchItems {
         FROM items
           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
+          LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
+          WHERE 1
     };
     if (defined $where_str and $where_str ne '') {
-        $query .= qq{ WHERE $where_str };
+        $query .= qq{ AND $where_str };
     }
 
+    $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
+    push @where_args, C4::Context->preference('marcflavour');
+
     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
@@ -2819,9 +2337,13 @@ sub PrepareItemrecordDisplay {
     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
 
     my $dbh = C4::Context->dbh;
-    $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
-    my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
-    my $tagslib = &GetMarcStructure( 1, $frameworkcode );
+    $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
+    my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+
+    # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
+    # a shared data structure. No plugin (including custom ones) should change
+    # its contents. See also GetMarcStructure.
+    my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
 
     # return nothing if we don't have found an existing framework.
     return q{} unless $tagslib;
@@ -2845,13 +2367,13 @@ sub PrepareItemrecordDisplay {
     $query .= qq{ ORDER BY lib};
     my $authorised_values_sth = $dbh->prepare( $query );
     foreach my $tag ( sort keys %{$tagslib} ) {
-        my $previous_tag = '';
         if ( $tag ne '' ) {
 
             # loop through each subfield
             my $cntsubf;
             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
+                next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
                 my %subfield_data;
                 $subfield_data{tag}           = $tag;
@@ -2878,6 +2400,8 @@ sub PrepareItemrecordDisplay {
                     $defaultvalue =~ s/"/&quot;/g;
                 }
 
+                my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
+
                 # search for itemcallnumber if applicable
                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
                     && C4::Context->preference('itemcallnumber') ) {
@@ -2890,7 +2414,7 @@ sub PrepareItemrecordDisplay {
                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
                     && $defaultvalues
                     && $defaultvalues->{'callnumber'} ) {
-                    if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
+                    if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
                         # if the item record exists, only use default value if the item has no callnumber
                         $defaultvalue = $defaultvalues->{callnumber};
                     } elsif ( !$itemrecord and $defaultvalues ) {
@@ -2901,7 +2425,7 @@ sub PrepareItemrecordDisplay {
                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
                     && $defaultvalues
                     && $defaultvalues->{'branchcode'} ) {
-                    if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
+                    if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
                         $defaultvalue = $defaultvalues->{branchcode};
                     }
                 }
@@ -2909,7 +2433,7 @@ sub PrepareItemrecordDisplay {
                     && $defaultvalues
                     && $defaultvalues->{'location'} ) {
 
-                    if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
+                    if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
                         # if the item record exists, only use default value if the item has no locationr
                         $defaultvalue = $defaultvalues->{location};
                     } elsif ( !$itemrecord and $defaultvalues ) {
@@ -2952,13 +2476,17 @@ sub PrepareItemrecordDisplay {
 
                         #----- itemtypes
                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-                        my $itemtypes = GetItemTypes( style => 'array' );
+                        my $itemtypes = Koha::ItemTypes->search_with_localization;
                         push @authorised_values, ""
                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
-                        for my $itemtype ( @$itemtypes ) {
-                            push @authorised_values, $itemtype->{itemtype};
-                            $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
+                        while ( my $itemtype = $itemtypes->next ) {
+                            push @authorised_values, $itemtype->itemtype;
+                            $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
+                        }
+                        if ($defaultvalues && $defaultvalues->{'itemtype'}) {
+                            $defaultvalue = $defaultvalues->{'itemtype'};
                         }
+
                         #---- class_sources
                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
@@ -3003,22 +2531,25 @@ sub PrepareItemrecordDisplay {
                     });
                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
                     $plugin->build( $pars );
+                    if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
+                        $defaultvalue = $field->subfield($subfield);
+                    }
                     if( !$plugin->errstr ) {
                         #TODO Move html to template; see report 12176/13397
                         my $tab= $plugin->noclick? '-1': '';
                         my $class= $plugin->noclick? ' disabled': '';
                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
-                        $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
+                        $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
                     } else {
                         warn $plugin->errstr;
-                        $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" />); # supply default input form
+                        $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
                     }
                 }
                 elsif ( $tag eq '' ) {       # it's an hidden field
-                    $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
+                    $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
                 }
                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
-                    $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
+                    $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
                 }
                 elsif ( length($defaultvalue) > 100
                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
@@ -3027,9 +2558,9 @@ sub PrepareItemrecordDisplay {
                                   500 <= $tag && $tag < 600                     )
                           ) {
                     # oversize field (textarea)
-                    $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
+                    $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
                 } else {
-                    $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
+                    $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"$maxlength\" />";
                 }
                 push( @loop_data, \%subfield_data );
             }
@@ -3061,10 +2592,13 @@ sub ToggleNewStatus {
         my $age = $rule->{age};
         my $conditions = $rule->{conditions};
         my $substitutions = $rule->{substitutions};
+        foreach ( @$substitutions ) {
+            ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
+        }
         my @params;
 
         my $query = q|
-            SELECT items.biblionumber, items.itemnumber
+            SELECT items.*
             FROM items
             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
             WHERE 1
@@ -3095,10 +2629,10 @@ sub ToggleNewStatus {
         while ( my $values = $sth->fetchrow_hashref ) {
             my $biblionumber = $values->{biblionumber};
             my $itemnumber = $values->{itemnumber};
-            my $item = C4::Items::GetItem( $itemnumber );
             for my $substitution ( @$substitutions ) {
                 next unless $substitution->{field};
-                C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
+                next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
+                C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber )
                     unless $report_only;
                 push @{ $report->{$itemnumber} }, $substitution;
             }
@@ -3108,5 +2642,38 @@ sub ToggleNewStatus {
     return $report;
 }
 
+=head2 _after_item_action_hooks
+
+Helper method that takes care of calling all plugin hooks
+
+=cut
+
+sub _after_item_action_hooks {
+    my ( $args ) = @_;
+
+    my $item_id = $args->{item_id};
+    my $action  = $args->{action};
+
+    if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) {
+
+        my @plugins = Koha::Plugins->new->GetPlugins({
+            method => 'after_item_action',
+        });
+
+        if (@plugins) {
+
+            my $item = Koha::Items->find( $item_id );
+
+            foreach my $plugin ( @plugins ) {
+                try {
+                    $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id });
+                }
+                catch {
+                    warn "$_";
+                };
+            }
+        }
+    }
+}
 
 1;