Bug 15348: Add estimated delivery date field to individual orders
[koha-ffzg.git] / C4 / Items.pm
index 8ed1c5e..817e86e 100644 (file)
@@ -20,62 +20,51 @@ package C4::Items;
 
 use Modern::Perl;
 
-use vars qw(@ISA @EXPORT);
+our (@ISA, @EXPORT_OK);
 BEGIN {
     require Exporter;
     @ISA = qw(Exporter);
 
-    @EXPORT = qw(
+    @EXPORT_OK = qw(
         AddItemFromMarc
-        AddItem
         AddItemBatchFromMarc
         ModItemFromMarc
         Item2Marc
-        ModItem
         ModDateLastSeen
         ModItemTransfer
-        DelItem
         CheckItemPreSave
         GetItemsForInventory
-        GetItemsInfo
-        GetItemsLocationInfo
-        GetHostItemsInfo
         get_hostitemnumbers_of
-        GetHiddenItemnumbers
-        ItemSafeToDelete
-        DelItemCheck
-        MoveItemFromBiblio
+        GetMarcItem
         CartToShelf
         GetAnalyticsCount
-        SearchItemsByField
         SearchItems
         PrepareItemrecordDisplay
+        ToggleNewStatus
     );
 }
 
-use Carp;
-use Try::Tiny;
+use Carp qw( croak );
 use C4::Context;
 use C4::Koha;
-use C4::Biblio;
-use Koha::DateUtils;
+use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
 use MARC::Record;
-use C4::ClassSource;
-use C4::Log;
-use List::MoreUtils qw(any);
-use YAML qw(Load);
+use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource );
+use C4::Log qw( logaction );
+use List::MoreUtils qw( any );
 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::DateUtils qw( dt_from_string );
 use Koha::Database;
 
+use Koha::Biblios;
 use Koha::Biblioitems;
 use Koha::Items;
 use Koha::ItemTypes;
 use Koha::SearchEngine;
+use Koha::SearchEngine::Indexer;
 use Koha::SearchEngine::Search;
 use Koha::Libraries;
 
@@ -136,97 +125,55 @@ sub CartToShelf {
 
     my $item = Koha::Items->find($itemnumber);
     if ( $item->location eq 'CART' ) {
-        ModItem({ location => $item->permanent_location}, undef, $itemnumber);
+        $item->location($item->permanent_location)->store;
     }
 }
 
 =head2 AddItemFromMarc
 
   my ($biblionumber, $biblioitemnumber, $itemnumber) 
-      = AddItemFromMarc($source_item_marc, $biblionumber);
+      = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
 
 Given a MARC::Record object containing an embedded item
 record and a biblionumber, create a new item record.
 
+The final optional parameter, C<$params>, may contain
+'skip_record_index' key, which relayed down to Koha::Item/store,
+there it prevents calling of index_records,
+which takes most of the time in batch adds/deletes: index_records
+to be called later in C<additem.pl> after the whole loop.
+
+You may also optionally pass biblioitemnumber in the params hash to
+boost performance of inserts by preventing a lookup in Koha::Item.
+
+$params:
+    skip_record_index => 1|0
+    biblioitemnumber => $biblioitemnumber
+
 =cut
 
 sub AddItemFromMarc {
-    my ( $source_item_marc, $biblionumber ) = @_;
+    my $source_item_marc = shift;
+    my $biblionumber     = shift;
+    my $params           = @_ ? shift : {};
+
     my $dbh = C4::Context->dbh;
 
     # parse item hash from MARC
     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
-
     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
-
-  my ($biblionumber, $biblioitemnumber, $itemnumber) 
-      = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
-
-Given a hash containing item column names as keys,
-create a new Koha item record.
-
-The first two optional parameters (C<$dbh> and C<$frameworkcode>)
-do not need to be supplied for general use; they exist
-simply to allow them to be picked up from AddItemFromMarc.
-
-The final optional parameter, 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 
-the biblio items tag for display and indexing.
-
-=cut
-
-sub AddItem {
-    my $item         = shift;
-    my $biblionumber = shift;
-
-    my $dbh           = @_ ? shift : C4::Context->dbh;
-    my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
-    my $unlinked_item_subfields;
-    if (@_) {
-        $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;
-
-    _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
-        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} );
-    return if $error;
-
-    $item->{'itemnumber'} = $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 );
+    my $item_values = C4::Biblio::TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
+    my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+    $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
+    $item_values->{biblionumber} = $biblionumber;
+    $item_values->{biblioitemnumber} = $params->{biblioitemnumber};
+    $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+    $item_values->{cn_sort}   = delete $item_values->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
+    my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
+    return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
 }
 
 =head2 AddItemBatchFromMarc
@@ -276,7 +223,6 @@ Additional information appropriate to the error condition.
 
 sub AddItemBatchFromMarc {
     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
-    my $error;
     my @itemnumbers = ();
     my @errors = ();
     my $dbh = C4::Context->dbh;
@@ -298,11 +244,13 @@ sub AddItemBatchFromMarc {
         $temp_item_marc->append_fields($item_field);
     
         # add biblionumber and biblioitemnumber
-        my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
+        my $item = TransformMarcToKoha({ record => $temp_item_marc, limit_table => 'items' });
         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
         $item->{'biblionumber'} = $biblionumber;
         $item->{'biblioitemnumber'} = $biblioitemnumber;
+        $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+        $item->{cn_sort}   = delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
 
         # check for duplicate barcode
         my %item_errors = CheckItemPreSave($item);
@@ -312,16 +260,12 @@ sub AddItemBatchFromMarc {
             next ITEMFIELD;
         }
 
-        _set_defaults_for_add($item);
-        _set_derived_columns_for_add($item);
-        my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
-        warn $error if $error;
-        push @itemnumbers, $itemnumber; # FIXME not checking error
-        $item->{'itemnumber'} = $itemnumber;
+        my $item_object = Koha::Item->new($item)->store;
+        push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
 
-        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
+        logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
 
-        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
+        my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
         $item_field->replace_with($new_item_marc->field($itemtag));
     }
 
@@ -330,301 +274,140 @@ sub AddItemBatchFromMarc {
         $record->delete_field($item_field);
     }
 
-    # update the MARC biblio
- #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
-
     return (\@itemnumbers, \@errors);
 }
 
 =head2 ModItemFromMarc
 
-  ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
-
-This function updates an item record based on a supplied
-C<MARC::Record> object containing an embedded item field.
-This API is meant for the use of C<additem.pl>; for 
-other purposes, C<ModItem> should be used.
+my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
 
