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
98 unless ( $self->permanent_location ) {
99 $self->permanent_location($self->location);
102 my $default_location = C4::Context->preference('NewItemsDefaultLocation');
103 unless ( $self->location || !$default_location ) {
104 $self->permanent_location( $self->location || $default_location )
105 unless $self->permanent_location;
106 $self->location($default_location);
109 unless ( $self->replacementpricedate ) {
110 $self->replacementpricedate($today);
112 unless ( $self->datelastseen ) {
113 $self->datelastseen($today);
116 unless ( $self->dateaccessioned ) {
117 $self->dateaccessioned($today);
120 if ( $self->itemcallnumber
121 or $self->cn_source )
123 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
124 $self->cn_sort($cn_sort);
131 my %updated_columns = $self->_result->get_dirty_columns;
132 return $self->SUPER::store unless %updated_columns;
134 # Retrieve the item for comparison if we need to
136 exists $updated_columns{itemlost}
137 or exists $updated_columns{withdrawn}
138 or exists $updated_columns{damaged}
139 ) ? $self->get_from_storage : undef;
141 # Update *_on fields if needed
142 # FIXME: Why not for AddItem as well?
143 my @fields = qw( itemlost withdrawn damaged );
144 for my $field (@fields) {
146 # If the field is defined but empty or 0, we are
147 # removing/unsetting and thus need to clear out
149 if ( exists $updated_columns{$field}
150 && defined( $self->$field )
153 my $field_on = "${field}_on";
154 $self->$field_on(undef);
156 # If the field has changed otherwise, we much update
158 elsif (exists $updated_columns{$field}
159 && $updated_columns{$field}
160 && !$pre_mod_item->$field )
162 my $field_on = "${field}_on";
164 DateTime::Format::MySQL->format_datetime(
171 if ( exists $updated_columns{itemcallnumber}
172 or exists $updated_columns{cn_source} )
174 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
175 $self->cn_sort($cn_sort);
179 if ( exists $updated_columns{location}
180 and $self->location ne 'CART'
181 and $self->location ne 'PROC'
182 and not exists $updated_columns{permanent_location} )
184 $self->permanent_location( $self->location );
187 # If item was lost and has now been found,
188 # reverse any list item charges if necessary.
189 if ( exists $updated_columns{itemlost}
190 and $updated_columns{itemlost} <= 0
191 and $pre_mod_item->itemlost > 0 )
193 $self->_set_found_trigger($pre_mod_item);
198 unless ( $self->dateaccessioned ) {
199 $self->dateaccessioned($today);
202 my $result = $self->SUPER::store;
203 if ( $log_action && C4::Context->preference("CataloguingLog") ) {
205 ? logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
206 : logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper( $self->unblessed ) );
208 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
209 $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
210 unless $params->{skip_record_index};
211 $self->get_from_storage->_after_item_action_hooks({ action => $action });
222 my $params = @_ ? shift : {};
224 # FIXME check the item has no current issues
225 # i.e. raise the appropriate exception
227 my $result = $self->SUPER::delete;
229 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
230 $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
231 unless $params->{skip_record_index};
233 $self->_after_item_action_hooks({ action => 'delete' });
235 logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
236 if C4::Context->preference("CataloguingLog");
247 my $params = @_ ? shift : {};
249 my $safe_to_delete = $self->safe_to_delete;
250 return $safe_to_delete unless $safe_to_delete eq '1';
252 $self->move_to_deleted;
254 return $self->delete($params);
257 =head3 safe_to_delete
259 returns 1 if the item is safe to delete,
261 "book_on_loan" if the item is checked out,
263 "not_same_branch" if the item is blocked by independent branches,
265 "book_reserved" if the there are holds aganst the item, or
267 "linked_analytics" if the item has linked analytic records.
269 "last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
276 return "book_on_loan" if $self->checkout;
278 return "not_same_branch"
279 if defined C4::Context->userenv
280 and !C4::Context->IsSuperLibrarian()
281 and C4::Context->preference("IndependentBranches")
282 and ( C4::Context->userenv->{branch} ne $self->homebranch );
284 # check it doesn't have a waiting reserve
285 return "book_reserved"
286 if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
288 return "linked_analytics"
289 if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
291 return "last_item_for_hold"
292 if $self->biblio->items->count == 1
293 && $self->biblio->holds->search(
302 =head3 move_to_deleted
304 my $is_moved = $item->move_to_deleted;
306 Move an item to the deleteditems table.
307 This can be done before deleting an item, to make sure the data are not completely deleted.
311 sub move_to_deleted {
313 my $item_infos = $self->unblessed;
314 delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
315 return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
319 =head3 effective_itemtype
321 Returns the itemtype for the item based on whether item level itemtypes are set or not.
325 sub effective_itemtype {
328 return $self->_result()->effective_itemtype();
338 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
340 return $self->{_home_branch};
343 =head3 holding_branch
350 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
352 return $self->{_holding_branch};
357 my $biblio = $item->biblio;
359 Return the bibliographic record of this item
365 my $biblio_rs = $self->_result->biblio;
366 return Koha::Biblio->_new_from_dbic( $biblio_rs );
371 my $biblioitem = $item->biblioitem;
373 Return the biblioitem record of this item
379 my $biblioitem_rs = $self->_result->biblioitem;
380 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
385 my $checkout = $item->checkout;
387 Return the checkout for this item
393 my $checkout_rs = $self->_result->issue;
394 return unless $checkout_rs;
395 return Koha::Checkout->_new_from_dbic( $checkout_rs );
400 my $holds = $item->holds();
401 my $holds = $item->holds($params);
402 my $holds = $item->holds({ found => 'W'});
404 Return holds attached to an item, optionally accept a hashref of params to pass to search
409 my ( $self,$params ) = @_;
410 my $holds_rs = $self->_result->reserves->search($params);
411 return Koha::Holds->_new_from_dbic( $holds_rs );
414 =head3 request_transfer
416 my $transfer = $item->request_transfer(
420 [ ignore_limits => 0, enqueue => 1, replace => 1 ]
424 Add a transfer request for this item to the given branch for the given reason.
426 An exception will be thrown if the BranchTransferLimits would prevent the requested
427 transfer, unless 'ignore_limits' is passed to override the limits.
429 An exception will be thrown if an active transfer (i.e pending arrival date) is found;
430 The caller should catch such cases and retry the transfer request as appropriate passing
431 an appropriate override.
434 * enqueue - Used to queue up the transfer when the existing transfer is found to be in transit.
435 * replace - Used to replace the existing transfer request with your own.
439 sub request_transfer {
440 my ( $self, $params ) = @_;
442 # check for mandatory params
443 my @mandatory = ( 'to', 'reason' );
444 for my $param (@mandatory) {
445 unless ( defined( $params->{$param} ) ) {
446 Koha::Exceptions::MissingParameter->throw(
447 error => "The $param parameter is mandatory" );
451 Koha::Exceptions::Item::Transfer::Limit->throw()
452 unless ( $params->{ignore_limits}
453 || $self->can_be_transferred( { to => $params->{to} } ) );
455 my $request = $self->get_transfer;
456 Koha::Exceptions::Item::Transfer::InQueue->throw( transfer => $request )
457 if ( $request && !$params->{enqueue} && !$params->{replace} );
459 $request->cancel( { reason => $params->{reason}, force => 1 } )
460 if ( defined($request) && $params->{replace} );
462 my $transfer = Koha::Item::Transfer->new(
464 itemnumber => $self->itemnumber,
465 daterequested => dt_from_string,
466 frombranch => $self->holdingbranch,
467 tobranch => $params->{to}->branchcode,
468 reason => $params->{reason},
469 comments => $params->{comment}
478 my $transfer = $item->get_transfer;
480 Return the active transfer request or undef
482 Note: Transfers are retrieved in a Modified FIFO (First In First Out) order
483 whereby the most recently sent, but not received, transfer will be returned
484 if it exists, otherwise the oldest unsatisfied transfer will be returned.
486 This allows for transfers to queue, which is the case for stock rotation and
487 rotating collections where a manual transfer may need to take precedence but
488 we still expect the item to end up at a final location eventually.
494 my $transfer_rs = $self->_result->branchtransfers->search(
496 datearrived => undef,
497 datecancelled => undef
501 [ { -desc => 'datesent' }, { -asc => 'daterequested' } ],
505 return unless $transfer_rs;
506 return Koha::Item::Transfer->_new_from_dbic($transfer_rs);
509 =head3 last_returned_by
511 Gets and sets the last borrower to return an item.
513 Accepts and returns Koha::Patron objects
515 $item->last_returned_by( $borrowernumber );
517 $last_returned_by = $item->last_returned_by();
521 sub last_returned_by {
522 my ( $self, $borrower ) = @_;
524 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
527 return $items_last_returned_by_rs->update_or_create(
528 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
531 unless ( $self->{_last_returned_by} ) {
532 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
534 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
538 return $self->{_last_returned_by};
542 =head3 can_article_request
544 my $bool = $item->can_article_request( $borrower )
546 Returns true if item can be specifically requested
548 $borrower must be a Koha::Patron object
552 sub can_article_request {
553 my ( $self, $borrower ) = @_;
555 my $rule = $self->article_request_type($borrower);
557 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
561 =head3 hidden_in_opac
563 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
565 Returns true if item fields match the hidding criteria defined in $rules.
566 Returns false otherwise.
568 Takes HASHref that can have the following parameters:
570 $rules : { <field> => [ value_1, ... ], ... }
572 Note: $rules inherits its structure from the parsed YAML from reading
573 the I<OpacHiddenItems> system preference.
578 my ( $self, $params ) = @_;
580 my $rules = $params->{rules} // {};
583 if C4::Context->preference('hidelostitems') and
586 my $hidden_in_opac = 0;
588 foreach my $field ( keys %{$rules} ) {
590 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
596 return $hidden_in_opac;
599 =head3 can_be_transferred
601 $item->can_be_transferred({ to => $to_library, from => $from_library })
602 Checks if an item can be transferred to given library.
604 This feature is controlled by two system preferences:
605 UseBranchTransferLimits to enable / disable the feature
606 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
607 for setting the limitations
609 Takes HASHref that can have the following parameters:
610 MANDATORY PARAMETERS:
613 $from : Koha::Library # if not given, item holdingbranch
614 # will be used instead
616 Returns 1 if item can be transferred to $to_library, otherwise 0.
618 To find out whether at least one item of a Koha::Biblio can be transferred, please
619 see Koha::Biblio->can_be_transferred() instead of using this method for
620 multiple items of the same biblio.
624 sub can_be_transferred {
625 my ($self, $params) = @_;
627 my $to = $params->{to};
628 my $from = $params->{from};
630 $to = $to->branchcode;
631 $from = defined $from ? $from->branchcode : $self->holdingbranch;
633 return 1 if $from eq $to; # Transfer to current branch is allowed
634 return 1 unless C4::Context->preference('UseBranchTransferLimits');
636 my $limittype = C4::Context->preference('BranchTransferLimitsType');
637 return Koha::Item::Transfer::Limits->search({
640 $limittype => $limittype eq 'itemtype'
641 ? $self->effective_itemtype : $self->ccode
646 =head3 pickup_locations
648 $pickup_locations = $item->pickup_locations( {patron => $patron } )
650 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)
651 and if item can be transferred to each pickup location.
655 sub pickup_locations {
656 my ($self, $params) = @_;
658 my $patron = $params->{patron};
660 my $circ_control_branch =
661 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
663 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
665 if(defined $patron) {
666 return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
667 return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
670 my $pickup_libraries = Koha::Libraries->search();
671 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
672 $pickup_libraries = $self->home_branch->get_hold_libraries;
673 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
674 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
675 $pickup_libraries = $plib->get_hold_libraries;
676 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
677 $pickup_libraries = Koha::Libraries->search({ branchcode => $self->homebranch });
678 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
679 $pickup_libraries = Koha::Libraries->search({ branchcode => $self->holdingbranch });
682 return $pickup_libraries->search(
687 order_by => ['branchname']
689 ) unless C4::Context->preference('UseBranchTransferLimits');
691 my $limittype = C4::Context->preference('BranchTransferLimitsType');
692 my ($ccode, $itype) = (undef, undef);
693 if( $limittype eq 'ccode' ){
694 $ccode = $self->ccode;
696 $itype = $self->itype;
698 my $limits = Koha::Item::Transfer::Limits->search(
700 fromBranch => $self->holdingbranch,
704 { columns => ['toBranch'] }
707 return $pickup_libraries->search(
709 pickup_location => 1,
711 '-not_in' => $limits->_resultset->as_query
715 order_by => ['branchname']
720 =head3 article_request_type
722 my $type = $item->article_request_type( $borrower )
724 returns 'yes', 'no', 'bib_only', or 'item_only'
726 $borrower must be a Koha::Patron object
730 sub article_request_type {
731 my ( $self, $borrower ) = @_;
733 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
735 $branch_control eq 'homebranch' ? $self->homebranch
736 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
738 my $borrowertype = $borrower->categorycode;
739 my $itemtype = $self->effective_itemtype();
740 my $rule = Koha::CirculationRules->get_effective_rule(
742 rule_name => 'article_requests',
743 categorycode => $borrowertype,
744 itemtype => $itemtype,
745 branchcode => $branchcode
749 return q{} unless $rule;
750 return $rule->rule_value || q{}
759 my $attributes = { order_by => 'priority' };
760 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
762 itemnumber => $self->itemnumber,
765 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
766 waitingdate => { '!=' => undef },
769 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
770 return Koha::Holds->_new_from_dbic($hold_rs);
773 =head3 stockrotationitem
775 my $sritem = Koha::Item->stockrotationitem;
777 Returns the stock rotation item associated with the current item.
781 sub stockrotationitem {
783 my $rs = $self->_result->stockrotationitem;
785 return Koha::StockRotationItem->_new_from_dbic( $rs );
790 my $item = $item->add_to_rota($rota_id);
792 Add this item to the rota identified by $ROTA_ID, which means associating it
793 with the first stage of that rota. Should this item already be associated
794 with a rota, then we will move it to the new rota.
799 my ( $self, $rota_id ) = @_;
800 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
804 =head3 has_pending_hold
806 my $is_pending_hold = $item->has_pending_hold();
808 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
812 sub has_pending_hold {
814 my $pending_hold = $self->_result->tmp_holdsqueues;
815 return $pending_hold->count ? 1: 0;
820 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
821 my $field = $item->as_marc_field({ [ mss => $mss ] });
823 This method returns a MARC::Field object representing the Koha::Item object
824 with the current mappings configuration.
829 my ( $self, $params ) = @_;
831 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
832 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
836 my @columns = $self->_result->result_source->columns;
838 foreach my $item_field ( @columns ) {
839 my $mapping = $mss->{ "items.$item_field"}[0];
840 my $tagfield = $mapping->{tagfield};
841 my $tagsubfield = $mapping->{tagsubfield};
842 next if !$tagfield; # TODO: Should we raise an exception instead?
843 # Feels like safe fallback is better
845 push @subfields, $tagsubfield => $self->$item_field
846 if defined $self->$item_field and $item_field ne '';
849 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
850 push( @subfields, @{$unlinked_item_subfields} )
851 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
855 $field = MARC::Field->new(
856 "$item_tag", ' ', ' ', @subfields
862 =head3 renewal_branchcode
864 Returns the branchcode to be recorded in statistics renewal of the item
868 sub renewal_branchcode {
870 my ($self, $params ) = @_;
872 my $interface = C4::Context->interface;
874 if ( $interface eq 'opac' ){
875 my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
876 if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
877 $branchcode = 'OPACRenew';
879 elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
880 $branchcode = $self->homebranch;
882 elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
883 $branchcode = $self->checkout->patron->branchcode;
885 elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
886 $branchcode = $self->checkout->branchcode;
892 $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
893 ? C4::Context->userenv->{branch} : $params->{branch};
900 Return the cover images associated with this item.
907 my $cover_image_rs = $self->_result->cover_images;
908 return unless $cover_image_rs;
909 return Koha::CoverImages->_new_from_dbic($cover_image_rs);
912 =head3 _set_found_trigger
914 $self->_set_found_trigger
916 Finds the most recent lost item charge for this item and refunds the patron
917 appropriately, taking into account any payments or writeoffs already applied
920 Internal function, not exported, called only by Koha::Item->store.
924 sub _set_found_trigger {
925 my ( $self, $pre_mod_item ) = @_;
927 ## If item was lost, it has now been found, reverse any list item charges if necessary.
928 my $no_refund_after_days =
929 C4::Context->preference('NoRefundOnLostReturnedItemsAge');
930 if ($no_refund_after_days) {
931 my $today = dt_from_string();
932 my $lost_age_in_days =
933 dt_from_string( $pre_mod_item->itemlost_on )->delta_days($today)
936 return $self unless $lost_age_in_days < $no_refund_after_days;
939 my $lostreturn_policy = Koha::CirculationRules->get_lostreturn_policy(
942 return_branch => C4::Context->userenv
943 ? C4::Context->userenv->{'branch'}
948 if ( $lostreturn_policy ) {
950 # refund charge made for lost book
951 my $lost_charge = Koha::Account::Lines->search(
953 itemnumber => $self->itemnumber,
954 debit_type_code => 'LOST',
955 status => [ undef, { '<>' => 'FOUND' } ]
958 order_by => { -desc => [ 'date', 'accountlines_id' ] },
963 if ( $lost_charge ) {
965 my $patron = $lost_charge->patron;
968 my $account = $patron->account;
969 my $total_to_refund = 0;
972 if ( $lost_charge->amount > $lost_charge->amountoutstanding ) {
974 # some amount has been cancelled. collect the offsets that are not writeoffs
975 # this works because the only way to subtract from this kind of a debt is
976 # using the UI buttons 'Pay' and 'Write off'
977 my $credits_offsets = Koha::Account::Offsets->search(
979 debit_id => $lost_charge->id,
980 credit_id => { '!=' => undef }, # it is not the debit itself
981 type => { '!=' => 'Writeoff' },
982 amount => { '<' => 0 } # credits are negative on the DB
986 $total_to_refund = ( $credits_offsets->count > 0 )
987 ? $credits_offsets->total * -1 # credits are negative on the DB
991 my $credit_total = $lost_charge->amountoutstanding + $total_to_refund;
994 if ( $credit_total > 0 ) {
996 C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
997 $credit = $account->add_credit(
999 amount => $credit_total,
1000 description => 'Item found ' . $self->itemnumber,
1001 type => 'LOST_FOUND',
1002 interface => C4::Context->interface,
1003 library_id => $branchcode,
1004 item_id => $self->itemnumber,
1005 issue_id => $lost_charge->issue_id
1009 $credit->apply( { debits => [$lost_charge] } );
1010 $self->{_refunded} = 1;
1013 # Update the account status
1014 $lost_charge->status('FOUND');
1015 $lost_charge->store();
1017 # Reconcile balances if required
1018 if ( C4::Context->preference('AccountAutoReconcile') ) {
1019 $account->reconcile_balance;
1024 # restore fine for lost book
1025 if ( $lostreturn_policy eq 'restore' ) {
1026 my $lost_overdue = Koha::Account::Lines->search(
1028 itemnumber => $self->itemnumber,
1029 debit_type_code => 'OVERDUE',
1033 order_by => { '-desc' => 'date' },
1038 if ( $lost_overdue ) {
1040 my $patron = $lost_overdue->patron;
1042 my $account = $patron->account;
1044 # Update status of fine
1045 $lost_overdue->status('FOUND')->store();
1047 # Find related forgive credit
1048 my $refund = $lost_overdue->credits(
1050 credit_type_code => 'FORGIVEN',
1051 itemnumber => $self->itemnumber,
1052 status => [ { '!=' => 'VOID' }, undef ]
1054 { order_by => { '-desc' => 'date' }, rows => 1 }
1058 # Revert the forgive credit
1060 $self->{_restored} = 1;
1063 # Reconcile balances if required
1064 if ( C4::Context->preference('AccountAutoReconcile') ) {
1065 $account->reconcile_balance;
1069 } elsif ( $lostreturn_policy eq 'charge' ) {
1070 $self->{_charge} = 1;
1077 =head3 to_api_mapping
1079 This method returns the mapping for representing a Koha::Item object
1084 sub to_api_mapping {
1086 itemnumber => 'item_id',
1087 biblionumber => 'biblio_id',
1088 biblioitemnumber => undef,
1089 barcode => 'external_id',
1090 dateaccessioned => 'acquisition_date',
1091 booksellerid => 'acquisition_source',
1092 homebranch => 'home_library_id',
1093 price => 'purchase_price',
1094 replacementprice => 'replacement_price',
1095 replacementpricedate => 'replacement_price_date',
1096 datelastborrowed => 'last_checkout_date',
1097 datelastseen => 'last_seen_date',
1099 notforloan => 'not_for_loan_status',
1100 damaged => 'damaged_status',
1101 damaged_on => 'damaged_date',
1102 itemlost => 'lost_status',
1103 itemlost_on => 'lost_date',
1104 withdrawn => 'withdrawn',
1105 withdrawn_on => 'withdrawn_date',
1106 itemcallnumber => 'callnumber',
1107 coded_location_qualifier => 'coded_location_qualifier',
1108 issues => 'checkouts_count',
1109 renewals => 'renewals_count',
1110 reserves => 'holds_count',
1111 restricted => 'restricted_status',
1112 itemnotes => 'public_notes',
1113 itemnotes_nonpublic => 'internal_notes',
1114 holdingbranch => 'holding_library_id',
1115 timestamp => 'timestamp',
1116 location => 'location',
1117 permanent_location => 'permanent_location',
1118 onloan => 'checked_out_date',
1119 cn_source => 'call_number_source',
1120 cn_sort => 'call_number_sort',
1121 ccode => 'collection_code',
1122 materials => 'materials_notes',
1124 itype => 'item_type',
1125 more_subfields_xml => 'extended_subfields',
1126 enumchron => 'serial_issue_number',
1127 copynumber => 'copy_number',
1128 stocknumber => 'inventory_number',
1129 new_status => 'new_status'
1135 my $itemtype = $item->itemtype;
1137 Returns Koha object for effective itemtype
1143 return Koha::ItemTypes->find( $self->effective_itemtype );
1146 =head2 Internal methods
1148 =head3 _after_item_action_hooks
1150 Helper method that takes care of calling all plugin hooks
1154 sub _after_item_action_hooks {
1155 my ( $self, $params ) = @_;
1157 my $action = $params->{action};
1159 Koha::Plugins->call(
1160 'after_item_action',
1164 item_id => $self->itemnumber,
1179 Kyle M Hall <kyle@bywatersolutions.com>