Bug 17600: Standardize our EXPORT_OK
[srvgit] / Koha / Item.pm
index 1d18d34..c653dc2 100644 (file)
@@ -4,35 +4,43 @@ package Koha::Item;
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 3 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 use Modern::Perl;
 
-use Carp;
-use List::MoreUtils qw(any);
+use List::MoreUtils qw( any );
+use Data::Dumper qw( Dumper );
 
 use Koha::Database;
 use Koha::DateUtils qw( dt_from_string );
 
 use C4::Context;
-use C4::Circulation;
+use C4::Circulation qw( GetBranchItemRule );
 use C4::Reserves;
+use C4::ClassSource qw( GetClassSort );
+use C4::Log qw( logaction );
+
 use Koha::Checkouts;
-use Koha::IssuingRules;
+use Koha::CirculationRules;
+use Koha::CoverImages;
+use Koha::SearchEngine::Indexer;
+use Koha::Exceptions::Item::Transfer;
 use Koha::Item::Transfer::Limits;
 use Koha::Item::Transfers;
+use Koha::ItemTypes;
 use Koha::Patrons;
+use Koha::Plugins;
 use Koha::Libraries;
 use Koha::StockRotationItem;
 use Koha::StockRotationRotas;
@@ -49,6 +57,263 @@ Koha::Item - Koha Item object class
 
 =cut
 