-This function uses the hash %default_values_for_mod_from_marc,
-which contains default values for item fields to
-apply when modifying an item.  This is needed because
-if an item field's value is cleared, TransformMarcToKoha
-does not include the column in the
-hash that's passed to ModItem, which without
-use of this hash makes it impossible to clear
-an item field's value.  See bug 2466.
+The final optional parameter, C<$params>, expected to contain
+'skip_record_index' key, which relayed down to Koha::Item/store,
+there it prevents calling of index_records,
+which takes most of the time in batch adds/deletes: index_records better
+to be called later in C<additem.pl> after the whole loop.
 
-Note that only columns that can be directly
-changed from the cataloging and serials
-item editors are included in this hash.
-
-Returns item record
+$params:
+    skip_record_index => 1|0
 
 =cut
 
-sub _build_default_values_for_mod_marc {
-    # Has no framework parameter anymore, since Default is authoritative
-    # for Koha to MARC mappings.
-
-    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;
-
-    my $default_values = {
-        barcode                  => undef,
-        booksellerid             => undef,
-        ccode                    => undef,
-        'items.cn_source'        => undef,
-        coded_location_qualifier => undef,
-        copynumber               => undef,
-        damaged                  => 0,
-        enumchron                => undef,
-        holdingbranch            => undef,
-        homebranch               => undef,
-        itemcallnumber           => undef,
-        itemlost                 => 0,
-        itemnotes                => undef,
-        itemnotes_nonpublic      => undef,
-        itype                    => undef,
-        location                 => undef,
-        permanent_location       => undef,
-        materials                => undef,
-        new_status               => undef,
-        notforloan               => 0,
-        price                    => undef,
-        replacementprice         => undef,
-        replacementpricedate     => undef,
-        restricted               => undef,
-        stack                    => undef,
-        stocknumber              => undef,
-        uri                      => undef,
-        withdrawn                => 0,
-    };
-    my %default_values_for_mod_from_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::Biblio::GetMarcFromKohaField( $kohafield );
-    }
-
-    $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
-    return \%default_values_for_mod_from_marc;
-}
-
 sub ModItemFromMarc {
-    my $item_marc = shift;
-    my $biblionumber = shift;
-    my $itemnumber = shift;
+    my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
 
     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
 
     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();
-    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, { unlinked_item_subfields => $unlinked_item_subfields } );
-    return $item;
-}
-
-=head2 ModItem
-
-ModItem(
-    { column => $newvalue },
-    $biblionumber,
-    $itemnumber,
-    {
-        [ unlinked_item_subfields => $unlinked_item_subfields, ]
-        [ log_action => 1, ]
-    }
-);
-
-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.
-
-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 
-the biblio items tag for display and indexing.
-
-If one of the changed columns is used to calculate
-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, $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);
-    }
-
-    if ($unlinked_item_subfields) {
-        $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
-    };
+    my $item_object = Koha::Items->find($itemnumber);
+    my $item = TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
 
-    my @fields = qw( itemlost withdrawn damaged );
+    # When importing items we blank this column, we need to set it to the existing value
+    # to prevent it being blanked by set_or_blank
+    $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} );
 
-    # 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 $item->{$field} )
-            {
-                $item->{ $field . '_on' } =
-                  DateTime::Format::MySQL->format_datetime( dt_from_string() );
-            }
-        }
-    }
+    my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
+    my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
 
-    # If the field is defined but empty, we are removing and,
-    # and thus need to clear out the 'on' field as well
-    for my $field (@fields) {
-        if ( defined( $item->{$field} ) && !$item->{$field} ) {
-            $item->{ $field . '_on' } = undef;
+    # Retrieving the values for the fields that are not linked
+    my @mapped_fields = Koha::MarcSubfieldStructures->search(
+        {
+            frameworkcode => $frameworkcode,
+            kohafield     => { -like => "items.%" }
         }
+    )->get_column('kohafield');
+    for my $c ( $item_object->_result->result_source->columns ) {
+        next if grep { "items.$c" eq $_ } @mapped_fields;
+        $item->{$c} = $item_object->$c;
     }
 
+    $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+    delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
+    $item->{itemnumber} = $itemnumber;
+    $item->{biblionumber} = $biblionumber;
 
-    _set_derived_columns_for_mod($item);
-    _do_column_fixes_for_mod($item);
-    # FIXME add checks
-    # duplicate barcode
-    # attempt to change itemnumber
-    # attempt to change biblionumber (if we want
-    # an API to relink an item to a different bib,
-    # it should be a separate function)
-
-    # update items table
-    _koha_modify_item($item);
+    my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
+                                                  # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
+    $item_object = $item_object->set_or_blank($item);
+    $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
 
-    # request that bib be reindexed so that searching on current
-    # item status is possible
-    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
+    $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
 
-    _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
+    my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+    $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
+    $item_object->store({ skip_record_index => $params->{skip_record_index} });
 
-    logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
-      if $log_action && C4::Context->preference("CataloguingLog");
+    return $item_object->unblessed;
 }
 
 =head2 ModItemTransfer
 
-  ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
+  ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
 
 Marks an item as being transferred from one branch to another and records the trigger.
 
+The last optional parameter allows for passing skip_record_index through to the items store call.
+
 =cut
 
 sub ModItemTransfer {
-    my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
+    my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
 
     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 $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $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);
+    # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
+    # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
+    # will have been preceded by a check of branch transfer limits)
+    my $to_library = Koha::Libraries->find($tobranch);
+    my $transfer = $item->request_transfer(
+        {
+            to            => $to_library,
+            reason        => $trigger,
+            ignore_limits => 1,
+            replace       => 1
+        }
+    );
 
-    #new entry in branchtransfers....
-    my $sth = $dbh->prepare(
-        "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
-        VALUES (?, ?, NOW(), ?, ?)");
-    $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
+    # Immediately set the item to in transit if it is checked in
+    if ( !$item->checkout ) {
+        $item->holdingbranch($frombranch)->store(
+            {
+                log_action        => 0,
+                skip_record_index => $params->{skip_record_index}
+            }
+        );
+        $transfer->transit;
+    }
 
-    ModItem({ holdingbranch => $frombranch }, undef, $itemnumber, { log_action => 0 });
-    ModDateLastSeen($itemnumber);
     return;
 }
 
 =head2 ModDateLastSeen
 
-ModDateLastSeen( $itemnumber, $leave_item_lost );
+ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
 
 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
 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, $leave_item_lost ) = @_;
-
-    my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
-
-    my $params;
-    $params->{datelastseen} = $today;
-    $params->{itemlost} = 0 unless $leave_item_lost;
-
-    ModItem( $params, undef, $itemnumber, { log_action => 0 } );
-}
-
-=head2 DelItem
-
-  DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
-
-Exported function (core API) for deleting an item record in Koha.
+The last optional parameter allows for passing skip_record_index through to the items store call.
 
 =cut
 
