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(
408 { to => $to_library, reason => $reason, ignore_limits => 0 } );
410 Add a transfer request for this item to the given branch for the given reason.
412 An exception will be thrown if the BranchTransferLimits would prevent the requested
413 transfer, unless 'ignore_limits' is passed to override the limits.
415 Note: At this time, only one active transfer (i.e pending arrival date) may exist
416 at a time for any given item. An exception will be thrown should you attempt to
417 add a request when a transfer has already been queued, whether it is in transit
418 or just at the request stage.
422 sub request_transfer {
423 my ( $self, $params ) = @_;
425 # check for mandatory params
426 my @mandatory = ( 'to', 'reason' );
427 for my $param (@mandatory) {
428 unless ( defined( $params->{$param} ) ) {
429 Koha::Exceptions::MissingParameter->throw(
430 error => "The $param parameter is mandatory" );
435 Koha::Exceptions::Item::Transfer::Found->throw( transfer => $request )
436 if ( $request = $self->get_transfer );
438 Koha::Exceptions::Item::Transfer::Limit->throw()
439 unless ( $params->{ignore_limits}
440 || $self->can_be_transferred( { to => $params->{to} } ) );
442 my $transfer = Koha::Item::Transfer->new(
444 itemnumber => $self->itemnumber,
445 daterequested => dt_from_string,
446 frombranch => $self->holdingbranch,
447 tobranch => $params->{to}->branchcode,
448 reason => $params->{reason},
449 comments => $params->{comment}
457 my $transfer = $item->get_transfer;
459 Return the active transfer request or undef
461 Note: Transfers are retrieved in a Modified FIFO (First In First Out) order
462 whereby the most recently sent, but not received, transfer will be returned
463 if it exists, otherwise the oldest unsatisfied transfer will be returned.
465 This allows for transfers to queue, which is the case for stock rotation and
466 rotating collections where a manual transfer may need to take precedence but
467 we still expect the item to end up at a final location eventually.
473 my $transfer_rs = $self->_result->branchtransfers->search(
474 { datearrived => undef },
476 order_by => [ { -desc => 'datesent' }, { -asc => 'daterequested' } ],
480 return unless $transfer_rs;
481 return Koha::Item::Transfer->_new_from_dbic($transfer_rs);
484 =head3 last_returned_by
486 Gets and sets the last borrower to return an item.
488 Accepts and returns Koha::Patron objects
490 $item->last_returned_by( $borrowernumber );
492 $last_returned_by = $item->last_returned_by();
496 sub last_returned_by {
497 my ( $self, $borrower ) = @_;
499 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
502 return $items_last_returned_by_rs->update_or_create(
503 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
506 unless ( $self->{_last_returned_by} ) {
507 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
509 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
513 return $self->{_last_returned_by};
517 =head3 can_article_request
519 my $bool = $item->can_article_request( $borrower )
521 Returns true if item can be specifically requested
523 $borrower must be a Koha::Patron object
527 sub can_article_request {
528 my ( $self, $borrower ) = @_;
530 my $rule = $self->article_request_type($borrower);
532 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
536 =head3 hidden_in_opac
538 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
540 Returns true if item fields match the hidding criteria defined in $rules.
541 Returns false otherwise.
543 Takes HASHref that can have the following parameters:
545 $rules : { <field> => [ value_1, ... ], ... }
547 Note: $rules inherits its structure from the parsed YAML from reading
548 the I<OpacHiddenItems> system preference.
553 my ( $self, $params ) = @_;
555 my $rules = $params->{rules} // {};
558 if C4::Context->preference('hidelostitems') and
561 my $hidden_in_opac = 0;
563 foreach my $field ( keys %{$rules} ) {
565 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
571 return $hidden_in_opac;
574 =head3 can_be_transferred
576 $item->can_be_transferred({ to => $to_library, from => $from_library })
577 Checks if an item can be transferred to given library.
579 This feature is controlled by two system preferences:
580 UseBranchTransferLimits to enable / disable the feature
581 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
582 for setting the limitations
584 Takes HASHref that can have the following parameters:
585 MANDATORY PARAMETERS:
588 $from : Koha::Library # if not given, item holdingbranch
589 # will be used instead
591 Returns 1 if item can be transferred to $to_library, otherwise 0.
593 To find out whether at least one item of a Koha::Biblio can be transferred, please
594 see Koha::Biblio->can_be_transferred() instead of using this method for
595 multiple items of the same biblio.
599 sub can_be_transferred {
600 my ($self, $params) = @_;
602 my $to = $params->{to};
603 my $from = $params->{from};
605 $to = $to->branchcode;
606 $from = defined $from ? $from->branchcode : $self->holdingbranch;
608 return 1 if $from eq $to; # Transfer to current branch is allowed
609 return 1 unless C4::Context->preference('UseBranchTransferLimits');
611 my $limittype = C4::Context->preference('BranchTransferLimitsType');
612 return Koha::Item::Transfer::Limits->search({
615 $limittype => $limittype eq 'itemtype'
616 ? $self->effective_itemtype : $self->ccode
621 =head3 pickup_locations
623 $pickup_locations = $item->pickup_locations( {patron => $patron } )
625 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)
626 and if item can be transferred to each pickup location.
630 sub pickup_locations {
631 my ($self, $params) = @_;
633 my $patron = $params->{patron};
635 my $circ_control_branch =
636 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
638 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
640 if(defined $patron) {
641 return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
642 return Koha::Libraries->new()->empty if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
645 my $pickup_libraries = Koha::Libraries->search();
646 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
647 $pickup_libraries = $self->home_branch->get_hold_libraries;
648 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
649 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
650 $pickup_libraries = $plib->get_hold_libraries;
651 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
652 $pickup_libraries = Koha::Libraries->search({ branchcode => $self->homebranch });
653 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
654 $pickup_libraries = Koha::Libraries->search({ branchcode => $self->holdingbranch });
657 return $pickup_libraries->search(
662 order_by => ['branchname']
664 ) unless C4::Context->preference('UseBranchTransferLimits');
666 my $limittype = C4::Context->preference('BranchTransferLimitsType');
667 my ($ccode, $itype) = (undef, undef);
668 if( $limittype eq 'ccode' ){
669 $ccode = $self->ccode;
671 $itype = $self->itype;
673 my $limits = Koha::Item::Transfer::Limits->search(
675 fromBranch => $self->holdingbranch,
679 { columns => ['toBranch'] }
682 return $pickup_libraries->search(
684 pickup_location => 1,
686 '-not_in' => $limits->_resultset->as_query
690 order_by => ['branchname']
695 =head3 article_request_type
697 my $type = $item->article_request_type( $borrower )
699 returns 'yes', 'no', 'bib_only', or 'item_only'
701 $borrower must be a Koha::Patron object
705 sub article_request_type {
706 my ( $self, $borrower ) = @_;
708 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
710 $branch_control eq 'homebranch' ? $self->homebranch
711 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
713 my $borrowertype = $borrower->categorycode;
714 my $itemtype = $self->effective_itemtype();
715 my $rule = Koha::CirculationRules->get_effective_rule(
717 rule_name => 'article_requests',
718 categorycode => $borrowertype,
719 itemtype => $itemtype,
720 branchcode => $branchcode
724 return q{} unless $rule;
725 return $rule->rule_value || q{}
734 my $attributes = { order_by => 'priority' };
735 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
737 itemnumber => $self->itemnumber,
740 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
741 waitingdate => { '!=' => undef },
744 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
745 return Koha::Holds->_new_from_dbic($hold_rs);
748 =head3 stockrotationitem
750 my $sritem = Koha::Item->stockrotationitem;
752 Returns the stock rotation item associated with the current item.
756 sub stockrotationitem {
758 my $rs = $self->_result->stockrotationitem;
760 return Koha::StockRotationItem->_new_from_dbic( $rs );
765 my $item = $item->add_to_rota($rota_id);
767 Add this item to the rota identified by $ROTA_ID, which means associating it
768 with the first stage of that rota. Should this item already be associated
769 with a rota, then we will move it to the new rota.
774 my ( $self, $rota_id ) = @_;
775 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
779 =head3 has_pending_hold
781 my $is_pending_hold = $item->has_pending_hold();
783 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
787 sub has_pending_hold {
789 my $pending_hold = $self->_result->tmp_holdsqueues;
790 return $pending_hold->count ? 1: 0;
795 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
796 my $field = $item->as_marc_field({ [ mss => $mss ] });
798 This method returns a MARC::Field object representing the Koha::Item object
799 with the current mappings configuration.
804 my ( $self, $params ) = @_;
806 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
807 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
811 my @columns = $self->_result->result_source->columns;
813 foreach my $item_field ( @columns ) {
814 my $mapping = $mss->{ "items.$item_field"}[0];
815 my $tagfield = $mapping->{tagfield};
816 my $tagsubfield = $mapping->{tagsubfield};
817 next if !$tagfield; # TODO: Should we raise an exception instead?
818 # Feels like safe fallback is better
820 push @subfields, $tagsubfield => $self->$item_field
821 if defined $self->$item_field and $item_field ne '';
824 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
825 push( @subfields, @{$unlinked_item_subfields} )
826 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
830 $field = MARC::Field->new(
831 "$item_tag", ' ', ' ', @subfields
837 =head3 renewal_branchcode
839 Returns the branchcode to be recorded in statistics renewal of the item
843 sub renewal_branchcode {
845 my ($self, $params ) = @_;
847 my $interface = C4::Context->interface;
849 if ( $interface eq 'opac' ){
850 my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
851 if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
852 $branchcode = 'OPACRenew';
854 elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
855 $branchcode = $self->homebranch;
857 elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
858 $branchcode = $self->checkout->patron->branchcode;
860 elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
861 $branchcode = $self->checkout->branchcode;
867 $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
868 ? C4::Context->userenv->{branch} : $params->{branch};
875 Return the cover images associated with this item.
882 my $cover_image_rs = $self->_result->cover_images;
883 return unless $cover_image_rs;
884 return Koha::CoverImages->_new_from_dbic($cover_image_rs);
887 =head3 _set_found_trigger
889 $self->_set_found_trigger
891 Finds the most recent lost item charge for this item and refunds the patron
892 appropriately, taking into account any payments or writeoffs already applied
895 Internal function, not exported, called only by Koha::Item->store.
899 sub _set_found_trigger {
900 my ( $self, $pre_mod_item ) = @_;
902 ## If item was lost, it has now been found, reverse any list item charges if necessary.
903 my $no_refund_after_days =
904 C4::Context->preference('NoRefundOnLostReturnedItemsAge');
905 if ($no_refund_after_days) {
906 my $today = dt_from_string();
907 my $lost_age_in_days =
908 dt_from_string( $pre_mod_item->itemlost_on )->delta_days($today)
911 return $self unless $lost_age_in_days < $no_refund_after_days;
914 my $lostreturn_policy = Koha::CirculationRules->get_lostreturn_policy(
917 return_branch => C4::Context->userenv
918 ? C4::Context->userenv->{'branch'}
923 if ( $lostreturn_policy ) {
925 # refund charge made for lost book
926 my $lost_charge = Koha::Account::Lines->search(
928 itemnumber => $self->itemnumber,
929 debit_type_code => 'LOST',
930 status => [ undef, { '<>' => 'FOUND' } ]
933 order_by => { -desc => [ 'date', 'accountlines_id' ] },
938 if ( $lost_charge ) {
940 my $patron = $lost_charge->patron;
943 my $account = $patron->account;
944 my $total_to_refund = 0;
947 if ( $lost_charge->amount > $lost_charge->amountoutstanding ) {
949 # some amount has been cancelled. collect the offsets that are not writeoffs
950 # this works because the only way to subtract from this kind of a debt is
951 # using the UI buttons 'Pay' and 'Write off'
952 my $credits_offsets = Koha::Account::Offsets->search(
954 debit_id => $lost_charge->id,
955 credit_id => { '!=' => undef }, # it is not the debit itself
956 type => { '!=' => 'Writeoff' },
957 amount => { '<' => 0 } # credits are negative on the DB
961 $total_to_refund = ( $credits_offsets->count > 0 )
962 ? $credits_offsets->total * -1 # credits are negative on the DB
966 my $credit_total = $lost_charge->amountoutstanding + $total_to_refund;
969 if ( $credit_total > 0 ) {
971 C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
972 $credit = $account->add_credit(
974 amount => $credit_total,
975 description => 'Item found ' . $self->itemnumber,
976 type => 'LOST_FOUND',
977 interface => C4::Context->interface,
978 library_id => $branchcode,
979 item_id => $self->itemnumber,
980 issue_id => $lost_charge->issue_id
984 $credit->apply( { debits => [$lost_charge] } );
985 $self->{_refunded} = 1;
988 # Update the account status
989 $lost_charge->status('FOUND');
990 $lost_charge->store();
992 # Reconcile balances if required
993 if ( C4::Context->preference('AccountAutoReconcile') ) {
994 $account->reconcile_balance;
999 # restore fine for lost book
1000 if ( $lostreturn_policy eq 'restore' ) {
1001 my $lost_overdue = Koha::Account::Lines->search(
1003 itemnumber => $self->itemnumber,
1004 debit_type_code => 'OVERDUE',
1008 order_by => { '-desc' => 'date' },
1013 if ( $lost_overdue ) {
1015 my $patron = $lost_overdue->patron;
1017 my $account = $patron->account;
1019 # Update status of fine
1020 $lost_overdue->status('FOUND')->store();
1022 # Find related forgive credit
1023 my $refund = $lost_overdue->credits(
1025 credit_type_code => 'FORGIVEN',
1026 itemnumber => $self->itemnumber,
1027 status => [ { '!=' => 'VOID' }, undef ]
1029 { order_by => { '-desc' => 'date' }, rows => 1 }
1033 # Revert the forgive credit
1035 $self->{_restored} = 1;
1038 # Reconcile balances if required
1039 if ( C4::Context->preference('AccountAutoReconcile') ) {
1040 $account->reconcile_balance;
1044 } elsif ( $lostreturn_policy eq 'charge' ) {
1045 $self->{_charge} = 1;
1052 =head3 to_api_mapping
1054 This method returns the mapping for representing a Koha::Item object
1059 sub to_api_mapping {
1061 itemnumber => 'item_id',
1062 biblionumber => 'biblio_id',
1063 biblioitemnumber => undef,
1064 barcode => 'external_id',
1065 dateaccessioned => 'acquisition_date',
1066 booksellerid => 'acquisition_source',
1067 homebranch => 'home_library_id',
1068 price => 'purchase_price',
1069 replacementprice => 'replacement_price',
1070 replacementpricedate => 'replacement_price_date',
1071 datelastborrowed => 'last_checkout_date',
1072 datelastseen => 'last_seen_date',
1074 notforloan => 'not_for_loan_status',
1075 damaged => 'damaged_status',
1076 damaged_on => 'damaged_date',
1077 itemlost => 'lost_status',
1078 itemlost_on => 'lost_date',
1079 withdrawn => 'withdrawn',
1080 withdrawn_on => 'withdrawn_date',
1081 itemcallnumber => 'callnumber',
1082 coded_location_qualifier => 'coded_location_qualifier',
1083 issues => 'checkouts_count',
1084 renewals => 'renewals_count',
1085 reserves => 'holds_count',
1086 restricted => 'restricted_status',
1087 itemnotes => 'public_notes',
1088 itemnotes_nonpublic => 'internal_notes',
1089 holdingbranch => 'holding_library_id',
1090 timestamp => 'timestamp',
1091 location => 'location',
1092 permanent_location => 'permanent_location',
1093 onloan => 'checked_out_date',
1094 cn_source => 'call_number_source',
1095 cn_sort => 'call_number_sort',
1096 ccode => 'collection_code',
1097 materials => 'materials_notes',
1099 itype => 'item_type',
1100 more_subfields_xml => 'extended_subfields',
1101 enumchron => 'serial_issue_number',
1102 copynumber => 'copy_number',
1103 stocknumber => 'inventory_number',
1104 new_status => 'new_status'
1110 my $itemtype = $item->itemtype;
1112 Returns Koha object for effective itemtype
1118 return Koha::ItemTypes->find( $self->effective_itemtype );
1121 =head2 Internal methods
1123 =head3 _after_item_action_hooks
1125 Helper method that takes care of calling all plugin hooks
1129 sub _after_item_action_hooks {
1130 my ( $self, $params ) = @_;
1132 my $action = $params->{action};
1134 Koha::Plugins->call(
1135 'after_item_action',
1139 item_id => $self->itemnumber,
1154 Kyle M Hall <kyle@bywatersolutions.com>