+=head3 store
+
+    $item->store;
+
+$params can take an optional 'skip_record_index' parameter.
+If set, the reindexation process will not happen (index_records not called)
+
+NOTE: This is a temporary fix to answer a performance issue when lot of items
+are added (or modified) at the same time.
+The correct way to fix this is to make the ES reindexation process async.
+You should not turn it on if you do not understand what it is doing exactly.
+
+=cut
+
+sub store {
+    my $self = shift;
+    my $params = @_ ? shift : {};
+
+    my $log_action = $params->{log_action} // 1;
+
+    # We do not want to oblige callers to pass this value
+    # Dev conveniences vs performance?
+    unless ( $self->biblioitemnumber ) {
+        $self->biblioitemnumber( $self->biblio->biblioitem->biblioitemnumber );
+    }
+
+    # See related changes from C4::Items::AddItem
+    unless ( $self->itype ) {
+        $self->itype($self->biblio->biblioitem->itemtype);
+    }
+
+    my $today  = dt_from_string;
+    my $action = 'create';
+
+    unless ( $self->in_storage ) { #AddItem
+
+        unless ( $self->permanent_location ) {
+            $self->permanent_location($self->location);
+        }
+
+        my $default_location = C4::Context->preference('NewItemsDefaultLocation');
+        unless ( $self->location || !$default_location ) {
+            $self->permanent_location( $self->location || $default_location )
+              unless $self->permanent_location;
+            $self->location($default_location);
+        }
+
+        unless ( $self->replacementpricedate ) {
+            $self->replacementpricedate($today);
+        }
+        unless ( $self->datelastseen ) {
+            $self->datelastseen($today);
+        }
+
+        unless ( $self->dateaccessioned ) {
+            $self->dateaccessioned($today);
+        }
+
+        if (   $self->itemcallnumber
+            or $self->cn_source )
+        {
+            my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
+            $self->cn_sort($cn_sort);
+        }
+
+    } else { # ModItem
+
+        $action = 'modify';
+
+        my %updated_columns = $self->_result->get_dirty_columns;
+        return $self->SUPER::store unless %updated_columns;
+
+        # Retrieve the item for comparison if we need to
+        my $pre_mod_item = (
+                 exists $updated_columns{itemlost}
+              or exists $updated_columns{withdrawn}
+              or exists $updated_columns{damaged}
+        ) ? $self->get_from_storage : undef;
+
+        # Update *_on  fields if needed
+        # FIXME: Why not for AddItem as well?
+        my @fields = qw( itemlost withdrawn damaged );
+        for my $field (@fields) {
+
+            # If the field is defined but empty or 0, we are
+            # removing/unsetting and thus need to clear out
+            # the 'on' field
+            if (   exists $updated_columns{$field}
+                && defined( $self->$field )
+                && !$self->$field )
+            {
+                my $field_on = "${field}_on";
+                $self->$field_on(undef);
+            }
+            # If the field has changed otherwise, we much update
+            # the 'on' field
+            elsif (exists $updated_columns{$field}
+                && $updated_columns{$field}
+                && !$pre_mod_item->$field )
+            {
+                my $field_on = "${field}_on";
+                $self->$field_on(
+                    DateTime::Format::MySQL->format_datetime(
+                        dt_from_string()
+                    )
+                );
+            }
+        }
+
+        if (   exists $updated_columns{itemcallnumber}
+            or exists $updated_columns{cn_source} )
+        {
+            my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
+            $self->cn_sort($cn_sort);
+        }
+
+
+        if (    exists $updated_columns{location}
+            and $self->location ne 'CART'
+            and $self->location ne 'PROC'
+            and not exists $updated_columns{permanent_location} )
+        {
+            $self->permanent_location( $self->location );
+        }
+
+        # If item was lost and has now been found,
+        # reverse any list item charges if necessary.
+        if (    exists $updated_columns{itemlost}
+            and $updated_columns{itemlost} <= 0
+            and $pre_mod_item->itemlost > 0 )
+        {
+            $self->_set_found_trigger($pre_mod_item);
+        }
+
+    }
+
+    unless ( $self->dateaccessioned ) {
+        $self->dateaccessioned($today);
+    }
+
+    my $result = $self->SUPER::store;
+    if ( $log_action && C4::Context->preference("CataloguingLog") ) {
+        $action eq 'create'
+          ? logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
+          : logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper( $self->unblessed ) );
+    }
+    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+    $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
+        unless $params->{skip_record_index};
+    $self->get_from_storage->_after_item_action_hooks({ action => $action });
+
+    return $result;
+}
+
+=head3 delete
+
+=cut
+
+sub delete {
+    my $self = shift;
+    my $params = @_ ? shift : {};
+
+    # FIXME check the item has no current issues
+    # i.e. raise the appropriate exception
+
+    my $result = $self->SUPER::delete;
+
+    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+    $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
+        unless $params->{skip_record_index};
+
+    $self->_after_item_action_hooks({ action => 'delete' });
+
+    logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
+      if C4::Context->preference("CataloguingLog");
+
+    return $result;
+}
+
+=head3 safe_delete
+
+=cut
+
+sub safe_delete {
+    my $self = shift;
+    my $params = @_ ? shift : {};
+
+    my $safe_to_delete = $self->safe_to_delete;
+    return $safe_to_delete unless $safe_to_delete eq '1';
+
+    $self->move_to_deleted;
+
+    return $self->delete($params);
+}
+
+=head3 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.
+
+"last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
+
+=cut
+
+sub safe_to_delete {
+    my ($self) = @_;
+
+    return "book_on_loan" if $self->checkout;
+
+    return "not_same_branch"
+      if defined C4::Context->userenv
+      and !C4::Context->IsSuperLibrarian()
+      and C4::Context->preference("IndependentBranches")
+      and ( C4::Context->userenv->{branch} ne $self->homebranch );
+
+    # check it doesn't have a waiting reserve
+    return "book_reserved"
+      if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
+
+    return "linked_analytics"
+      if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
+
+    return "last_item_for_hold"
+      if $self->biblio->items->count == 1
+      && $self->biblio->holds->search(
+          {
+              itemnumber => undef,
+          }
+        )->count;
+
+    return 1;
+}
+
+=head3 move_to_deleted
+
+my $is_moved = $item->move_to_deleted;
+
+Move an item to the deleteditems table.
+This can be done before deleting an item, to make sure the data are not completely deleted.
+
+=cut
+
+sub move_to_deleted {
+    my ($self) = @_;
+    my $item_infos = $self->unblessed;
+    delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
+    return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
+}
+
+
 =head3 effective_itemtype
 
 Returns the itemtype for the item based on whether item level itemtypes are set or not.
@@ -144,19 +409,131 @@ sub holds {
     return Koha::Holds->_new_from_dbic( $holds_rs );
 }
 