-sub DelItem {
-    my ( $params ) = @_;
-
-    my $itemnumber   = $params->{itemnumber};
-    my $biblionumber = $params->{biblionumber};
-
-    unless ($biblionumber) {
-        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
-    return 0 unless $biblionumber;
-
-    # FIXME check the item has no current issues
-    my $deleted = _koha_delete_item( $itemnumber );
-
-    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
-
-    _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
+sub ModDateLastSeen {
+    my ( $itemnumber, $leave_item_lost, $params ) = @_;
 
-    #search item field code
-    logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
-    return $deleted;
+    my $item = Koha::Items->find($itemnumber);
+    $item->datelastseen(dt_from_string);
+    $item->itemlost(0) unless $leave_item_lost;
+    $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
 }
 
 =head2 CheckItemPreSave
 
-    my $item_ref = TransformMarcToKoha($marc, 'items');
+    my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
     # do stuff
     my %errors = CheckItemPreSave($item_ref);
     if (exists $errors{'duplicate_barcode'}) {
@@ -714,7 +497,6 @@ lists of authorized values for certain item fields.
   minlocation  => $minlocation,
   maxlocation  => $maxlocation,
   location     => $location,
-  itemtype     => $itemtype,
   ignoreissued => $ignoreissued,
   datelastseen => $datelastseen,
   branchcode   => $branchcode,
@@ -722,6 +504,7 @@ lists of authorized values for certain item fields.
   offset       => $offset,
   size         => $size,
   statushash   => $statushash,
+  itemtypes    => \@itemsarray,
 } );
 
 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
@@ -754,6 +537,8 @@ sub GetItemsForInventory {
     my $size         = $parameters->{'size'}         // '';
     my $statushash   = $parameters->{'statushash'}   // '';
     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
+    my $itemtypes    = $parameters->{'itemtypes'}    || [];
+    my $ccode        = $parameters->{'ccode'}        // '';
 
     my $dbh = C4::Context->dbh;
     my ( @bind_params, @where_strings );
@@ -762,7 +547,8 @@ sub GetItemsForInventory {
     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
 
     my $select_columns = q{
-        SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
+        SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
+
     };
     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
     my $query = q{
@@ -779,6 +565,11 @@ sub GetItemsForInventory {
         }
     }
 
+    if ($ccode){
+        push @where_strings, 'ccode = ?';
+        push @bind_params, $ccode;
+    }
+
     if ($minlocation) {
         push @where_strings, 'items.cn_sort >= ?';
         push @bind_params, $min_cnsort;
@@ -790,7 +581,6 @@ sub GetItemsForInventory {
     }
 
     if ($datelastseen) {
-        $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
         push @bind_params, $datelastseen;
     }
@@ -813,7 +603,6 @@ sub GetItemsForInventory {
         push @where_strings, 'biblioitems.itemtype = ?';
         push @bind_params, $itemtype;
     }
-
     if ( $ignoreissued) {
         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
         push @where_strings, 'issues.date_due IS NULL';
@@ -824,6 +613,11 @@ sub GetItemsForInventory {
         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
     }
 
+    if ( @$itemtypes ) {
+        my $itemtypes_str = join ', ', @$itemtypes;
+        push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
+    }
+
     if ( @where_strings ) {
         $query .= 'WHERE ';
         $query .= join ' AND ', @where_strings;
@@ -850,7 +644,7 @@ sub GetItemsForInventory {
             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
         }
-    );
+    )->as_list;
 
     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
 
@@ -872,263 +666,6 @@ sub GetItemsForInventory {
     return (\@results, $iTotalRecords);
 }
 
-=head2 GetItemsInfo
-
-  @results = GetItemsInfo($biblionumber);
-
-Returns information about items with the given biblionumber.
-
-C<GetItemsInfo> returns a list of references-to-hash. Each element
-contains a number of keys. Most of them are attributes from the
-C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
-Koha database. Other keys include:
-
-=over 2
-
-=item C<$data-E<gt>{branchname}>
-
-The name (not the code) of the branch to which the book belongs.
-
-=item C<$data-E<gt>{datelastseen}>
-
-This is simply C<items.datelastseen>, except that while the date is
-stored in YYYY-MM-DD format in the database, here it is converted to
-DD/MM/YYYY format. A NULL date is returned as C<//>.
-
-=item C<$data-E<gt>{datedue}>
-
-=item C<$data-E<gt>{class}>
-
-This is the concatenation of C<biblioitems.classification>, the book's
-Dewey code, and C<biblioitems.subclass>.
-
-=item C<$data-E<gt>{ocount}>
-
-I think this is the number of copies of the book available.
-
-=item C<$data-E<gt>{order}>
-
-If this is set, it is set to C<One Order>.
-
-=back
-
-=cut
-
-sub GetItemsInfo {
-    my ( $biblionumber ) = @_;
-    my $dbh   = C4::Context->dbh;
-    require C4::Languages;
-    my $language = C4::Languages::getlanguage();
-    my $query = "
-    SELECT items.*,
-           biblio.*,
-           biblioitems.volume,
-           biblioitems.number,
-           biblioitems.itemtype,
-           biblioitems.isbn,
-           biblioitems.issn,
-           biblioitems.publicationyear,
-           biblioitems.publishercode,
-           biblioitems.volumedate,
-           biblioitems.volumedesc,
-           biblioitems.lccn,
-           biblioitems.url,
-           items.notforloan as itemnotforloan,
-           issues.borrowernumber,
-           issues.date_due as datedue,
-           issues.onsite_checkout,
-           borrowers.cardnumber,
-           borrowers.surname,
-           borrowers.firstname,
-           borrowers.branchcode as bcode,
-           serial.serialseq,
-           serial.publisheddate,
-           itemtypes.description,
-           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,
-           IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
-     FROM items
-     LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
-     LEFT JOIN branches AS home ON items.homebranch=home.branchcode
-     LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
-     LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
-     LEFT JOIN issues USING (itemnumber)
-     LEFT JOIN borrowers USING (borrowernumber)
-     LEFT JOIN serialitems USING (itemnumber)
-     LEFT JOIN serial USING (serialid)
-     LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
-     . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
-    $query .= q|
-    LEFT JOIN tmp_holdsqueue USING (itemnumber)
-    LEFT JOIN localization ON itemtypes.itemtype = localization.code
-        AND localization.entity = 'itemtypes'
-        AND localization.lang = ?
-    |;
-
-    $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
-    my $sth = $dbh->prepare($query);
-    $sth->execute($language, $biblionumber);
-    my $i = 0;
-    my @results;
-    my $serial;
-
-    my $userenv = C4::Context->userenv;
-    my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
-    while ( my $data = $sth->fetchrow_hashref ) {
-        if ( $data->{borrowernumber} && $want_not_same_branch) {
-            $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
-        }
-
-        $serial ||= $data->{'serial'};
-
-        my $descriptions;
-        # get notforloan complete status if applicable
-        $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
-        $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
-        $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
-                                    WHERE itemnumber = ?
-                                    AND old_issues.borrowernumber = borrowers.borrowernumber
-                                    ORDER BY returndate DESC
-                                    LIMIT 3");
-        $sth2->execute($data->{'itemnumber'});
-        my $ii = 0;
-        while (my $data2 = $sth2->fetchrow_hashref()) {
-            $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
-            $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
-            $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
-            $ii++;
-        }
-
-        $results[$i] = $data;
-        $i++;
-    }
-
-    return $serial
-        ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
-        : @results;
-}
-
-=head2 GetItemsLocationInfo
-
-  my @itemlocinfo = GetItemsLocationInfo($biblionumber);
-
-Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
-
-C<GetItemsInfo> returns a list of references-to-hash. Data returned:
-
-=over 2
-
-=item C<$data-E<gt>{homebranch}>
-
-Branch Name of the item's homebranch
-
-=item C<$data-E<gt>{holdingbranch}>
-
-Branch Name of the item's holdingbranch
-
-=item C<$data-E<gt>{location}>
-
-Item's shelving location code
-
-=item C<$data-E<gt>{location_intranet}>
-
-The intranet description for the Shelving Location as set in authorised_values 'LOC'
-
-=item C<$data-E<gt>{location_opac}>
-
-The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
-description is set.
-
-=item C<$data-E<gt>{itemcallnumber}>
-
-Item's itemcallnumber
-
-=item C<$data-E<gt>{cn_sort}>
-
-Item's call number normalized for sorting
-
-=back
-  
-=cut
-
-sub GetItemsLocationInfo {
-        my $biblionumber = shift;
-        my @results;
-
-       my $dbh = C4::Context->dbh;
-       my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
-                           location, itemcallnumber, cn_sort
-                    FROM items, branches as a, branches as b
-                    WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
-                    AND biblionumber = ?
-                    ORDER BY cn_sort ASC";
-       my $sth = $dbh->prepare($query);
-        $sth->execute($biblionumber);
-
-        while ( my $data = $sth->fetchrow_hashref ) {
-             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;
-}
-
-=head2 GetHostItemsInfo
-
-    $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('EasyAnalyticalRecords') ) {
-        return @returnitemsInfo;
-    }
-
-    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');
-    }
-
-    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
 
   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
@@ -1148,13 +685,14 @@ sub get_hostitemnumbers_of {
         return ();
     }
 
-    my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
+    my $biblio = Koha::Biblios->find($biblionumber);
+    my $marcrecord = $biblio->metadata->record;
     return unless $marcrecord;
 
     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
 
     my $marcflavor = C4::Context->preference('marcflavour');
-    if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
+    if ( $marcflavor eq 'MARC21' ) {
         $tag      = '773';
         $biblio_s = '0';
         $item_s   = '9';
@@ -1181,71 +719,6 @@ sub get_hostitemnumbers_of {
     return @returnhostitemnumbers;
 }
 
-=head2 GetHiddenItemnumbers
-
-    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. Optionally takes a borcat parameter for certain borrower types
-to be excluded
-
-=cut
-
-sub GetHiddenItemnumbers {
-    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');
-    return () if (! $yaml =~ /\S/ );
-    $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
-    my $hidingrules;
-    eval {
-        $hidingrules = YAML::Load($yaml);
-    };
-    if ($@) {
-        warn "Unable to parse OpacHiddenItems syspref : $@";
-        return ();
-    }
-    my $dbh = C4::Context->dbh;
-
-    # For each item
-    foreach my $item (@$items) {
-
-        # We check each rule
-        foreach my $field (keys %$hidingrules) {
-            my $val;
-            if (exists $item->{$field}) {
-                $val = $item->{$field};
-            }
-            else {
-                my $query = "SELECT $field from items where itemnumber = ?";
-                $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
-            }
-            $val = '' unless defined $val;
-
-            # If the results matches the values in the yaml file
-            if (any { $val eq $_ } @{$hidingrules->{$field}}) {
-
-                # We add the itemnumber to the list
-                push @resultitems, $item->{'itemnumber'};
-
-                # If at least one rule matched for an item, no need to test the others
-                last;
-            }
-        }
-    }
-    return @resultitems;
-}
-
 =head1 LIMITED USE FUNCTIONS
 
 The following functions, while part of the public API,
@@ -1298,7 +771,7 @@ sub Item2Marc {
         } keys %{ $itemrecord } 
     };
     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
-    my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
+    my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
         "items.itemnumber", $framework,
     );
@@ -1320,572 +793,6 @@ the inner workings of C<C4::Items>.
 
 =cut
 
-=head2 %derived_columns
-
-This hash keeps track of item columns that
-are strictly derived from other columns in
-the item record and are not meant to be set
-independently.
-
-Each key in the hash should be the name of a
-column (as named by TransformMarcToKoha).  Each
-value should be hashref whose keys are the
-columns on which the derived column depends.  The
-hashref should also contain a 'BUILDER' key
-that is a reference to a sub that calculates
-the derived value.
-
-=cut
-
-my %derived_columns = (
-    'items.cn_sort' => {
-        'itemcallnumber' => 1,
-        'items.cn_source' => 1,
-        'BUILDER' => \&_calc_items_cn_sort,
-    }
-);
-
-=head2 _set_derived_columns_for_add 
-
-  _set_derived_column_for_add($item);
-
-Given an item hash representing a new item to be added,
-calculate any derived columns.  Currently the only
-such column is C<items.cn_sort>.
-
-=cut
-
-sub _set_derived_columns_for_add {
-    my $item = shift;
-
-    foreach my $column (keys %derived_columns) {
-        my $builder = $derived_columns{$column}->{'BUILDER'};
-        my $source_values = {};
-        foreach my $source_column (keys %{ $derived_columns{$column} }) {
-            next if $source_column eq 'BUILDER';
-            $source_values->{$source_column} = $item->{$source_column};
-        }
-        $builder->($item, $source_values);
-    }
-}
-
-=head2 _set_derived_columns_for_mod 
-
-  _set_derived_column_for_mod($item);
-
-Given an item hash representing a new item to be modified.
-calculate any derived columns.  Currently the only
-such column is C<items.cn_sort>.
-
-This routine differs from C<_set_derived_columns_for_add>
-in that it needs to handle partial item records.  In other
-words, the caller of C<ModItem> may have supplied only one
-or two columns to be changed, so this function needs to
-determine whether any of the columns to be changed affect
-any of the derived columns.  Also, if a derived column
-depends on more than one column, but the caller is not
-changing all of then, this routine retrieves the unchanged
-values from the database in order to ensure a correct
-calculation.
-
-=cut
-
-sub _set_derived_columns_for_mod {
-    my $item = shift;
-
-    foreach my $column (keys %derived_columns) {
-        my $builder = $derived_columns{$column}->{'BUILDER'};
-        my $source_values = {};
-        my %missing_sources = ();
-        my $must_recalc = 0;
-        foreach my $source_column (keys %{ $derived_columns{$column} }) {
-            next if $source_column eq 'BUILDER';
-            if (exists $item->{$source_column}) {
-                $must_recalc = 1;
-                $source_values->{$source_column} = $item->{$source_column};
-            } else {
-                $missing_sources{$source_column} = 1;
-            }
-        }
-        if ($must_recalc) {
-            foreach my $source_column (keys %missing_sources) {
-                $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
-            }
-            $builder->($item, $source_values);
-        }
-    }
-}
-
-=head2 _do_column_fixes_for_mod
-
-  _do_column_fixes_for_mod($item);
-
-Given an item hashref containing one or more
-columns to modify, fix up certain values.
-Specifically, set to 0 any passed value
-of C<notforloan>, C<damaged>, C<itemlost>, or
-C<withdrawn> that is either undefined or
-contains the empty string.
-
-=cut
-
-sub _do_column_fixes_for_mod {
-    my $item = shift;
-
-    if (exists $item->{'notforloan'} and
-        (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
-        $item->{'notforloan'} = 0;
-    }
-    if (exists $item->{'damaged'} and
-        (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
-        $item->{'damaged'} = 0;
-    }
-    if (exists $item->{'itemlost'} and
-        (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
-        $item->{'itemlost'} = 0;
-    }
-    if (exists $item->{'withdrawn'} and
-        (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
-        $item->{'withdrawn'} = 0;
-    }
-    if (
-        exists $item->{location}
-        and ( !defined $item->{location}
-            || ( $item->{location} ne 'CART' and $item->{location} ne 'PROC' ) )
-        and not $item->{permanent_location}
-      )
-    {
-        $item->{'permanent_location'} = $item->{'location'};
-    }
-    if (exists $item->{'timestamp'}) {
-        delete $item->{'timestamp'};
-    }
-}
-
-=head2 _get_single_item_column
-
-  _get_single_item_column($column, $itemnumber);
-
-Retrieves the value of a single column from an C<items>
-row specified by C<$itemnumber>.
-
-=cut
-
-sub _get_single_item_column {
-    my $column = shift;
-    my $itemnumber = shift;
-    
-    my $dbh = C4::Context->dbh;
-    my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
-    $sth->execute($itemnumber);
-    my ($value) = $sth->fetchrow();
-    return $value; 
-}
-
-=head2 _calc_items_cn_sort
-
-  _calc_items_cn_sort($item, $source_values);
-
-Helper routine to calculate C<items.cn_sort>.
-
-=cut
-
-sub _calc_items_cn_sort {
-    my $item = shift;
-    my $source_values = shift;
-
-    $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
-}
-
-=head2 _set_defaults_for_add 
-
-  _set_defaults_for_add($item_hash);
-
-Given an item hash representing an item to be added, set
-correct default values for columns whose default value
-is not handled by the DBMS.  This includes the following
-columns:
-
-=over 2
-
-=item * 
-
-C<items.dateaccessioned>
-
-=item *
-
-C<items.notforloan>
-
-=item *
-
-C<items.damaged>
-
-=item *
-
-C<items.itemlost>
-
-=item *
-
-C<items.withdrawn>
-
-=back
-
-=cut
-
-sub _set_defaults_for_add {
-    my $item = shift;
-    $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
-    $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
-}
-
-=head2 _koha_new_item
-
-  my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
-
-Perform the actual insert into the C<items> table.
-
-=cut
-
-sub _koha_new_item {
-    my ( $item, $barcode ) = @_;
-    my $dbh=C4::Context->dbh;  
-    my $error;
-    $item->{permanent_location} //= $item->{location};
-    _mod_item_dates( $item );
-    my $query =
-           "INSERT INTO items SET
-            biblionumber        = ?,
-            biblioitemnumber    = ?,
-            barcode             = ?,
-            dateaccessioned     = ?,
-            booksellerid        = ?,
-            homebranch          = ?,
-            price               = ?,
-            replacementprice    = ?,
-            replacementpricedate = ?,
-            datelastborrowed    = ?,
-            datelastseen        = ?,
-            stack               = ?,
-            notforloan          = ?,
-            damaged             = ?,
-            itemlost            = ?,
-            withdrawn           = ?,
-            itemcallnumber      = ?,
-            coded_location_qualifier = ?,
-            restricted          = ?,
-            itemnotes           = ?,
-            itemnotes_nonpublic = ?,
-            holdingbranch       = ?,
-            location            = ?,
-            permanent_location  = ?,
-            onloan              = ?,
-            issues              = ?,
-            renewals            = ?,
-            reserves            = ?,
-            cn_source           = ?,
-            cn_sort             = ?,
-            ccode               = ?,
-            itype               = ?,
-            materials           = ?,
-            uri                 = ?,
-            enumchron           = ?,
-            more_subfields_xml  = ?,
-            copynumber          = ?,
-            stocknumber         = ?,
-            new_status          = ?
-          ";
-    my $sth = $dbh->prepare($query);
-    my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
-   $sth->execute(
-            $item->{'biblionumber'},
-            $item->{'biblioitemnumber'},
-            $barcode,
-            $item->{'dateaccessioned'},
-            $item->{'booksellerid'},
-            $item->{'homebranch'},
-            $item->{'price'},
-            $item->{'replacementprice'},
-            $item->{'replacementpricedate'} || $today,
-            $item->{datelastborrowed},
-            $item->{datelastseen} || $today,
-            $item->{stack},
-            $item->{'notforloan'},
-            $item->{'damaged'},
-            $item->{'itemlost'},
-            $item->{'withdrawn'},
-            $item->{'itemcallnumber'},
-            $item->{'coded_location_qualifier'},
-            $item->{'restricted'},
-            $item->{'itemnotes'},
-            $item->{'itemnotes_nonpublic'},
-            $item->{'holdingbranch'},
-            $item->{'location'},
-            $item->{'permanent_location'},
-            $item->{'onloan'},
-            $item->{'issues'},
-            $item->{'renewals'},
-            $item->{'reserves'},
-            $item->{'items.cn_source'},
-            $item->{'items.cn_sort'},
-            $item->{'ccode'},
-            $item->{'itype'},
-            $item->{'materials'},
-            $item->{'uri'},
-            $item->{'enumchron'},
-            $item->{'more_subfields_xml'},
-            $item->{'copynumber'},
-            $item->{'stocknumber'},
-            $item->{'new_status'},
-    );
-
-    my $itemnumber;
-    if ( defined $sth->errstr ) {
-        $error.="ERROR in _koha_new_item $query".$sth->errstr;
-    }
-    else {
-        $itemnumber = $dbh->{'mysql_insertid'};
-    }
-
-    return ( $itemnumber, $error );
-}
-
-=head2 MoveItemFromBiblio
-
-  MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
-
-Moves an item from a biblio to another
-
-Returns undef if the move failed or the biblionumber of the destination record otherwise
-
-=cut
-
-sub MoveItemFromBiblio {
-    my ($itemnumber, $frombiblio, $tobiblio) = @_;
-    my $dbh = C4::Context->dbh;
-    my ( $tobiblioitem ) = $dbh->selectrow_array(q|
-        SELECT biblioitemnumber
-        FROM biblioitems
-        WHERE biblionumber = ?
-    |, undef, $tobiblio );
-    my $return = $dbh->do(q|
-        UPDATE items
-        SET biblioitemnumber = ?,
-            biblionumber = ?
-        WHERE itemnumber = ?
-            AND biblionumber = ?
-    |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
-    if ($return == 1) {
-        ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
-        ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
-           # Checking if the item we want to move is in an order 
-        require C4::Acquisition;
-        my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
-           if ($order) {
-                   # Replacing the biblionumber within the order if necessary
-                   $order->{'biblionumber'} = $tobiblio;
-               C4::Acquisition::ModOrder($order);
-           }
-
-        # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
-        for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
-            $dbh->do( qq|
-                UPDATE $table_name
-                SET biblionumber = ?
-                WHERE itemnumber = ?
-            |, undef, $tobiblio, $itemnumber );
-        }
-        return $tobiblio;
-       }
-    return;
-}
-
-=head2 ItemSafeToDelete
-
-   ItemSafeToDelete( $biblionumber, $itemnumber);
-
-Exported function (core API) for checking whether an item record is safe to delete.
-
-returns 1 if the item is safe to delete,
-
-"book_on_loan" if the item is checked out,
-
-"not_same_branch" if the item is blocked by independent branches,
-
-"book_reserved" if the there are holds aganst the item, or
-
-"linked_analytics" if the item has linked analytic records.
-
-=cut
-
-sub ItemSafeToDelete {
-    my ( $biblionumber, $itemnumber ) = @_;
-    my $status;
-    my $dbh = C4::Context->dbh;
-
-    my $error;
-
-    my $countanalytics = GetAnalyticsCount($itemnumber);
-
-    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 ) )
-    {
-        $status = "not_same_branch";
-    }
-    else {
-        # check it doesn't have a waiting reserve
-        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) {
-            $status = "book_reserved";
-        }
-        elsif ( $countanalytics > 0 ) {
-            $status = "linked_analytics";
-        }
-        else {
-            $status = 1;
-        }
-    }
-    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
-
-  my ($itemnumber,$error) =_koha_modify_item( $item );
-
-Perform the actual update of the C<items> row.  Note that this
-routine accepts a hashref specifying the columns to update.
-
-=cut
-
-sub _koha_modify_item {
-    my ( $item ) = @_;
-    my $dbh=C4::Context->dbh;  
-    my $error;
-
-    my $query = "UPDATE items SET ";
-    my @bind;
-    _mod_item_dates( $item );
-    for my $key ( keys %$item ) {
-        next if ( $key eq 'itemnumber' );
-        $query.="$key=?,";
-        push @bind, $item->{$key};
-    }
-    $query =~ s/,$//;
-    $query .= " WHERE itemnumber=?";
-    push @bind, $item->{'itemnumber'};
-    my $sth = $dbh->prepare($query);
-    $sth->execute(@bind);
-    if ( $sth->err ) {
-        $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
-        warn $error;
-    }
-    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 );
-
-Internal function to delete an item record from the koha tables
-
-=cut
-
-sub _koha_delete_item {
-    my ( $itemnum ) = @_;
-
-    my $dbh = C4::Context->dbh;
-    # save the deleted item to deleteditems table
-    my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
-    $sth->execute($itemnum);
-    my $data = $sth->fetchrow_hashref();
-
-    # There is no item to delete
-    return 0 unless $data;
-
-    my $query = "INSERT INTO deleteditems SET ";
-    my @bind  = ();
-    foreach my $key ( keys %$data ) {
-        next if ( $key eq 'timestamp' ); # timestamp will be set by db
-        $query .= "$key = ?,";
-        push( @bind, $data->{$key} );
-    }
-    $query =~ s/\,$//;
-    $sth = $dbh->prepare($query);
-    $sth->execute(@bind);
-
-    # delete from items table
-    $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
-    my $deleted = $sth->execute($itemnum);
-    return ( $deleted == 1 ) ? 1 : 0;
-}
-
 =head2 _marc_from_item_hash
 
   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
@@ -2046,6 +953,10 @@ counts Usage of itemnumber in Analytical bibliorecords.
 sub GetAnalyticsCount {
     my ($itemnumber) = @_;
 
+    if ( !C4::Context->preference('EasyAnalyticalRecords') ) {
+        return 0;
+    }
+
     ### ZOOM search here
     my $query;
     $query= "hi=".$itemnumber;
@@ -2054,29 +965,6 @@ sub GetAnalyticsCount {
     return ($result);
 }
 
-=head2 SearchItemsByField
-
-    my $items = SearchItemsByField($field, $value);
-
-SearchItemsByField will search for items on a specific given field.
-For instance you can search all items with a specific stocknumber like this:
-
-    my $items = SearchItemsByField('stocknumber', $stocknumber);
-
-=cut
-
-sub SearchItemsByField {
-    my ($field, $value) = @_;
-
-    my $filters = {
-        field => $field,
-        query => $value,
-    };
-
-    my ($results) = SearchItems($filters);
-    return $results;
-}
-
 sub _SearchItems_build_where_fragment {
     my ($filter) = @_;
 
@@ -2104,11 +992,14 @@ sub _SearchItems_build_where_fragment {
         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;
-        my @operators = qw(= != > < >= <= like);
+        push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
+        my @operators = qw(= != > < >= <= is like);
+        push @operators, 'not like';
         my $field = $filter->{field} // q{};
         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
             my $op = $filter->{operator};
             my $query = $filter->{query};
+            my $ifnull = $filter->{ifnull};
 
             if (!$op or (0 == grep { $_ eq $op } @operators)) {
                 $op = '='; # default operator
@@ -2145,22 +1036,52 @@ sub _SearchItems_build_where_fragment {
                     }
                     $column = "ExtractValue($sqlfield, '$xpath')";
                 }
-            } elsif ($field eq 'issues') {
-                # Consider NULL as 0 for issues count
-                $column = 'COALESCE(issues,0)';
+            }
+            elsif ($field eq 'isbn') {
+                if ( C4::Context->preference("SearchWithISBNVariations") and $query ) {
+                    my @isbns = C4::Koha::GetVariationsOfISBN( $query );
+                    $query = [];
+                    push @$query, @isbns;
+                }
+                $column = $field;
+            }
+            elsif ($field eq 'issn') {
+                if ( C4::Context->preference("SearchWithISSNVariations") and $query ) {
+                    my @issns = C4::Koha::GetVariationsOfISSN( $query );
+                    $query = [];
+                    push @$query, @issns;
+                }
+                $column = $field;
             } else {
                 $column = $field;
             }
 
+            if ( defined $ifnull ) {
+                $column = "COALESCE($column, ?)";
+            }
+
             if (ref $query eq 'ARRAY') {
-                if ($op eq '=') {
-                    $op = 'IN';
-                } elsif ($op eq '!=') {
-                    $op = 'NOT IN';
+                if ($op eq 'like') {
+                    $where_fragment = {
+                        str => "($column LIKE " . join (" OR $column LIKE ", ('?') x @$query ) . ")",
+                        args => $query,
+                    };
+                }
+                else {
+                    if ($op eq '=') {
+                        $op = 'IN';
+                    } elsif ($op eq '!=') {
+                        $op = 'NOT IN';
+                    }
+                    $where_fragment = {
+                        str => "$column $op (" . join (',', ('?') x @$query) . ")",
+                        args => $query,
+                    };
                 }
+            } elsif ( $op eq 'is' ) {
                 $where_fragment = {
-                    str => "$column $op (" . join (',', ('?') x @$query) . ")",
-                    args => $query,
+                    str => "$column $op $query",
+                    args => [],
                 };
             } else {
                 $where_fragment = {
@@ -2168,6 +1089,10 @@ sub _SearchItems_build_where_fragment {
                     args => [ $query ],
                 };
             }
+
+            if ( defined $ifnull ) {
+                unshift @{ $where_fragment->{args} }, $ifnull;
+            }
         }
     }
 
@@ -2190,7 +1115,7 @@ A filter has the following keys:
 
 =item * query: the value to search in this column
 
-=item * operator: comparison operator. Can be one of = != > < >= <= like
+=item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
 
 =back
 
@@ -2249,6 +1174,7 @@ sub SearchItems {
           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
+          LEFT JOIN issues ON issues.itemnumber = items.itemnumber
           WHERE 1
     };
     if (defined $where_str and $where_str ne '') {
@@ -2261,10 +1187,17 @@ sub SearchItems {
     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;
-    my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
-        ? $params->{sortby} : 'itemnumber';
-    my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
-    $query .= qq{ ORDER BY $sortby $sortorder };
+    push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
+
+    if ( $params->{sortby} eq 'availability' ) {
+        my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
+        $query .= qq{ ORDER BY onloan $sortorder };
+    } else {
+        my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
+            ? $params->{sortby} : 'itemnumber';
+        my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
+        $query .= qq{ ORDER BY $sortby $sortorder };
+    }
 
     my $rows = $params->{rows};
     my @limit_args;
@@ -2328,10 +1261,12 @@ sub _find_value {
 
 =head2 PrepareItemrecordDisplay
 
-  PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
+  PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
 
 Returns a hash with all the fields for Display a given item data in a template
 
+$defaultvalues should either contain a hashref of values for the new item, or be undefined.
+
 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
 
 =cut
@@ -2349,6 +1284,12 @@ sub PrepareItemrecordDisplay {
     # its contents. See also GetMarcStructure.
     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
 
+    # Pick the default location from NewItemsDefaultLocation
+    if ( C4::Context->preference('NewItemsDefaultLocation') ) {
+        $defaultvalues //= {};
+        $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
+    }
+
     # return nothing if we don't have found an existing framework.
     return q{} unless $tagslib;
     my $itemrecord;
@@ -2375,52 +1316,73 @@ sub PrepareItemrecordDisplay {
 
             # 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" );
+            foreach my $subfield (
+                sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
+                grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
+                values %{ $tagslib->{$tag} } )
+            {
+                next unless ( $subfield->{'tab'} );
+                next if ( $subfield->{'tab'} ne "10" );
                 my %subfield_data;
                 $subfield_data{tag}           = $tag;
-                $subfield_data{subfield}      = $subfield;
+                $subfield_data{subfield}      = $subfield->{subfield};
                 $subfield_data{countsubfield} = $cntsubf++;
-                $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
-                $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
+                $subfield_data{kohafield}     = $subfield->{kohafield};
+                $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
 
                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
-                $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
-                $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
-                $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
+                $subfield_data{marc_lib}   = $subfield->{lib};
+                $subfield_data{mandatory}  = $subfield->{mandatory};
+                $subfield_data{repeatable} = $subfield->{repeatable};
                 $subfield_data{hidden}     = "display:none"
-                  if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
-                    || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
+                  if ( ( $subfield->{hidden} > 4 )
+                    || ( $subfield->{hidden} < -4 ) );
                 my ( $x, $defaultvalue );
                 if ($itemrecord) {
-                    ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
+                    ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
                 }
-                $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
+                $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
                 if ( !defined $defaultvalue ) {
                     $defaultvalue = q||;
                 } else {
                     $defaultvalue =~ s/"/&quot;/g;
+                    # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
+                    my $today_dt = dt_from_string;
+                    my $year     = $today_dt->strftime('%Y');
+                    my $shortyear     = $today_dt->strftime('%y');
+                    my $month    = $today_dt->strftime('%m');
+                    my $day      = $today_dt->strftime('%d');
+                    $defaultvalue =~ s/<<YYYY>>/$year/g;
+                    $defaultvalue =~ s/<<YY>>/$shortyear/g;
+                    $defaultvalue =~ s/<<MM>>/$month/g;
+                    $defaultvalue =~ s/<<DD>>/$day/g;
+
+                    # And <<USER>> with surname (?)
+                    my $username =
+                      (   C4::Context->userenv
+                        ? C4::Context->userenv->{'surname'}
+                        : "superlibrarian" );
+                    $defaultvalue =~ s/<<USER>>/$username/g;
                 }
 
-                my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
+                my $maxlength = $subfield->{maxlength};
 
                 # search for itemcallnumber if applicable
-                if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
+                if ( $subfield->{kohafield} eq 'items.itemcallnumber'
                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
                         next unless my $field = $itemrecord->field($CNtag);
                         my $CNsubfields = substr( $itemcn_pref, 3 );
+                        $CNsubfields = undef if $CNsubfields eq '';
                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
                         last if $defaultvalue;
                     }
                 }
-                if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
+                if (   $subfield->{kohafield} eq 'items.itemcallnumber'
                     && $defaultvalues
                     && $defaultvalues->{'callnumber'} ) {
-                    if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
+                    if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
                         # if the item record exists, only use default value if the item has no callnumber
                         $defaultvalue = $defaultvalues->{callnumber};
                     } elsif ( !$itemrecord and $defaultvalues ) {
@@ -2428,18 +1390,18 @@ sub PrepareItemrecordDisplay {
                         $defaultvalue = $defaultvalues->{callnumber};
                     }
                 }
-                if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
+                if (   ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
                     && $defaultvalues
                     && $defaultvalues->{'branchcode'} ) {
                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
                         $defaultvalue = $defaultvalues->{branchcode};
                     }
                 }
-                if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
+                if (   ( $subfield->{kohafield} eq 'items.location' )
                     && $defaultvalues
                     && $defaultvalues->{'location'} ) {
 
-                    if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
+                    if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
                         # if the item record exists, only use default value if the item has no locationr
                         $defaultvalue = $defaultvalues->{location};
                     } elsif ( !$itemrecord and $defaultvalues ) {
@@ -2447,19 +1409,28 @@ sub PrepareItemrecordDisplay {
                         $defaultvalue = $defaultvalues->{location};
                     }
                 }
-                if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
+                if (   ( $subfield->{kohafield} eq 'items.ccode' )
+                    && $defaultvalues
+                    && $defaultvalues->{'ccode'} ) {
+
+                    if ( !$itemrecord and $defaultvalues ) {
+                        # if the item record *doesn't* exists, always use the default value
+                        $defaultvalue = $defaultvalues->{ccode};
+                    }
+                }
+                if ( $subfield->{authorised_value} ) {
                     my @authorised_values;
                     my %authorised_lib;
 
                     # builds list, depending on authorised value...
                     #---- branch
-                    if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
+                    if ( $subfield->{'authorised_value'} eq "branches" ) {
                         if (   ( C4::Context->preference("IndependentBranches") )
                             && !C4::Context->IsSuperLibrarian() ) {
                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
                             $sth->execute( C4::Context->userenv->{branch} );
                             push @authorised_values, ""
-                              unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+                              unless ( $subfield->{mandatory} );
                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
                                 push @authorised_values, $branchcode;
                                 $authorised_lib{$branchcode} = $branchname;
@@ -2468,7 +1439,7 @@ sub PrepareItemrecordDisplay {
                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
                             $sth->execute;
                             push @authorised_values, ""
-                              unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+                              unless ( $subfield->{mandatory} );
                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
                                 push @authorised_values, $branchcode;
                                 $authorised_lib{$branchcode} = $branchname;
@@ -2481,10 +1452,9 @@ sub PrepareItemrecordDisplay {
                         }
 
                         #----- itemtypes
-                    } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
+                    } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
                         my $itemtypes = Koha::ItemTypes->search_with_localization;
-                        push @authorised_values, ""
-                          unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+                        push @authorised_values, "";
                         while ( my $itemtype = $itemtypes->next ) {
                             push @authorised_values, $itemtype->itemtype;
                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
@@ -2494,8 +1464,8 @@ sub PrepareItemrecordDisplay {
                         }
 
                         #---- class_sources
-                    } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
-                        push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+                    } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
+                        push @authorised_values, "";
 
                         my $class_sources = GetClassSources();
                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
@@ -2512,11 +1482,10 @@ sub PrepareItemrecordDisplay {
                         #---- "true" authorised value
                     } else {
                         $authorised_values_sth->execute(
-                            $tagslib->{$tag}->{$subfield}->{authorised_value},
+                            $subfield->{authorised_value},
                             $branch_limit ? $branch_limit : ()
                         );
-                        push @authorised_values, ""
-                          unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+                        push @authorised_values, "";
                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
                             push @authorised_values, $value;
                             $authorised_lib{$value} = $lib;
@@ -2528,17 +1497,17 @@ sub PrepareItemrecordDisplay {
                         default => $defaultvalue // q{},
                         labels  => \%authorised_lib,
                     };
-                } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
+                } elsif ( $subfield->{value_builder} ) {
                 # it is a plugin
                     require Koha::FrameworkPlugin;
                     my $plugin = Koha::FrameworkPlugin->new({
-                        name => $tagslib->{$tag}->{$subfield}->{value_builder},
+                        name => $subfield->{value_builder},
                         item_style => 1,
                     });
-                    my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
+                    my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
                     $plugin->build( $pars );
                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
-                        $defaultvalue = $field->subfield($subfield) || q{};
+                        $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
                     }
                     if( !$plugin->errstr ) {
                         #TODO Move html to template; see report 12176/13397
@@ -2554,12 +1523,12 @@ sub PrepareItemrecordDisplay {
                 elsif ( $tag eq '' ) {       # it's an hidden field
                     $subfield_data{marc_value} = qq(<input type="hidden" 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" ?
+                elsif ( $subfield->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
                     $subfield_data{marc_value} = qq(<input type="text" 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
-                                  300 <= $tag && $tag < 400 && $subfield eq 'a' )
+                                  300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
                                   500 <= $tag && $tag < 600                     )
                           ) {
@@ -2596,6 +1565,9 @@ sub ToggleNewStatus {
     my $report;
     for my $rule ( @rules ) {
         my $age = $rule->{age};
+        # Default to using items.dateaccessioned if there's an old item modification rule
+        # missing an agefield value
+        my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
         my $conditions = $rule->{conditions};
         my $substitutions = $rule->{substitutions};
         foreach ( @$substitutions ) {
@@ -2610,6 +1582,7 @@ sub ToggleNewStatus {
             WHERE 1
         |;
         for my $condition ( @$conditions ) {
+            next unless $condition->{field};
             if (
                  grep { $_ eq $condition->{field} } @item_columns
               or grep { $_ eq $condition->{field} } @biblioitem_columns
@@ -2627,7 +1600,7 @@ sub ToggleNewStatus {
             }
         }
         if ( defined $age ) {
-            $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
+            $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
             push @params, $age;
         }
         my $sth = $dbh->prepare($query);
@@ -2635,51 +1608,20 @@ sub ToggleNewStatus {
         while ( my $values = $sth->fetchrow_hashref ) {
             my $biblionumber = $values->{biblionumber};
             my $itemnumber = $values->{itemnumber};
+            my $item = Koha::Items->find($itemnumber);
             for my $substitution ( @$substitutions ) {
+                my $field = $substitution->{item_field};
+                my $value = $substitution->{value};
                 next unless $substitution->{field};
                 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;
+                $item->$field($value);
                 push @{ $report->{$itemnumber} }, $substitution;
             }
+            $item->store unless $report_only;
         }
     }
 
     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;