3 # Copyright ByWater Solutions 2014
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use List::MoreUtils qw(any);
28 use Koha::DateUtils qw( dt_from_string );
33 use C4::ClassSource; # FIXME We would like to avoid that
34 use C4::Log qw( logaction );
37 use Koha::CirculationRules;
38 use Koha::CoverImages;
39 use Koha::SearchEngine::Indexer;
40 use Koha::Exceptions::Item::Transfer;
41 use Koha::Item::Transfer::Limits;
42 use Koha::Item::Transfers;
47 use Koha::StockRotationItem;
48 use Koha::StockRotationRotas;
50 use base qw(Koha::Object);
54 Koha::Item - Koha Item object class
66 $params can take an optional 'skip_record_index' parameter.
67 If set, the reindexation process will not happen (index_records not called)
69 NOTE: This is a temporary fix to answer a performance issue when lot of items
70 are added (or modified) at the same time.
71 The correct way to fix this is to make the ES reindexation process async.
72 You should not turn it on if you do not understand what it is doing exactly.
78 my $params = @_ ? shift : {};
80 my $log_action = $params->{log_action} // 1;
82 # We do not want to oblige callers to pass this value
83 # Dev conveniences vs performance?
84 unless ( $self->biblioitemnumber ) {
85 $self->biblioitemnumber( $self->biblio->biblioitem->biblioitemnumber );
88 # See related changes from C4::Items::AddItem
89 unless ( $self->itype ) {
90 $self->itype($self->biblio->biblioitem->itemtype);
93 my $today = dt_from_string;
94 my $action = 'create';
96 unless ( $self->in_storage ) { #AddItem
97 unless ( $self->permanent_location ) {
98 $self->permanent_location($self->location);
100 unless ( $self->replacementpricedate ) {
101 $self->replacementpricedate($today);
103 unless ( $self->datelastseen ) {
104 $self->datelastseen($today);
107 unless ( $self->dateaccessioned ) {
108 $self->dateaccessioned($today);
111 if ( $self->itemcallnumber
112 or $self->cn_source )
114 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
115 $self->cn_sort($cn_sort);
122 my %updated_columns = $self->_result->get_dirty_columns;
123 return $self->SUPER::store unless %updated_columns;
125 # Retrieve the item for comparison if we need to
127 exists $updated_columns{itemlost}
128 or exists $updated_columns{withdrawn}
129 or exists $updated_columns{damaged}
130 ) ? $self->get_from_storage : undef;
132 # Update *_on fields if needed
133 # FIXME: Why not for AddItem as well?
134 my @fields = qw( itemlost withdrawn damaged );
135 for my $field (@fields) {
137 # If the field is defined but empty or 0, we are
138 # removing/unsetting and thus need to clear out
140 if ( exists $updated_columns{$field}
141 && defined( $self->$field )
144 my $field_on = "${field}_on";
145 $self->$field_on(undef);
147 # If the field has changed otherwise, we much update
149 elsif (exists $updated_columns{$field}
150 && $updated_columns{$field}
151 && !$pre_mod_item->$field )
153 my $field_on = "${field}_on";
155 DateTime::Format::MySQL->format_datetime(
162 if ( exists $updated_columns{itemcallnumber}
163 or exists $updated_columns{cn_source} )
165 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
166 $self->cn_sort($cn_sort);
170 if ( exists $updated_columns{location}
171 and $self->location ne 'CART'
172 and $self->location ne 'PROC'
173 and not exists $updated_columns{permanent_location} )
175 $self->permanent_location( $self->location );
178 # If item was lost and has now been found,
179 # reverse any list item charges if necessary.
180 if ( exists $updated_columns{itemlost}
181 and $updated_columns{itemlost} <= 0
182 and $pre_mod_item->itemlost > 0 )
184 $self->_set_found_trigger($pre_mod_item);
189 unless ( $self->dateaccessioned ) {
190 $self->dateaccessioned($today);
193 my $result = $self->SUPER::store;
194 if ( $log_action && C4::Context->preference("CataloguingLog") ) {
196 ? logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
197 : logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper( $self->unblessed ) );
199 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
200 $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
201 unless $params->{skip_record_index};
202 $self->get_from_storage->_after_item_action_hooks({ action => $action });
213 my $params = @_ ? shift : {};
215 # FIXME check the item has no current issues
216 # i.e. raise the appropriate exception
218 my $result = $self->SUPER::delete;
220 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
221 $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
222 unless $params->{skip_record_index};
224 $self->_after_item_action_hooks({ action => 'delete' });
226 logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
227 if C4::Context->preference("CataloguingLog");
238 my $params = @_ ? shift : {};
240 my $safe_to_delete = $self->safe_to_delete;
241 return $safe_to_delete unless $safe_to_delete eq '1';
243 $self->move_to_deleted;
245 return $self->delete($params);
248 =head3 safe_to_delete
250 returns 1 if the item is safe to delete,
252 "book_on_loan" if the item is checked out,
254 "not_same_branch" if the item is blocked by independent branches,
256 "book_reserved" if the there are holds aganst the item, or
258 "linked_analytics" if the item has linked analytic records.
260 "last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
267 return "book_on_loan" if $self->checkout;
269 return "not_same_branch"
270 if defined C4::Context->userenv
271 and !C4::Context->IsSuperLibrarian()
272 and C4::Context->preference("IndependentBranches")
273 and ( C4::Context->userenv->{branch} ne $self->homebranch );
275 # check it doesn't have a waiting reserve
276 return "book_reserved"
277 if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
279 return "linked_analytics"
280 if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
282 return "last_item_for_hold"
283 if $self->biblio->items->count == 1
284 && $self->biblio->holds->search(
293 =head3 move_to_deleted
295 my $is_moved = $item->move_to_deleted;
297 Move an item to the deleteditems table.
298 This can be done before deleting an item, to make sure the data are not completely deleted.
302 sub move_to_deleted {
304 my $item_infos = $self->unblessed;
305 delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
306 return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
310 =head3 effective_itemtype
312 Returns the itemtype for the item based on whether item level itemtypes are set or not.
316 sub effective_itemtype {
319 return $self->_result()->effective_itemtype();
329 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
331 return $self->{_home_branch};
334 =head3 holding_branch
341 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
343 return $self->{_holding_branch};
348 my $biblio = $item->biblio;
350 Return the bibliographic record of this item
356 my $biblio_rs = $self->_result->biblio;
357 return Koha::Biblio->_new_from_dbic( $biblio_rs );
362 my $biblioitem = $item->biblioitem;
364 Return the biblioitem record of this item
370 my $biblioitem_rs = $self->_result->biblioitem;
371 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
376 my $checkout = $item->checkout;
378 Return the checkout for this item
384 my $checkout_rs = $self->_result->issue;
385 return unless $checkout_rs;
386 return Koha::Checkout->_new_from_dbic( $checkout_rs );
391 my $holds = $item->holds();
392 my $holds = $item->holds($params);
393 my $holds = $item->holds({ found => 'W'});
395 Return holds attached to an item, optionally accept a hashref of params to pass to search
400 my ( $self,$params ) = @_;
401 my $holds_rs = $self->_result->reserves->search($params);
402 return Koha::Holds->_new_from_dbic( $holds_rs );
405 =head3 request_transfer
407 my $transfer = $item->request_transfer(
411 [ ignore_limits => 0, enqueue => 1, replace => 1 ]
415 Add a transfer request for this item to the given branch for the given reason.
417 An exception will be thrown if the BranchTransferLimits would prevent the requested
418 transfer, unless 'ignore_limits' is passed to override the limits.
420 An exception will be thrown if an active transfer (i.e pending arrival date) is found;
421 The caller should catch such cases and retry the transfer request as appropriate passing
422 an appropriate override.
425 * enqueue - Used to queue up the transfer when the existing transfer is found to be in transit.
426 * replace - Used to replace the existing transfer request with your own.
430 sub request_transfer {
431 my ( $self, $params ) = @_;
433 # check for mandatory params
434 my @mandatory = ( 'to', 'reason' );
435 for my $param (@mandatory) {
436 unless ( defined( $params->{$param} ) ) {
437 Koha::Exceptions::MissingParameter->throw(
438 error => "The $param parameter is mandatory" );
442 Koha::Exceptions::Item::Transfer::Limit->throw()
443 unless ( $params->{ignore_limits}
444 || $self->can_be_transferred( { to => $params->{to} } ) );
446 my $request = $self->get_transfer;
447 Koha::Exceptions::Item::Transfer::InQueue->throw( transfer => $request )
448 if ( $request && !$params->{enqueue} && !$params->{replace} );
450 $request->cancel( { reason => $params->{reason}, force => 1 } )
451 if ( defined($request) && $params->{replace} );
453 my $transfer = Koha::Item::Transfer->new(
455 itemnumber => $self->itemnumber,
456 daterequested => dt_from_string,
457 frombranch => $self->holdingbranch,
458 tobranch => $params->{to}->branchcode,
459 reason => $params->{reason},
460 comments => $params->{comment}
469 my $transfer = $item->get_transfer;
471 Return the active transfer request or undef
473 Note: Transfers are retrieved in a Modified FIFO (First In First Out) order
474 whereby the most recently sent, but not received, transfer will be returned
475 if it exists, otherwise the oldest unsatisfied transfer will be returned.
477 This allows for transfers to queue, which is the case for stock rotation and
478 rotating collections where a manual transfer may need to take precedence but
479 we still expect the item to end up at a final location eventually.
485 my $transfer_rs = $self->_result->branchtransfers->search(
487 datearrived => undef,
488 datecancelled => undef
492 [ { -desc => 'datesent' }, { -asc => 'daterequested' } ],
496 return unless $transfer_rs;
497 return Koha::Item::Transfer->_new_from_dbic($transfer_rs);
500 =head3 last_returned_by
502 Gets and sets the last borrower to return an item.
504 Accepts and returns Koha::Patron objects
506 $item->last_returned_by( $borrowernumber );
508 $last_returned_by = $item->last_returned_by();
512 sub last_returned_by {
513 my ( $self, $borrower ) = @_;
515 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
518 return $items_last_returned_by_rs->update_or_create(
519 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
522 unless ( $self->{_last_returned_by} ) {
523 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
525 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
529 return $self->{_last_returned_by};
533 =head3 can_article_request
535 my $bool = $item->can_article_request( $borrower )
537 Returns true if item can be specifically requested
539 $borrower must be a Koha::Patron object
543 sub can_article_request {
544 my ( $self, $borrower ) = @_;
546 my $rule = $self->article_request_type($borrower);
548 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
552 =head3 hidden_in_opac
554 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
556 Returns true if item fields match the hidding criteria defined in $rules.
557 Returns false otherwise.
559 Takes HASHref that can have the following parameters:
561 $rules : { <field> => [ value_1, ... ], ... }
563 Note: $rules inherits its structure from the parsed YAML from reading
564 the I<OpacHiddenItems> system preference.
569 my ( $self, $params ) = @_;
571 my $rules = $params->{rules} // {};
574 if C4::Context->preference('hidelostitems') and
577 my $hidden_in_opac = 0;
579 foreach my $field ( keys %{$rules} ) {
581 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
587 return $hidden_in_opac;
590 =head3 can_be_transferred
592 $item->can_be_transferred({ to => $to_library, from => $from_library })
593 Checks if an item can be transferred to given library.
595 This feature is controlled by two system preferences:
596 UseBranchTransferLimits to enable / disable the feature
597 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
598 for setting the limitations
600 Takes HASHref that can have the following parameters:
601 MANDATORY PARAMETERS:
604 $from : Koha::Library # if not given, item holdingbranch
605 # will be used instead
607 Returns 1 if item can be transferred to $to_library, otherwise 0.
609 To find out whether at least one item of a Koha::Biblio can be transferred, please
610 see Koha::Biblio->can_be_transferred() instead of using this method for
611 multiple items of the same biblio.
615 sub can_be_transferred {
616 my ($self, $params) = @_;
618 my $to = $params->{to};
619 my $from = $params->{from};
621 $to = $to->branchcode;
622 $from = defined $from ? $from->branchcode : $self->holdingbranch;
624 return 1 if $from eq $to; # Transfer to current branch is allowed
625 return 1 unless C4::Context->preference('UseBranchTransferLimits');
627 my $limittype = C4::Context->preference('BranchTransferLimitsType');
628 return Koha::Item::Transfer::Limits->search({
631 $limittype => $limittype eq 'itemtype'
632 ? $self->effective_itemtype : $self->ccode
637 =head3 pickup_locations
639 $pickup_locations = $item->pickup_locations( {patron => $patron } )
641 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)
642 and if item can be transferred to each pickup location.
646 sub pickup_locations {
647 my ($self, $params) = @_;
649 my $patron = $params->{patron};
651 my $circ_control_branch =
652 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
654 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
656 if(defined $patron) {
657 return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
658 return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
661 my $pickup_libraries = Koha::Libraries->search();
662 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
663 $pickup_libraries = $self->home_branch->get_hold_libraries;
664 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
665 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
666 $pickup_libraries = $plib->get_hold_libraries;
667 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
668 $pickup_libraries = Koha::Libraries->search({ branchcode => $self->homebranch });
669 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
670 $pickup_libraries = Koha::Libraries->search({ branchcode => $self->holdingbranch });
673 return $pickup_libraries->search(
678 order_by => ['branchname']
680 ) unless C4::Context->preference('UseBranchTransferLimits');
682 my $limittype = C4::Context->preference('BranchTransferLimitsType');
683 my ($ccode, $itype) = (undef, undef);
684 if( $limittype eq 'ccode' ){
685 $ccode = $self->ccode;
687 $itype = $self->itype;
689 my $limits = Koha::Item::Transfer::Limits->search(
691 fromBranch => $self->holdingbranch,
695 { columns => ['toBranch'] }
698 return $pickup_libraries->search(
700 pickup_location => 1,
702 '-not_in' => $limits->_resultset->as_query
706 order_by => ['branchname']
711 =head3 article_request_type
713 my $type = $item->article_request_type( $borrower )
715 returns 'yes', 'no', 'bib_only', or 'item_only'
717 $borrower must be a Koha::Patron object
721 sub article_request_type {
722 my ( $self, $borrower ) = @_;
724 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
726 $branch_control eq 'homebranch' ? $self->homebranch
727 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
729 my $borrowertype = $borrower->categorycode;
730 my $itemtype = $self->effective_itemtype();
731 my $rule = Koha::CirculationRules->get_effective_rule(
733 rule_name => 'article_requests',
734 categorycode => $borrowertype,
735 itemtype => $itemtype,
736 branchcode => $branchcode
740 return q{} unless $rule;
741 return $rule->rule_value || q{}
750 my $attributes = { order_by => 'priority' };
751 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
753 itemnumber => $self->itemnumber,
756 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
757 waitingdate => { '!=' => undef },
760 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
761 return Koha::Holds->_new_from_dbic($hold_rs);
764 =head3 stockrotationitem
766 my $sritem = Koha::Item->stockrotationitem;
768 Returns the stock rotation item associated with the current item.
772 sub stockrotationitem {
774 my $rs = $self->_result->stockrotationitem;
776 return Koha::StockRotationItem->_new_from_dbic( $rs );
781 my $item = $item->add_to_rota($rota_id);
783 Add this item to the rota identified by $ROTA_ID, which means associating it
784 with the first stage of that rota. Should this item already be associated
785 with a rota, then we will move it to the new rota.
790 my ( $self, $rota_id ) = @_;
791 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
795 =head3 has_pending_hold
797 my $is_pending_hold = $item->has_pending_hold();
799 This method checks the tmp_holdsqueue to see if this item has been selected for a hold, but not filled yet and returns true or false
803 sub has_pending_hold {
805 my $pending_hold = $self->_result->tmp_holdsqueues;
806 return $pending_hold->count ? 1: 0;
811 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
812 my $field = $item->as_marc_field({ [ mss => $mss ] });
814 This method returns a MARC::Field object representing the Koha::Item object
815 with the current mappings configuration.
820 my ( $self, $params ) = @_;
822 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
823 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
827 my @columns = $self->_result->result_source->columns;
829 foreach my $item_field ( @columns ) {
830 my $mapping = $mss->{ "items.$item_field"}[0];
831 my $tagfield = $mapping->{tagfield};
832 my $tagsubfield = $mapping->{tagsubfield};
833 next if !$tagfield; # TODO: Should we raise an exception instead?
834 # Feels like safe fallback is better
836 push @subfields, $tagsubfield => $self->$item_field
837 if defined $self->$item_field and $item_field ne '';
840 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
841 push( @subfields, @{$unlinked_item_subfields} )
842 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
846 $field = MARC::Field->new(
847 "$item_tag", ' ', ' ', @subfields
853 =head3 renewal_branchcode
855 Returns the branchcode to be recorded in statistics renewal of the item
859 sub renewal_branchcode {
861 my ($self, $params ) = @_;
863 my $interface = C4::Context->interface;
865 if ( $interface eq 'opac' ){
866 my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
867 if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
868 $branchcode = 'OPACRenew';
870 elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
871 $branchcode = $self->homebranch;
873 elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
874 $branchcode = $self->checkout->patron->branchcode;
876 elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
877 $branchcode = $self->checkout->branchcode;
883 $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
884 ? C4::Context->userenv->{branch} : $params->{branch};
891 Return the cover images associated with this item.
898 my $cover_image_rs = $self->_result->cover_images;
899 return unless $cover_image_rs;
900 return Koha::CoverImages->_new_from_dbic($cover_image_rs);
903 =head3 _set_found_trigger
905 $self->_set_found_trigger
907 Finds the most recent lost item charge for this item and refunds the patron
908 appropriately, taking into account any payments or writeoffs already applied
911 Internal function, not exported, called only by Koha::Item->store.
915 sub _set_found_trigger {
916 my ( $self, $pre_mod_item ) = @_;
918 ## If item was lost, it has now been found, reverse any list item charges if necessary.
919 my $no_refund_after_days =
920 C4::Context->preference('NoRefundOnLostReturnedItemsAge');
921 if ($no_refund_after_days) {
922 my $today = dt_from_string();
923 my $lost_age_in_days =
924 dt_from_string( $pre_mod_item->itemlost_on )->delta_days($today)
927 return $self unless $lost_age_in_days < $no_refund_after_days;
930 my $lostreturn_policy = Koha::CirculationRules->get_lostreturn_policy(
933 return_branch => C4::Context->userenv
934 ? C4::Context->userenv->{'branch'}
939 if ( $lostreturn_policy ) {
941 # refund charge made for lost book
942 my $lost_charge = Koha::Account::Lines->search(
944 itemnumber => $self->itemnumber,
945 debit_type_code => 'LOST',
946 status => [ undef, { '<>' => 'FOUND' } ]
949 order_by => { -desc => [ 'date', 'accountlines_id' ] },
954 if ( $lost_charge ) {
956 my $patron = $lost_charge->patron;
959 my $account = $patron->account;
960 my $total_to_refund = 0;
963 if ( $lost_charge->amount > $lost_charge->amountoutstanding ) {
965 # some amount has been cancelled. collect the offsets that are not writeoffs
966 # this works because the only way to subtract from this kind of a debt is
967 # using the UI buttons 'Pay' and 'Write off'
968 my $credits_offsets = Koha::Account::Offsets->search(
970 debit_id => $lost_charge->id,
971 credit_id => { '!=' => undef }, # it is not the debit itself
972 type => { '!=' => 'Writeoff' },
973 amount => { '<' => 0 } # credits are negative on the DB
977 $total_to_refund = ( $credits_offsets->count > 0 )
978 ? $credits_offsets->total * -1 # credits are negative on the DB
982 my $credit_total = $lost_charge->amountoutstanding + $total_to_refund;
985 if ( $credit_total > 0 ) {
987 C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
988 $credit = $account->add_credit(
990 amount => $credit_total,
991 description => 'Item found ' . $self->itemnumber,
992 type => 'LOST_FOUND',
993 interface => C4::Context->interface,
994 library_id => $branchcode,
995 item_id => $self->itemnumber,
996 issue_id => $lost_charge->issue_id
1000 $credit->apply( { debits => [$lost_charge] } );
1001 $self->{_refunded} = 1;
1004 # Update the account status
1005 $lost_charge->status('FOUND');
1006 $lost_charge->store();
1008 # Reconcile balances if required
1009 if ( C4::Context->preference('AccountAutoReconcile') ) {
1010 $account->reconcile_balance;
1015 # restore fine for lost book
1016 if ( $lostreturn_policy eq 'restore' ) {
1017 my $lost_overdue = Koha::Account::Lines->search(
1019 itemnumber => $self->itemnumber,
1020 debit_type_code => 'OVERDUE',
1024 order_by => { '-desc' => 'date' },
1029 if ( $lost_overdue ) {
1031 my $patron = $lost_overdue->patron;
1033 my $account = $patron->account;
1035 # Update status of fine
1036 $lost_overdue->status('FOUND')->store();
1038 # Find related forgive credit
1039 my $refund = $lost_overdue->credits(
1041 credit_type_code => 'FORGIVEN',
1042 itemnumber => $self->itemnumber,
1043 status => [ { '!=' => 'VOID' }, undef ]
1045 { order_by => { '-desc' => 'date' }, rows => 1 }
1049 # Revert the forgive credit
1051 $self->{_restored} = 1;
1054 # Reconcile balances if required
1055 if ( C4::Context->preference('AccountAutoReconcile') ) {
1056 $account->reconcile_balance;
1060 } elsif ( $lostreturn_policy eq 'charge' ) {
1061 $self->{_charge} = 1;
1068 =head3 to_api_mapping
1070 This method returns the mapping for representing a Koha::Item object
1075 sub to_api_mapping {
1077 itemnumber => 'item_id',
1078 biblionumber => 'biblio_id',
1079 biblioitemnumber => undef,
1080 barcode => 'external_id',
1081 dateaccessioned => 'acquisition_date',
1082 booksellerid => 'acquisition_source',
1083 homebranch => 'home_library_id',
1084 price => 'purchase_price',
1085 replacementprice => 'replacement_price',
1086 replacementpricedate => 'replacement_price_date',
1087 datelastborrowed => 'last_checkout_date',
1088 datelastseen => 'last_seen_date',
1090 notforloan => 'not_for_loan_status',
1091 damaged => 'damaged_status',
1092 damaged_on => 'damaged_date',
1093 itemlost => 'lost_status',
1094 itemlost_on => 'lost_date',
1095 withdrawn => 'withdrawn',
1096 withdrawn_on => 'withdrawn_date',
1097 itemcallnumber => 'callnumber',
1098 coded_location_qualifier => 'coded_location_qualifier',
1099 issues => 'checkouts_count',
1100 renewals => 'renewals_count',
1101 reserves => 'holds_count',
1102 restricted => 'restricted_status',
1103 itemnotes => 'public_notes',
1104 itemnotes_nonpublic => 'internal_notes',
1105 holdingbranch => 'holding_library_id',
1106 timestamp => 'timestamp',
1107 location => 'location',
1108 permanent_location => 'permanent_location',
1109 onloan => 'checked_out_date',
1110 cn_source => 'call_number_source',
1111 cn_sort => 'call_number_sort',
1112 ccode => 'collection_code',
1113 materials => 'materials_notes',
1115 itype => 'item_type',
1116 more_subfields_xml => 'extended_subfields',
1117 enumchron => 'serial_issue_number',
1118 copynumber => 'copy_number',
1119 stocknumber => 'inventory_number',
1120 new_status => 'new_status'
1126 my $itemtype = $item->itemtype;
1128 Returns Koha object for effective itemtype
1134 return Koha::ItemTypes->find( $self->effective_itemtype );
1137 =head2 Internal methods
1139 =head3 _after_item_action_hooks
1141 Helper method that takes care of calling all plugin hooks
1145 sub _after_item_action_hooks {
1146 my ( $self, $params ) = @_;
1148 my $action = $params->{action};
1150 Koha::Plugins->call(
1151 'after_item_action',
1155 item_id => $self->itemnumber,
1170 Kyle M Hall <kyle@bywatersolutions.com>