+=head3 request_transfer
+
+  my $transfer = $item->request_transfer(
+    {
+        to     => $to_library,
+        reason => $reason,
+        [ ignore_limits => 0, enqueue => 1, replace => 1 ]
+    }
+  );
+
+Add a transfer request for this item to the given branch for the given reason.
+
+An exception will be thrown if the BranchTransferLimits would prevent the requested
+transfer, unless 'ignore_limits' is passed to override the limits.
+
+An exception will be thrown if an active transfer (i.e pending arrival date) is found;
+The caller should catch such cases and retry the transfer request as appropriate passing
+an appropriate override.
+
+Overrides
+* enqueue - Used to queue up the transfer when the existing transfer is found to be in transit.
+* replace - Used to replace the existing transfer request with your own.
+
+=cut
+
+sub request_transfer {
+    my ( $self, $params ) = @_;
+
+    # check for mandatory params
+    my @mandatory = ( 'to', 'reason' );
+    for my $param (@mandatory) {
+        unless ( defined( $params->{$param} ) ) {
+            Koha::Exceptions::MissingParameter->throw(
+                error => "The $param parameter is mandatory" );
+        }
+    }
+
+    Koha::Exceptions::Item::Transfer::Limit->throw()
+      unless ( $params->{ignore_limits}
+        || $self->can_be_transferred( { to => $params->{to} } ) );
+
+    my $request = $self->get_transfer;
+    Koha::Exceptions::Item::Transfer::InQueue->throw( transfer => $request )
+      if ( $request && !$params->{enqueue} && !$params->{replace} );
+
+    $request->cancel( { reason => $params->{reason}, force => 1 } )
+      if ( defined($request) && $params->{replace} );
+
+    my $transfer = Koha::Item::Transfer->new(
+        {
+            itemnumber    => $self->itemnumber,
+            daterequested => dt_from_string,
+            frombranch    => $self->holdingbranch,
+            tobranch      => $params->{to}->branchcode,
+            reason        => $params->{reason},
+            comments      => $params->{comment}
+        }
+    )->store();
+
+    return $transfer;
+}
+
 =head3 get_transfer
 
-my $transfer = $item->get_transfer;
+  my $transfer = $item->get_transfer;
+
+Return the active transfer request or undef
+
+Note: Transfers are retrieved in a Modified FIFO (First In First Out) order
+whereby the most recently sent, but not received, transfer will be returned
+if it exists, otherwise the oldest unsatisfied transfer will be returned.
 
-Return the transfer if the item is in transit or undef
+This allows for transfers to queue, which is the case for stock rotation and
+rotating collections where a manual transfer may need to take precedence but
+we still expect the item to end up at a final location eventually.
 
 =cut
 
 sub get_transfer {
-    my ( $self ) = @_;
-    my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
+    my ($self) = @_;
+    my $transfer_rs = $self->_result->branchtransfers->search(
+        {
+            datearrived   => undef,
+            datecancelled => undef
+        },
+        {
+            order_by =>
+              [ { -desc => 'datesent' }, { -asc => 'daterequested' } ],
+            rows => 1
+        }
+    )->first;
     return unless $transfer_rs;
-    return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
+    return Koha::Item::Transfer->_new_from_dbic($transfer_rs);
+}
+
+=head3 get_transfers
+
+  my $transfer = $item->get_transfers;
+
+Return the list of outstanding transfers (i.e requested but not yet cancelled
+or received).
+
+Note: Transfers are retrieved in a Modified FIFO (First In First Out) order
+whereby the most recently sent, but not received, transfer will be returned
+first if it exists, otherwise requests are in oldest to newest request order.
+
+This allows for transfers to queue, which is the case for stock rotation and
+rotating collections where a manual transfer may need to take precedence but
+we still expect the item to end up at a final location eventually.
+
+=cut
+
+sub get_transfers {
+    my ($self) = @_;
+    my $transfer_rs = $self->_result->branchtransfers->search(
+        {
+            datearrived   => undef,
+            datecancelled => undef
+        },
+        {
+            order_by =>
+              [ { -desc => 'datesent' }, { -asc => 'daterequested' } ],
+        }
+    );
+    return Koha::Item::Transfers->_new_from_dbic($transfer_rs);
 }
 
 =head3 last_returned_by
