Bug 26519: Clean up OPAC buttons with incorrect classes
[koha.git] / Koha / Item.pm
blob5b12115932fcba9c10f583a8322dda908397b4d1
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::ClassSource; # FIXME We would like to avoid that
34 use C4::Log qw( logaction );
36 use Koha::Checkouts;
37 use Koha::CirculationRules;
38 use Koha::SearchEngine::Indexer;
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_record_index' parameter.
65 If set, the reindexation process will not happen (index_records 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 my $plugin_action = 'create';
94 unless ( $self->in_storage ) { #AddItem
95 unless ( $self->permanent_location ) {
96 $self->permanent_location($self->location);
98 unless ( $self->replacementpricedate ) {
99 $self->replacementpricedate($today);
101 unless ( $self->datelastseen ) {
102 $self->datelastseen($today);
105 unless ( $self->dateaccessioned ) {
106 $self->dateaccessioned($today);
109 if ( $self->itemcallnumber
110 or $self->cn_source )
112 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
113 $self->cn_sort($cn_sort);
116 logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
117 if $log_action && C4::Context->preference("CataloguingLog");
119 } else { # ModItem
121 $plugin_action = 'modify';
123 my %updated_columns = $self->_result->get_dirty_columns;
124 return $self->SUPER::store unless %updated_columns;
126 # Retrieve the item for comparison if we need to
127 my $pre_mod_item = (
128 exists $updated_columns{itemlost}
129 or exists $updated_columns{withdrawn}
130 or exists $updated_columns{damaged}
131 ) ? $self->get_from_storage : undef;
133 # Update *_on fields if needed
134 # FIXME: Why not for AddItem as well?
135 my @fields = qw( itemlost withdrawn damaged );
136 for my $field (@fields) {
138 # If the field is defined but empty or 0, we are
139 # removing/unsetting and thus need to clear out
140 # the 'on' field
141 if ( exists $updated_columns{$field}
142 && defined( $self->$field )
143 && !$self->$field )
145 my $field_on = "${field}_on";
146 $self->$field_on(undef);
148 # If the field has changed otherwise, we much update
149 # the 'on' field
150 elsif (exists $updated_columns{$field}
151 && $updated_columns{$field}
152 && !$pre_mod_item->$field )
154 my $field_on = "${field}_on";
155 $self->$field_on(
156 DateTime::Format::MySQL->format_datetime(
157 dt_from_string()
163 if ( exists $updated_columns{itemcallnumber}
164 or exists $updated_columns{cn_source} )
166 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
167 $self->cn_sort($cn_sort);
171 if ( exists $updated_columns{location}
172 and $self->location ne 'CART'
173 and $self->location ne 'PROC'
174 and not exists $updated_columns{permanent_location} )
176 $self->permanent_location( $self->location );
179 # If item was lost and has now been found,
180 # reverse any list item charges if necessary.
181 if ( exists $updated_columns{itemlost}
182 and $updated_columns{itemlost} <= 0
183 and $pre_mod_item->itemlost > 0 )
185 $self->_set_found_trigger($pre_mod_item);
188 logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper($self->unblessed) )
189 if $log_action && C4::Context->preference("CataloguingLog");
192 unless ( $self->dateaccessioned ) {
193 $self->dateaccessioned($today);
196 my $result = $self->SUPER::store;
197 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
198 $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
199 unless $params->{skip_record_index};
200 $self->get_from_storage->_after_item_action_hooks({ action => $plugin_action });
202 return $result;
205 =head3 delete
207 =cut
209 sub delete {
210 my $self = shift;
211 my $params = @_ ? shift : {};
213 # FIXME check the item has no current issues
214 # i.e. raise the appropriate exception
216 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
217 $indexer->index_records( $self->biblionumber, "specialUpdate", "biblioserver" )
218 unless $params->{skip_record_index};
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 item => $self,
815 return_branch => C4::Context->userenv
816 ? C4::Context->userenv->{'branch'}
817 : undef,
821 # check for charge made for lost book
822 my $accountlines = Koha::Account::Lines->search(
824 itemnumber => $self->itemnumber,
825 debit_type_code => 'LOST',
826 status => [ undef, { '<>' => 'FOUND' } ]
829 order_by => { -desc => [ 'date', 'accountlines_id' ] }
833 return $self unless $accountlines->count > 0;
835 my $accountline = $accountlines->next;
836 my $total_to_refund = 0;
838 return $self unless $accountline->borrowernumber;
840 my $patron = Koha::Patrons->find( $accountline->borrowernumber );
841 return $self
842 unless $patron; # Patron has been deleted, nobody to credit the return to
843 # FIXME Should not we notify this somewhere
845 my $account = $patron->account;
847 # Use cases
848 if ( $accountline->amount > $accountline->amountoutstanding ) {
850 # some amount has been cancelled. collect the offsets that are not writeoffs
851 # this works because the only way to subtract from this kind of a debt is
852 # using the UI buttons 'Pay' and 'Write off'
853 my $credits_offsets = Koha::Account::Offsets->search(
855 debit_id => $accountline->id,
856 credit_id => { '!=' => undef }, # it is not the debit itself
857 type => { '!=' => 'Writeoff' },
858 amount => { '<' => 0 } # credits are negative on the DB
862 $total_to_refund = ( $credits_offsets->count > 0 )
863 ? $credits_offsets->total * -1 # credits are negative on the DB
864 : 0;
867 my $credit_total = $accountline->amountoutstanding + $total_to_refund;
869 my $credit;
870 if ( $credit_total > 0 ) {
871 my $branchcode =
872 C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
873 $credit = $account->add_credit(
875 amount => $credit_total,
876 description => 'Item found ' . $self->itemnumber,
877 type => 'LOST_FOUND',
878 interface => C4::Context->interface,
879 library_id => $branchcode,
880 item_id => $self->itemnumber,
881 issue_id => $accountline->issue_id
885 $credit->apply( { debits => [$accountline] } );
886 $self->{_refunded} = 1;
889 # Update the account status
890 $accountline->status('FOUND');
891 $accountline->store();
893 if ( defined $account and C4::Context->preference('AccountAutoReconcile') ) {
894 $account->reconcile_balance;
897 return $self;
900 =head3 to_api_mapping
902 This method returns the mapping for representing a Koha::Item object
903 on the API.
905 =cut
907 sub to_api_mapping {
908 return {
909 itemnumber => 'item_id',
910 biblionumber => 'biblio_id',
911 biblioitemnumber => undef,
912 barcode => 'external_id',
913 dateaccessioned => 'acquisition_date',
914 booksellerid => 'acquisition_source',
915 homebranch => 'home_library_id',
916 price => 'purchase_price',
917 replacementprice => 'replacement_price',
918 replacementpricedate => 'replacement_price_date',
919 datelastborrowed => 'last_checkout_date',
920 datelastseen => 'last_seen_date',
921 stack => undef,
922 notforloan => 'not_for_loan_status',
923 damaged => 'damaged_status',
924 damaged_on => 'damaged_date',
925 itemlost => 'lost_status',
926 itemlost_on => 'lost_date',
927 withdrawn => 'withdrawn',
928 withdrawn_on => 'withdrawn_date',
929 itemcallnumber => 'callnumber',
930 coded_location_qualifier => 'coded_location_qualifier',
931 issues => 'checkouts_count',
932 renewals => 'renewals_count',
933 reserves => 'holds_count',
934 restricted => 'restricted_status',
935 itemnotes => 'public_notes',
936 itemnotes_nonpublic => 'internal_notes',
937 holdingbranch => 'holding_library_id',
938 timestamp => 'timestamp',
939 location => 'location',
940 permanent_location => 'permanent_location',
941 onloan => 'checked_out_date',
942 cn_source => 'call_number_source',
943 cn_sort => 'call_number_sort',
944 ccode => 'collection_code',
945 materials => 'materials_notes',
946 uri => 'uri',
947 itype => 'item_type',
948 more_subfields_xml => 'extended_subfields',
949 enumchron => 'serial_issue_number',
950 copynumber => 'copy_number',
951 stocknumber => 'inventory_number',
952 new_status => 'new_status'
956 =head3 itemtype
958 my $itemtype = $item->itemtype;
960 Returns Koha object for effective itemtype
962 =cut
964 sub itemtype {
965 my ( $self ) = @_;
966 return Koha::ItemTypes->find( $self->effective_itemtype );
969 =head2 Internal methods
971 =head3 _after_item_action_hooks
973 Helper method that takes care of calling all plugin hooks
975 =cut
977 sub _after_item_action_hooks {
978 my ( $self, $params ) = @_;
980 my $action = $params->{action};
982 Koha::Plugins->call(
983 'after_item_action',
985 action => $action,
986 item => $self,
987 item_id => $self->itemnumber,
992 =head3 _type
994 =cut
996 sub _type {
997 return 'Item';
1000 =head1 AUTHOR
1002 Kyle M Hall <kyle@bywatersolutions.com>
1004 =cut