Bug 18501: Fix QA issues
[koha.git] / Koha / Item.pm
blob84ba2da33cde51d67be8259a7409b27922cb6da5
1 package Koha::Item;
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>.
20 use Modern::Perl;
22 use Carp;
23 use List::MoreUtils qw(any);
24 use Data::Dumper;
25 use Try::Tiny;
27 use Koha::Database;
28 use Koha::DateUtils qw( dt_from_string );
30 use C4::Context;
31 use C4::Circulation;
32 use C4::Reserves;
33 use C4::Biblio qw( ModZebra ); # FIXME This is terrible, we should move the indexation code outside of C4::Biblio
34 use C4::ClassSource; # FIXME We would like to avoid that
35 use C4::Log qw( logaction );
37 use Koha::Checkouts;
38 use Koha::CirculationRules;
39 use Koha::Item::Transfer::Limits;
40 use Koha::Item::Transfers;
41 use Koha::ItemTypes;
42 use Koha::Patrons;
43 use Koha::Plugins;
44 use Koha::Libraries;
45 use Koha::StockRotationItem;
46 use Koha::StockRotationRotas;
48 use base qw(Koha::Object);
50 =head1 NAME
52 Koha::Item - Koha Item object class
54 =head1 API
56 =head2 Class methods
58 =cut
60 =head3 store
62 $item->store;
64 $params can take an optional 'skip_modzebra_update' parameter.
65 If set, the reindexation process will not happen (ModZebra not called)
67 NOTE: This is a temporary fix to answer a performance issue when lot of items
68 are added (or modified) at the same time.
69 The correct way to fix this is to make the ES reindexation process async.
70 You should not turn it on if you do not understand what it is doing exactly.
72 =cut
74 sub store {
75 my $self = shift;
76 my $params = @_ ? shift : {};
78 my $log_action = $params->{log_action} // 1;
80 # We do not want to oblige callers to pass this value
81 # Dev conveniences vs performance?
82 unless ( $self->biblioitemnumber ) {
83 $self->biblioitemnumber( $self->biblio->biblioitem->biblioitemnumber );
86 # See related changes from C4::Items::AddItem
87 unless ( $self->itype ) {
88 $self->itype($self->biblio->biblioitem->itemtype);
91 my $today = dt_from_string;
92 unless ( $self->in_storage ) { #AddItem
93 unless ( $self->permanent_location ) {
94 $self->permanent_location($self->location);
96 unless ( $self->replacementpricedate ) {
97 $self->replacementpricedate($today);
99 unless ( $self->datelastseen ) {
100 $self->datelastseen($today);
103 unless ( $self->dateaccessioned ) {
104 $self->dateaccessioned($today);
107 if ( $self->itemcallnumber
108 or $self->cn_source )
110 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
111 $self->cn_sort($cn_sort);
114 C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
115 unless $params->{skip_modzebra_update};
117 logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
118 if $log_action && C4::Context->preference("CataloguingLog");
120 $self->_after_item_action_hooks({ action => 'create' });
122 } else { # ModItem
124 my %updated_columns = $self->_result->get_dirty_columns;
125 return $self->SUPER::store unless %updated_columns;
127 # Retrieve the item for comparison if we need to
128 my $pre_mod_item = (
129 exists $updated_columns{itemlost}
130 or exists $updated_columns{withdrawn}
131 or exists $updated_columns{damaged}
132 ) ? $self->get_from_storage : undef;
134 # Update *_on fields if needed
135 # FIXME: Why not for AddItem as well?
136 my @fields = qw( itemlost withdrawn damaged );
137 for my $field (@fields) {
139 # If the field is defined but empty or 0, we are
140 # removing/unsetting and thus need to clear out
141 # the 'on' field
142 if ( exists $updated_columns{$field}
143 && defined( $self->$field )
144 && !$self->$field )
146 my $field_on = "${field}_on";
147 $self->$field_on(undef);
149 # If the field has changed otherwise, we much update
150 # the 'on' field
151 elsif (exists $updated_columns{$field}
152 && $updated_columns{$field}
153 && !$pre_mod_item->$field )
155 my $field_on = "${field}_on";
156 $self->$field_on(
157 DateTime::Format::MySQL->format_datetime(
158 dt_from_string()
164 if ( exists $updated_columns{itemcallnumber}
165 or exists $updated_columns{cn_source} )
167 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
168 $self->cn_sort($cn_sort);
172 if ( exists $updated_columns{location}
173 and $self->location ne 'CART'
174 and $self->location ne 'PROC'
175 and not exists $updated_columns{permanent_location} )
177 $self->permanent_location( $self->location );
180 # If item was lost and has now been found,
181 # reverse any list item charges if necessary.
182 if ( exists $updated_columns{itemlost}
183 and $updated_columns{itemlost} <= 0
184 and $pre_mod_item->itemlost > 0 )
186 $self->_set_found_trigger($pre_mod_item);
187 $self->paidfor('');
190 C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
191 unless $params->{skip_modzebra_update};
193 $self->_after_item_action_hooks({ action => 'modify' });
195 logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper($self->unblessed) )
196 if $log_action && C4::Context->preference("CataloguingLog");
199 unless ( $self->dateaccessioned ) {
200 $self->dateaccessioned($today);
203 return $self->SUPER::store;
206 =head3 delete
208 =cut
210 sub delete {
211 my $self = shift;
212 my $params = @_ ? shift : {};
214 # FIXME check the item has no current issues
215 # i.e. raise the appropriate exception
217 C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
218 unless $params->{skip_modzebra_update};
220 $self->_after_item_action_hooks({ action => 'delete' });
222 logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
223 if C4::Context->preference("CataloguingLog");
225 return $self->SUPER::delete;
228 =head3 safe_delete
230 =cut
232 sub safe_delete {
233 my $self = shift;
234 my $params = @_ ? shift : {};
236 my $safe_to_delete = $self->safe_to_delete;
237 return $safe_to_delete unless $safe_to_delete eq '1';
239 $self->move_to_deleted;
241 return $self->delete($params);
244 =head3 safe_to_delete
246 returns 1 if the item is safe to delete,
248 "book_on_loan" if the item is checked out,
250 "not_same_branch" if the item is blocked by independent branches,
252 "book_reserved" if the there are holds aganst the item, or
254 "linked_analytics" if the item has linked analytic records.
256 "last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
258 =cut
260 sub safe_to_delete {
261 my ($self) = @_;
263 return "book_on_loan" if $self->checkout;
265 return "not_same_branch"
266 if defined C4::Context->userenv
267 and !C4::Context->IsSuperLibrarian()
268 and C4::Context->preference("IndependentBranches")
269 and ( C4::Context->userenv->{branch} ne $self->homebranch );
271 # check it doesn't have a waiting reserve
272 return "book_reserved"
273 if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
275 return "linked_analytics"
276 if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
278 return "last_item_for_hold"
279 if $self->biblio->items->count == 1
280 && $self->biblio->holds->search(
282 itemnumber => undef,
284 )->count;
286 return 1;
289 =head3 move_to_deleted
291 my $is_moved = $item->move_to_deleted;
293 Move an item to the deleteditems table.
294 This can be done before deleting an item, to make sure the data are not completely deleted.
296 =cut
298 sub move_to_deleted {
299 my ($self) = @_;
300 my $item_infos = $self->unblessed;
301 delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
302 return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
306 =head3 effective_itemtype
308 Returns the itemtype for the item based on whether item level itemtypes are set or not.
310 =cut
312 sub effective_itemtype {
313 my ( $self ) = @_;
315 return $self->_result()->effective_itemtype();
318 =head3 home_branch
320 =cut
322 sub home_branch {
323 my ($self) = @_;
325 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
327 return $self->{_home_branch};
330 =head3 holding_branch
332 =cut
334 sub holding_branch {
335 my ($self) = @_;
337 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
339 return $self->{_holding_branch};
342 =head3 biblio
344 my $biblio = $item->biblio;
346 Return the bibliographic record of this item
348 =cut
350 sub biblio {
351 my ( $self ) = @_;
352 my $biblio_rs = $self->_result->biblio;
353 return Koha::Biblio->_new_from_dbic( $biblio_rs );
356 =head3 biblioitem
358 my $biblioitem = $item->biblioitem;
360 Return the biblioitem record of this item
362 =cut
364 sub biblioitem {
365 my ( $self ) = @_;
366 my $biblioitem_rs = $self->_result->biblioitem;
367 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
370 =head3 checkout
372 my $checkout = $item->checkout;
374 Return the checkout for this item
376 =cut
378 sub checkout {
379 my ( $self ) = @_;
380 my $checkout_rs = $self->_result->issue;
381 return unless $checkout_rs;
382 return Koha::Checkout->_new_from_dbic( $checkout_rs );
385 =head3 holds
387 my $holds = $item->holds();
388 my $holds = $item->holds($params);
389 my $holds = $item->holds({ found => 'W'});
391 Return holds attached to an item, optionally accept a hashref of params to pass to search
393 =cut
395 sub holds {
396 my ( $self,$params ) = @_;
397 my $holds_rs = $self->_result->reserves->search($params);
398 return Koha::Holds->_new_from_dbic( $holds_rs );
401 =head3 get_transfer
403 my $transfer = $item->get_transfer;
405 Return the transfer if the item is in transit or undef
407 =cut
409 sub get_transfer {
410 my ( $self ) = @_;
411 my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
412 return unless $transfer_rs;
413 return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
416 =head3 last_returned_by
418 Gets and sets the last borrower to return an item.
420 Accepts and returns Koha::Patron objects
422 $item->last_returned_by( $borrowernumber );
424 $last_returned_by = $item->last_returned_by();
426 =cut
428 sub last_returned_by {
429 my ( $self, $borrower ) = @_;
431 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
433 if ($borrower) {
434 return $items_last_returned_by_rs->update_or_create(
435 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
437 else {
438 unless ( $self->{_last_returned_by} ) {
439 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
440 if ($result) {
441 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
445 return $self->{_last_returned_by};
449 =head3 can_article_request
451 my $bool = $item->can_article_request( $borrower )
453 Returns true if item can be specifically requested
455 $borrower must be a Koha::Patron object
457 =cut
459 sub can_article_request {
460 my ( $self, $borrower ) = @_;
462 my $rule = $self->article_request_type($borrower);
464 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
465 return q{};
468 =head3 hidden_in_opac
470 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
472 Returns true if item fields match the hidding criteria defined in $rules.
473 Returns false otherwise.
475 Takes HASHref that can have the following parameters:
476 OPTIONAL PARAMETERS:
477 $rules : { <field> => [ value_1, ... ], ... }
479 Note: $rules inherits its structure from the parsed YAML from reading
480 the I<OpacHiddenItems> system preference.
482 =cut
484 sub hidden_in_opac {
485 my ( $self, $params ) = @_;
487 my $rules = $params->{rules} // {};
489 return 1
490 if C4::Context->preference('hidelostitems') and
491 $self->itemlost > 0;
493 my $hidden_in_opac = 0;
495 foreach my $field ( keys %{$rules} ) {
497 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
498 $hidden_in_opac = 1;
499 last;
503 return $hidden_in_opac;
506 =head3 can_be_transferred
508 $item->can_be_transferred({ to => $to_library, from => $from_library })
509 Checks if an item can be transferred to given library.
511 This feature is controlled by two system preferences:
512 UseBranchTransferLimits to enable / disable the feature
513 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
514 for setting the limitations
516 Takes HASHref that can have the following parameters:
517 MANDATORY PARAMETERS:
518 $to : Koha::Library
519 OPTIONAL PARAMETERS:
520 $from : Koha::Library # if not given, item holdingbranch
521 # will be used instead
523 Returns 1 if item can be transferred to $to_library, otherwise 0.
525 To find out whether at least one item of a Koha::Biblio can be transferred, please
526 see Koha::Biblio->can_be_transferred() instead of using this method for
527 multiple items of the same biblio.
529 =cut
531 sub can_be_transferred {
532 my ($self, $params) = @_;
534 my $to = $params->{to};
535 my $from = $params->{from};
537 $to = $to->branchcode;
538 $from = defined $from ? $from->branchcode : $self->holdingbranch;
540 return 1 if $from eq $to; # Transfer to current branch is allowed
541 return 1 unless C4::Context->preference('UseBranchTransferLimits');
543 my $limittype = C4::Context->preference('BranchTransferLimitsType');
544 return Koha::Item::Transfer::Limits->search({
545 toBranch => $to,
546 fromBranch => $from,
547 $limittype => $limittype eq 'itemtype'
548 ? $self->effective_itemtype : $self->ccode
549 })->count ? 0 : 1;
552 =head3 pickup_locations
554 $pickup_locations = $item->pickup_locations( {patron => $patron } )
556 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)
557 and if item can be transferred to each pickup location.
559 =cut
561 sub pickup_locations {
562 my ($self, $params) = @_;
564 my $patron = $params->{patron};
566 my $circ_control_branch =
567 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
568 my $branchitemrule =
569 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
571 my @libs;
572 if(defined $patron) {
573 return \@libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
574 return \@libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
577 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
578 @libs = $self->home_branch->get_hold_libraries;
579 push @libs, $self->home_branch unless scalar(@libs) > 0;
580 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
581 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
582 @libs = $plib->get_hold_libraries;
583 push @libs, $self->home_branch unless scalar(@libs) > 0;
584 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
585 push @libs, $self->home_branch;
586 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
587 push @libs, $self->holding_branch;
588 } else {
589 @libs = Koha::Libraries->search({
590 pickup_location => 1
591 }, {
592 order_by => ['branchname']
593 })->as_list;
596 my @pickup_locations;
597 foreach my $library (@libs) {
598 if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
599 push @pickup_locations, $library;
603 return \@pickup_locations;
606 =head3 article_request_type
608 my $type = $item->article_request_type( $borrower )
610 returns 'yes', 'no', 'bib_only', or 'item_only'
612 $borrower must be a Koha::Patron object
614 =cut
616 sub article_request_type {
617 my ( $self, $borrower ) = @_;
619 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
620 my $branchcode =
621 $branch_control eq 'homebranch' ? $self->homebranch
622 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
623 : undef;
624 my $borrowertype = $borrower->categorycode;
625 my $itemtype = $self->effective_itemtype();
626 my $rule = Koha::CirculationRules->get_effective_rule(
628 rule_name => 'article_requests',
629 categorycode => $borrowertype,
630 itemtype => $itemtype,
631 branchcode => $branchcode
635 return q{} unless $rule;
636 return $rule->rule_value || q{}
639 =head3 current_holds
641 =cut
643 sub current_holds {
644 my ( $self ) = @_;
645 my $attributes = { order_by => 'priority' };
646 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
647 my $params = {
648 itemnumber => $self->itemnumber,
649 suspend => 0,
650 -or => [
651 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
652 waitingdate => { '!=' => undef },
655 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
656 return Koha::Holds->_new_from_dbic($hold_rs);
659 =head3 stockrotationitem
661 my $sritem = Koha::Item->stockrotationitem;
663 Returns the stock rotation item associated with the current item.
665 =cut
667 sub stockrotationitem {
668 my ( $self ) = @_;
669 my $rs = $self->_result->stockrotationitem;
670 return 0 if !$rs;
671 return Koha::StockRotationItem->_new_from_dbic( $rs );
674 =head3 add_to_rota
676 my $item = $item->add_to_rota($rota_id);
678 Add this item to the rota identified by $ROTA_ID, which means associating it
679 with the first stage of that rota. Should this item already be associated
680 with a rota, then we will move it to the new rota.
682 =cut
684 sub add_to_rota {
685 my ( $self, $rota_id ) = @_;
686 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
687 return $self;
690 =head3 has_pending_hold
692 my $is_pending_hold = $item->has_pending_hold();
694 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
696 =cut
698 sub has_pending_hold {
699 my ( $self ) = @_;
700 my $pending_hold = $self->_result->tmp_holdsqueues;
701 return $pending_hold->count ? 1: 0;
704 =head3 as_marc_field
706 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
707 my $field = $item->as_marc_field({ [ mss => $mss ] });
709 This method returns a MARC::Field object representing the Koha::Item object
710 with the current mappings configuration.
712 =cut
714 sub as_marc_field {
715 my ( $self, $params ) = @_;
717 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
718 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
720 my @subfields;
722 my @columns = $self->_result->result_source->columns;
724 foreach my $item_field ( @columns ) {
725 my $mapping = $mss->{ "items.$item_field"}[0];
726 my $tagfield = $mapping->{tagfield};
727 my $tagsubfield = $mapping->{tagsubfield};
728 next if !$tagfield; # TODO: Should we raise an exception instead?
729 # Feels like safe fallback is better
731 push @subfields, $tagsubfield => $self->$item_field
732 if defined $self->$item_field and $item_field ne '';
735 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
736 push( @subfields, @{$unlinked_item_subfields} )
737 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
739 my $field;
741 $field = MARC::Field->new(
742 "$item_tag", ' ', ' ', @subfields
743 ) if @subfields;
745 return $field;
748 =head3 renewal_branchcode
750 Returns the branchcode to be recorded in statistics renewal of the item
752 =cut
754 sub renewal_branchcode {
756 my ($self, $params ) = @_;
758 my $interface = C4::Context->interface;
759 my $branchcode;
760 if ( $interface eq 'opac' ){
761 my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
762 if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
763 $branchcode = 'OPACRenew';
765 elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
766 $branchcode = $self->homebranch;
768 elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
769 $branchcode = $self->checkout->patron->branchcode;
771 elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
772 $branchcode = $self->checkout->branchcode;
774 else {
775 $branchcode = "";
777 } else {
778 $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
779 ? C4::Context->userenv->{branch} : $params->{branch};
781 return $branchcode;
784 =head3 _set_found_trigger
786 $self->_set_found_trigger
788 Finds the most recent lost item charge for this item and refunds the patron
789 appropriately, taking into account any payments or writeoffs already applied
790 against the charge.
792 Internal function, not exported, called only by Koha::Item->store.
794 =cut
796 sub _set_found_trigger {
797 my ( $self, $pre_mod_item ) = @_;
799 ## If item was lost, it has now been found, reverse any list item charges if necessary.
800 my $no_refund_after_days =
801 C4::Context->preference('NoRefundOnLostReturnedItemsAge');
802 if ($no_refund_after_days) {
803 my $today = dt_from_string();
804 my $lost_age_in_days =
805 dt_from_string( $pre_mod_item->itemlost_on )->delta_days($today)
806 ->in_units('days');
808 return $self unless $lost_age_in_days < $no_refund_after_days;
811 return $self
812 unless Koha::CirculationRules->get_lostreturn_policy(
814 current_branch => C4::Context->userenv->{branch},
815 item => $self,
819 # check for charge made for lost book
820 my $accountlines = Koha::Account::Lines->search(
822 itemnumber => $self->itemnumber,
823 debit_type_code => 'LOST',
824 status => [ undef, { '<>' => 'FOUND' } ]
827 order_by => { -desc => [ 'date', 'accountlines_id' ] }
831 return $self unless $accountlines->count > 0;
833 my $accountline = $accountlines->next;
834 my $total_to_refund = 0;
836 return $self unless $accountline->borrowernumber;
838 my $patron = Koha::Patrons->find( $accountline->borrowernumber );
839 return $self
840 unless $patron; # Patron has been deleted, nobody to credit the return to
841 # FIXME Should not we notify this somewhere
843 my $account = $patron->account;
845 # Use cases
846 if ( $accountline->amount > $accountline->amountoutstanding ) {
848 # some amount has been cancelled. collect the offsets that are not writeoffs
849 # this works because the only way to subtract from this kind of a debt is
850 # using the UI buttons 'Pay' and 'Write off'
851 my $credits_offsets = Koha::Account::Offsets->search(
853 debit_id => $accountline->id,
854 credit_id => { '!=' => undef }, # it is not the debit itself
855 type => { '!=' => 'Writeoff' },
856 amount => { '<' => 0 } # credits are negative on the DB
860 $total_to_refund = ( $credits_offsets->count > 0 )
861 ? $credits_offsets->total * -1 # credits are negative on the DB
862 : 0;
865 my $credit_total = $accountline->amountoutstanding + $total_to_refund;
867 my $credit;
868 if ( $credit_total > 0 ) {
869 my $branchcode =
870 C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
871 $credit = $account->add_credit(
873 amount => $credit_total,
874 description => 'Item found ' . $self->itemnumber,
875 type => 'LOST_FOUND',
876 interface => C4::Context->interface,
877 library_id => $branchcode,
878 item_id => $self->itemnumber,
879 issue_id => $accountline->issue_id
883 $credit->apply( { debits => [$accountline] } );
884 $self->{_refunded} = 1;
887 # Update the account status
888 $accountline->status('FOUND');
889 $accountline->store();
891 if ( defined $account and C4::Context->preference('AccountAutoReconcile') ) {
892 $account->reconcile_balance;
895 return $self;
898 =head3 to_api_mapping
900 This method returns the mapping for representing a Koha::Item object
901 on the API.
903 =cut
905 sub to_api_mapping {
906 return {
907 itemnumber => 'item_id',
908 biblionumber => 'biblio_id',
909 biblioitemnumber => undef,
910 barcode => 'external_id',
911 dateaccessioned => 'acquisition_date',
912 booksellerid => 'acquisition_source',
913 homebranch => 'home_library_id',
914 price => 'purchase_price',
915 replacementprice => 'replacement_price',
916 replacementpricedate => 'replacement_price_date',
917 datelastborrowed => 'last_checkout_date',
918 datelastseen => 'last_seen_date',
919 stack => undef,
920 notforloan => 'not_for_loan_status',
921 damaged => 'damaged_status',
922 damaged_on => 'damaged_date',
923 itemlost => 'lost_status',
924 itemlost_on => 'lost_date',
925 withdrawn => 'withdrawn',
926 withdrawn_on => 'withdrawn_date',
927 itemcallnumber => 'callnumber',
928 coded_location_qualifier => 'coded_location_qualifier',
929 issues => 'checkouts_count',
930 renewals => 'renewals_count',
931 reserves => 'holds_count',
932 restricted => 'restricted_status',
933 itemnotes => 'public_notes',
934 itemnotes_nonpublic => 'internal_notes',
935 holdingbranch => 'holding_library_id',
936 paidfor => undef,
937 timestamp => 'timestamp',
938 location => 'location',
939 permanent_location => 'permanent_location',
940 onloan => 'checked_out_date',
941 cn_source => 'call_number_source',
942 cn_sort => 'call_number_sort',
943 ccode => 'collection_code',
944 materials => 'materials_notes',
945 uri => 'uri',
946 itype => 'item_type',
947 more_subfields_xml => 'extended_subfields',
948 enumchron => 'serial_issue_number',
949 copynumber => 'copy_number',
950 stocknumber => 'inventory_number',
951 new_status => 'new_status'
955 =head3 itemtype
957 my $itemtype = $item->itemtype;
959 Returns Koha object for effective itemtype
961 =cut
963 sub itemtype {
964 my ( $self ) = @_;
965 return Koha::ItemTypes->find( $self->effective_itemtype );
968 =head2 Internal methods
970 =head3 _after_item_action_hooks
972 Helper method that takes care of calling all plugin hooks
974 =cut
976 sub _after_item_action_hooks {
977 my ( $self, $params ) = @_;
979 my $action = $params->{action};
981 Koha::Plugins->call(
982 'after_item_action',
984 action => $action,
985 item => $self,
986 item_id => $self->itemnumber,
991 =head3 _type
993 =cut
995 sub _type {
996 return 'Item';
999 =head1 AUTHOR
1001 Kyle M Hall <kyle@bywatersolutions.com>
1003 =cut