@@ -293,11 +670,12 @@ sub can_be_transferred {
         $limittype => $limittype eq 'itemtype'
                         ? $self->effective_itemtype : $self->ccode
     })->count ? 0 : 1;
+
 }
 
 =head3 pickup_locations
 
-@pickup_locations = $item->pickup_locations( {patron => $patron } )
+$pickup_locations = $item->pickup_locations( {patron => $patron } )
 
 Returns possible pickup locations for this item, according to patron's home library (if patron is defined and holds are allowed only from hold groups)
 and if item can be transferred to each pickup location.
@@ -314,39 +692,59 @@ sub pickup_locations {
     my $branchitemrule =
       C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
 
-    my @libs;
     if(defined $patron) {
-        return @libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
-        return @libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
+        return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} eq 'from_local_hold_group' && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
+        return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} eq 'from_home_library' && $self->home_branch->branchcode ne $patron->branchcode;
     }
 
+    my $pickup_libraries = Koha::Libraries->search();
     if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
-        @libs  = $self->home_branch->get_hold_libraries;
-        push @libs, $self->home_branch unless scalar(@libs) > 0;
+        $pickup_libraries = $self->home_branch->get_hold_libraries;
     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
         my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
-        @libs  = $plib->get_hold_libraries;
-        push @libs, $self->home_branch unless scalar(@libs) > 0;
+        $pickup_libraries = $plib->get_hold_libraries;
     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
-        push @libs, $self->home_branch;
+        $pickup_libraries = Koha::Libraries->search({ branchcode => $self->homebranch });
     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
-        push @libs, $self->holding_branch;
-    } else {
-        @libs = Koha::Libraries->search({
+        $pickup_libraries = Koha::Libraries->search({ branchcode => $self->holdingbranch });
+    };
+
+    return $pickup_libraries->search(
+        {
             pickup_location => 1
-        }, {
+        },
+        {
             order_by => ['branchname']
-        })->as_list;
-    }
-
-    my @pickup_locations;
-    foreach my $library (@libs) {
-        if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
-            push @pickup_locations, $library->unblessed;
         }
-    }
+    ) unless C4::Context->preference('UseBranchTransferLimits');
 
-    return wantarray ? @pickup_locations : \@pickup_locations;
+    my $limittype = C4::Context->preference('BranchTransferLimitsType');
+    my ($ccode, $itype) = (undef, undef);
+    if( $limittype eq 'ccode' ){
+        $ccode = $self->ccode;
+    } else {
+        $itype = $self->itype;
+    }
+    my $limits = Koha::Item::Transfer::Limits->search(
+        {
+            fromBranch => $self->holdingbranch,
+            ccode      => $ccode,
+            itemtype   => $itype,
+        },
+        { columns => ['toBranch'] }
+    );
+
+    return $pickup_libraries->search(
+        {
+            pickup_location => 1,
+            branchcode      => {
+                '-not_in' => $limits->_resultset->as_query
+            }
+        },
+        {
+            order_by => ['branchname']
+        }
+    );
 }
 
 =head3 article_request_type
@@ -369,10 +767,17 @@ sub article_request_type {
       :                                      undef;
     my $borrowertype = $borrower->categorycode;
     my $itemtype = $self->effective_itemtype();
-    my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $borrowertype, itemtype => $itemtype, branchcode => $branchcode });
+    my $rule = Koha::CirculationRules->get_effective_rule(
+        {
+            rule_name    => 'article_requests',
+            categorycode => $borrowertype,
+            itemtype     => $itemtype,
+            branchcode   => $branchcode
+        }
+    );
 
-    return q{} unless $issuing_rule;
-    return $issuing_rule->article_requests || q{}
+    return q{} unless $rule;
+    return $rule->rule_value || q{}
 }
 
 =head3 current_holds
@@ -467,7 +872,8 @@ sub as_marc_field {
         next if !$tagfield; # TODO: Should we raise an exception instead?
                             # Feels like safe fallback is better
 
-        push @subfields, $tagsubfield => $self->$item_field;
+        push @subfields, $tagsubfield => $self->$item_field
+            if defined $self->$item_field and $item_field ne '';
     }
 
     my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
@@ -483,6 +889,221 @@ sub as_marc_field {
     return $field;
 }
 
+=head3 renewal_branchcode
+
+Returns the branchcode to be recorded in statistics renewal of the item
+
+=cut
+
+sub renewal_branchcode {
+
+    my ($self, $params ) = @_;
+
+    my $interface = C4::Context->interface;
+    my $branchcode;
+    if ( $interface eq 'opac' ){
+        my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
+        if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
+            $branchcode = 'OPACRenew';
+        }
+        elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
+            $branchcode = $self->homebranch;
+        }
+        elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
+            $branchcode = $self->checkout->patron->branchcode;
+        }
+        elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
+            $branchcode = $self->checkout->branchcode;
+        }
+        else {
+            $branchcode = "";
+        }
+    } else {
+        $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
+            ? C4::Context->userenv->{branch} : $params->{branch};
+    }
+    return $branchcode;
+}
+
+=head3 cover_images
+
+Return the cover images associated with this item.
+
+=cut
+
+sub cover_images {
+    my ( $self ) = @_;
+
+    my $cover_image_rs = $self->_result->cover_images;
+    return unless $cover_image_rs;
+    return Koha::CoverImages->_new_from_dbic($cover_image_rs);
+}
+
+=head3 _set_found_trigger
+
+    $self->_set_found_trigger
+
+Finds the most recent lost item charge for this item and refunds the patron
+appropriately, taking into account any payments or writeoffs already applied
+against the charge.
+
+Internal function, not exported, called only by Koha::Item->store.
+
+=cut
+
+sub _set_found_trigger {
+    my ( $self, $pre_mod_item ) = @_;
+
+    ## If item was lost, it has now been found, reverse any list item charges if necessary.
+    my $no_refund_after_days =
+      C4::Context->preference('NoRefundOnLostReturnedItemsAge');
+    if ($no_refund_after_days) {
+        my $today = dt_from_string();
+        my $lost_age_in_days =
+          dt_from_string( $pre_mod_item->itemlost_on )->delta_days($today)
+          ->in_units('days');
+
+        return $self unless $lost_age_in_days < $no_refund_after_days;
+    }
+
+    my $lostreturn_policy = Koha::CirculationRules->get_lostreturn_policy(
+        {
+            item          => $self,
+            return_branch => C4::Context->userenv
+            ? C4::Context->userenv->{'branch'}
+            : undef,
+        }
+      );
+
+    if ( $lostreturn_policy ) {
+
+        # refund charge made for lost book
+        my $lost_charge = Koha::Account::Lines->search(
+            {
+                itemnumber      => $self->itemnumber,
+                debit_type_code => 'LOST',
+                status          => [ undef, { '<>' => 'FOUND' } ]
+            },
+            {
+                order_by => { -desc => [ 'date', 'accountlines_id' ] },
+                rows     => 1
+            }
+        )->single;
+
+        if ( $lost_charge ) {
+
+            my $patron = $lost_charge->patron;
+            if ( $patron ) {
+
+                my $account = $patron->account;
+                my $total_to_refund = 0;
+
+                # Use cases
+                if ( $lost_charge->amount > $lost_charge->amountoutstanding ) {
+
+                    # some amount has been cancelled. collect the offsets that are not writeoffs
+                    # this works because the only way to subtract from this kind of a debt is
+                    # using the UI buttons 'Pay' and 'Write off'
+                    my $credits_offsets = Koha::Account::Offsets->search(
+                        {
+                            debit_id  => $lost_charge->id,
+                            credit_id => { '!=' => undef },     # it is not the debit itself
+                            type      => { '!=' => 'Writeoff' },
+                            amount    => { '<' => 0 }    # credits are negative on the DB
+                        }
+                    );
+
+                    $total_to_refund = ( $credits_offsets->count > 0 )
+                      ? $credits_offsets->total * -1    # credits are negative on the DB
+                      : 0;
+                }
+
+                my $credit_total = $lost_charge->amountoutstanding + $total_to_refund;
+
+                my $credit;
+                if ( $credit_total > 0 ) {
+                    my $branchcode =
+                      C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
+                    $credit = $account->add_credit(
+                        {
+                            amount      => $credit_total,
+                            description => 'Item found ' . $self->itemnumber,
+                            type        => 'LOST_FOUND',
+                            interface   => C4::Context->interface,
+                            library_id  => $branchcode,
+                            item_id     => $self->itemnumber,
+                            issue_id    => $lost_charge->issue_id
+                        }
+                    );
+
+                    $credit->apply( { debits => [$lost_charge] } );
+                    $self->{_refunded} = 1;
+                }
+
+                # Update the account status
+                $lost_charge->status('FOUND');
+                $lost_charge->store();
+
+                # Reconcile balances if required
+                if ( C4::Context->preference('AccountAutoReconcile') ) {
+                    $account->reconcile_balance;
+                }
+            }
+        }
+
+        # restore fine for lost book
+        if ( $lostreturn_policy eq 'restore' ) {
+            my $lost_overdue = Koha::Account::Lines->search(
+                {
+                    itemnumber      => $self->itemnumber,
+                    debit_type_code => 'OVERDUE',
+                    status          => 'LOST'
+                },
+                {
+                    order_by => { '-desc' => 'date' },
+                    rows     => 1
+                }
+            )->single;
+
+            if ( $lost_overdue ) {
+
+                my $patron = $lost_overdue->patron;
+                if ($patron) {
+                    my $account = $patron->account;
+
+                    # Update status of fine
+                    $lost_overdue->status('FOUND')->store();
+
+                    # Find related forgive credit
+                    my $refund = $lost_overdue->credits(
+                        {
+                            credit_type_code => 'FORGIVEN',
+                            itemnumber       => $self->itemnumber,
+                            status           => [ { '!=' => 'VOID' }, undef ]
+                        },
+                        { order_by => { '-desc' => 'date' }, rows => 1 }
+                    )->single;
+
+                    if ( $refund ) {
+                        # Revert the forgive credit
+                        $refund->void({ interface => 'trigger' });
+                        $self->{_restored} = 1;
+                    }
+
+                    # Reconcile balances if required
+                    if ( C4::Context->preference('AccountAutoReconcile') ) {
+                        $account->reconcile_balance;
+                    }
+                }
+            }
+        } elsif ( $lostreturn_policy eq 'charge' ) {
+            $self->{_charge} = 1;
+        }
+    }
+
+    return $self;
+}
+
 =head3 to_api_mapping
 
 This method returns the mapping for representing a Koha::Item object
@@ -521,7 +1142,6 @@ sub to_api_mapping {
         itemnotes                => 'public_notes',
         itemnotes_nonpublic      => 'internal_notes',
         holdingbranch            => 'holding_library_id',
-        paidfor                  => undef,
         timestamp                => 'timestamp',
         location                 => 'location',
         permanent_location       => 'permanent_location',
@@ -540,8 +1160,42 @@ sub to_api_mapping {
     };
 }
 
+=head3 itemtype
+
+    my $itemtype = $item->itemtype;
+
+    Returns Koha object for effective itemtype
+
+=cut
+
+sub itemtype {
+    my ( $self ) = @_;
+    return Koha::ItemTypes->find( $self->effective_itemtype );
+}
+
 =head2 Internal methods
 
+=head3 _after_item_action_hooks
+
+Helper method that takes care of calling all plugin hooks
+
+=cut
+
+sub _after_item_action_hooks {
+    my ( $self, $params ) = @_;
+
+    my $action = $params->{action};
+
+    Koha::Plugins->call(
+        'after_item_action',
+        {
+            action  => $action,
+            item    => $self,
+            item_id => $self->itemnumber,
+        }
+    );
+}
+
 =head3 _type
 
 =cut