Bug 18501: Add _set_found_trigger
[koha.git] / Koha / Item.pm
blob5e03e515152837f23f4d83584148b6c12993954f
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 { # Update *_on fields if needed
125 # Why not for AddItem as well?
126 my @fields = qw( itemlost withdrawn damaged );
128 # Only retrieve the item if we need to set an "on" date field
129 if ( $self->itemlost || $self->withdrawn || $self->damaged ) {
130 my $pre_mod_item = $self->get_from_storage;
131 for my $field (@fields) {
132 if ( $self->$field
133 and not $pre_mod_item->$field )
135 my $field_on = "${field}_on";
136 $self->$field_on(
137 DateTime::Format::MySQL->format_datetime( dt_from_string() )
143 # If the field is defined but empty, we are removing and,
144 # and thus need to clear out the 'on' field as well
145 for my $field (@fields) {
146 if ( defined( $self->$field ) && !$self->$field ) {
147 my $field_on = "${field}_on";
148 $self->$field_on(undef);
153 my %updated_columns = $self->_result->get_dirty_columns;
154 return $self->SUPER::store unless %updated_columns;
156 if ( exists $updated_columns{itemcallnumber}
157 or exists $updated_columns{cn_source} )
159 my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
160 $self->cn_sort($cn_sort);
164 if ( exists $updated_columns{location}
165 and $self->location ne 'CART'
166 and $self->location ne 'PROC'
167 and not exists $updated_columns{permanent_location} )
169 $self->permanent_location( $self->location );
172 # If item was lost, it has now been found, reverse any list item charges if necessary.
173 if ( exists $updated_columns{itemlost}
174 and $self->itemlost != $updated_columns{itemlost}
175 and $updated_columns{itemlost} >= 1 ) {
176 $self->_set_found_trigger;
177 $self->paidfor('');
180 C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
181 unless $params->{skip_modzebra_update};
183 $self->_after_item_action_hooks({ action => 'modify' });
185 logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper($self->unblessed) )
186 if $log_action && C4::Context->preference("CataloguingLog");
189 unless ( $self->dateaccessioned ) {
190 $self->dateaccessioned($today);
193 return $self->SUPER::store;
196 =head3 delete
198 =cut
200 sub delete {
201 my $self = shift;
202 my $params = @_ ? shift : {};
204 # FIXME check the item has no current issues
205 # i.e. raise the appropriate exception
207 C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
208 unless $params->{skip_modzebra_update};
210 $self->_after_item_action_hooks({ action => 'delete' });
212 logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
213 if C4::Context->preference("CataloguingLog");
215 return $self->SUPER::delete;
218 =head3 safe_delete
220 =cut
222 sub safe_delete {
223 my $self = shift;
224 my $params = @_ ? shift : {};
226 my $safe_to_delete = $self->safe_to_delete;
227 return $safe_to_delete unless $safe_to_delete eq '1';
229 $self->move_to_deleted;
231 return $self->delete($params);
234 =head3 safe_to_delete
236 returns 1 if the item is safe to delete,
238 "book_on_loan" if the item is checked out,
240 "not_same_branch" if the item is blocked by independent branches,
242 "book_reserved" if the there are holds aganst the item, or
244 "linked_analytics" if the item has linked analytic records.
246 "last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
248 =cut
250 sub safe_to_delete {
251 my ($self) = @_;
253 return "book_on_loan" if $self->checkout;
255 return "not_same_branch"
256 if defined C4::Context->userenv
257 and !C4::Context->IsSuperLibrarian()
258 and C4::Context->preference("IndependentBranches")
259 and ( C4::Context->userenv->{branch} ne $self->homebranch );
261 # check it doesn't have a waiting reserve
262 return "book_reserved"
263 if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
265 return "linked_analytics"
266 if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
268 return "last_item_for_hold"
269 if $self->biblio->items->count == 1
270 && $self->biblio->holds->search(
272 itemnumber => undef,
274 )->count;
276 return 1;
279 =head3 move_to_deleted
281 my $is_moved = $item->move_to_deleted;
283 Move an item to the deleteditems table.
284 This can be done before deleting an item, to make sure the data are not completely deleted.
286 =cut
288 sub move_to_deleted {
289 my ($self) = @_;
290 my $item_infos = $self->unblessed;
291 delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
292 return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
296 =head3 effective_itemtype
298 Returns the itemtype for the item based on whether item level itemtypes are set or not.
300 =cut
302 sub effective_itemtype {
303 my ( $self ) = @_;
305 return $self->_result()->effective_itemtype();
308 =head3 home_branch
310 =cut
312 sub home_branch {
313 my ($self) = @_;
315 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
317 return $self->{_home_branch};
320 =head3 holding_branch
322 =cut
324 sub holding_branch {
325 my ($self) = @_;
327 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
329 return $self->{_holding_branch};
332 =head3 biblio
334 my $biblio = $item->biblio;
336 Return the bibliographic record of this item
338 =cut
340 sub biblio {
341 my ( $self ) = @_;
342 my $biblio_rs = $self->_result->biblio;
343 return Koha::Biblio->_new_from_dbic( $biblio_rs );
346 =head3 biblioitem
348 my $biblioitem = $item->biblioitem;
350 Return the biblioitem record of this item
352 =cut
354 sub biblioitem {
355 my ( $self ) = @_;
356 my $biblioitem_rs = $self->_result->biblioitem;
357 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
360 =head3 checkout
362 my $checkout = $item->checkout;
364 Return the checkout for this item
366 =cut
368 sub checkout {
369 my ( $self ) = @_;
370 my $checkout_rs = $self->_result->issue;
371 return unless $checkout_rs;
372 return Koha::Checkout->_new_from_dbic( $checkout_rs );
375 =head3 holds
377 my $holds = $item->holds();
378 my $holds = $item->holds($params);
379 my $holds = $item->holds({ found => 'W'});
381 Return holds attached to an item, optionally accept a hashref of params to pass to search
383 =cut
385 sub holds {
386 my ( $self,$params ) = @_;
387 my $holds_rs = $self->_result->reserves->search($params);
388 return Koha::Holds->_new_from_dbic( $holds_rs );
391 =head3 get_transfer
393 my $transfer = $item->get_transfer;
395 Return the transfer if the item is in transit or undef
397 =cut
399 sub get_transfer {
400 my ( $self ) = @_;
401 my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
402 return unless $transfer_rs;
403 return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
406 =head3 last_returned_by
408 Gets and sets the last borrower to return an item.
410 Accepts and returns Koha::Patron objects
412 $item->last_returned_by( $borrowernumber );
414 $last_returned_by = $item->last_returned_by();
416 =cut
418 sub last_returned_by {
419 my ( $self, $borrower ) = @_;
421 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
423 if ($borrower) {
424 return $items_last_returned_by_rs->update_or_create(
425 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
427 else {
428 unless ( $self->{_last_returned_by} ) {
429 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
430 if ($result) {
431 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
435 return $self->{_last_returned_by};
439 =head3 can_article_request
441 my $bool = $item->can_article_request( $borrower )
443 Returns true if item can be specifically requested
445 $borrower must be a Koha::Patron object
447 =cut
449 sub can_article_request {
450 my ( $self, $borrower ) = @_;
452 my $rule = $self->article_request_type($borrower);
454 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
455 return q{};
458 =head3 hidden_in_opac
460 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
462 Returns true if item fields match the hidding criteria defined in $rules.
463 Returns false otherwise.
465 Takes HASHref that can have the following parameters:
466 OPTIONAL PARAMETERS:
467 $rules : { <field> => [ value_1, ... ], ... }
469 Note: $rules inherits its structure from the parsed YAML from reading
470 the I<OpacHiddenItems> system preference.
472 =cut
474 sub hidden_in_opac {
475 my ( $self, $params ) = @_;
477 my $rules = $params->{rules} // {};
479 return 1
480 if C4::Context->preference('hidelostitems') and
481 $self->itemlost > 0;
483 my $hidden_in_opac = 0;
485 foreach my $field ( keys %{$rules} ) {
487 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
488 $hidden_in_opac = 1;
489 last;
493 return $hidden_in_opac;
496 =head3 can_be_transferred
498 $item->can_be_transferred({ to => $to_library, from => $from_library })
499 Checks if an item can be transferred to given library.
501 This feature is controlled by two system preferences:
502 UseBranchTransferLimits to enable / disable the feature
503 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
504 for setting the limitations
506 Takes HASHref that can have the following parameters:
507 MANDATORY PARAMETERS:
508 $to : Koha::Library
509 OPTIONAL PARAMETERS:
510 $from : Koha::Library # if not given, item holdingbranch
511 # will be used instead
513 Returns 1 if item can be transferred to $to_library, otherwise 0.
515 To find out whether at least one item of a Koha::Biblio can be transferred, please
516 see Koha::Biblio->can_be_transferred() instead of using this method for
517 multiple items of the same biblio.
519 =cut
521 sub can_be_transferred {
522 my ($self, $params) = @_;
524 my $to = $params->{to};
525 my $from = $params->{from};
527 $to = $to->branchcode;
528 $from = defined $from ? $from->branchcode : $self->holdingbranch;
530 return 1 if $from eq $to; # Transfer to current branch is allowed
531 return 1 unless C4::Context->preference('UseBranchTransferLimits');
533 my $limittype = C4::Context->preference('BranchTransferLimitsType');
534 return Koha::Item::Transfer::Limits->search({
535 toBranch => $to,
536 fromBranch => $from,
537 $limittype => $limittype eq 'itemtype'
538 ? $self->effective_itemtype : $self->ccode
539 })->count ? 0 : 1;
542 =head3 pickup_locations
544 $pickup_locations = $item->pickup_locations( {patron => $patron } )
546 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)
547 and if item can be transferred to each pickup location.
549 =cut
551 sub pickup_locations {
552 my ($self, $params) = @_;
554 my $patron = $params->{patron};
556 my $circ_control_branch =
557 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
558 my $branchitemrule =
559 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
561 my @libs;
562 if(defined $patron) {
563 return \@libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
564 return \@libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
567 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
568 @libs = $self->home_branch->get_hold_libraries;
569 push @libs, $self->home_branch unless scalar(@libs) > 0;
570 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
571 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
572 @libs = $plib->get_hold_libraries;
573 push @libs, $self->home_branch unless scalar(@libs) > 0;
574 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
575 push @libs, $self->home_branch;
576 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
577 push @libs, $self->holding_branch;
578 } else {
579 @libs = Koha::Libraries->search({
580 pickup_location => 1
581 }, {
582 order_by => ['branchname']
583 })->as_list;
586 my @pickup_locations;
587 foreach my $library (@libs) {
588 if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
589 push @pickup_locations, $library;
593 return \@pickup_locations;
596 =head3 article_request_type
598 my $type = $item->article_request_type( $borrower )
600 returns 'yes', 'no', 'bib_only', or 'item_only'
602 $borrower must be a Koha::Patron object
604 =cut
606 sub article_request_type {
607 my ( $self, $borrower ) = @_;
609 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
610 my $branchcode =
611 $branch_control eq 'homebranch' ? $self->homebranch
612 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
613 : undef;
614 my $borrowertype = $borrower->categorycode;
615 my $itemtype = $self->effective_itemtype();
616 my $rule = Koha::CirculationRules->get_effective_rule(
618 rule_name => 'article_requests',
619 categorycode => $borrowertype,
620 itemtype => $itemtype,
621 branchcode => $branchcode
625 return q{} unless $rule;
626 return $rule->rule_value || q{}
629 =head3 current_holds
631 =cut
633 sub current_holds {
634 my ( $self ) = @_;
635 my $attributes = { order_by => 'priority' };
636 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
637 my $params = {
638 itemnumber => $self->itemnumber,
639 suspend => 0,
640 -or => [
641 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
642 waitingdate => { '!=' => undef },
645 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
646 return Koha::Holds->_new_from_dbic($hold_rs);
649 =head3 stockrotationitem
651 my $sritem = Koha::Item->stockrotationitem;
653 Returns the stock rotation item associated with the current item.
655 =cut
657 sub stockrotationitem {
658 my ( $self ) = @_;
659 my $rs = $self->_result->stockrotationitem;
660 return 0 if !$rs;
661 return Koha::StockRotationItem->_new_from_dbic( $rs );
664 =head3 add_to_rota
666 my $item = $item->add_to_rota($rota_id);
668 Add this item to the rota identified by $ROTA_ID, which means associating it
669 with the first stage of that rota. Should this item already be associated
670 with a rota, then we will move it to the new rota.
672 =cut
674 sub add_to_rota {
675 my ( $self, $rota_id ) = @_;
676 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
677 return $self;
680 =head3 has_pending_hold
682 my $is_pending_hold = $item->has_pending_hold();
684 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
686 =cut
688 sub has_pending_hold {
689 my ( $self ) = @_;
690 my $pending_hold = $self->_result->tmp_holdsqueues;
691 return $pending_hold->count ? 1: 0;
694 =head3 as_marc_field
696 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
697 my $field = $item->as_marc_field({ [ mss => $mss ] });
699 This method returns a MARC::Field object representing the Koha::Item object
700 with the current mappings configuration.
702 =cut
704 sub as_marc_field {
705 my ( $self, $params ) = @_;
707 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
708 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
710 my @subfields;
712 my @columns = $self->_result->result_source->columns;
714 foreach my $item_field ( @columns ) {
715 my $mapping = $mss->{ "items.$item_field"}[0];
716 my $tagfield = $mapping->{tagfield};
717 my $tagsubfield = $mapping->{tagsubfield};
718 next if !$tagfield; # TODO: Should we raise an exception instead?
719 # Feels like safe fallback is better
721 push @subfields, $tagsubfield => $self->$item_field
722 if defined $self->$item_field and $item_field ne '';
725 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
726 push( @subfields, @{$unlinked_item_subfields} )
727 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
729 my $field;
731 $field = MARC::Field->new(
732 "$item_tag", ' ', ' ', @subfields
733 ) if @subfields;
735 return $field;
738 =head3 renewal_branchcode
740 Returns the branchcode to be recorded in statistics renewal of the item
742 =cut
744 sub renewal_branchcode {
746 my ($self, $params ) = @_;
748 my $interface = C4::Context->interface;
749 my $branchcode;
750 if ( $interface eq 'opac' ){
751 my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
752 if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
753 $branchcode = 'OPACRenew';
755 elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
756 $branchcode = $self->homebranch;
758 elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
759 $branchcode = $self->checkout->patron->branchcode;
761 elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
762 $branchcode = $self->checkout->branchcode;
764 else {
765 $branchcode = "";
767 } else {
768 $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
769 ? C4::Context->userenv->{branch} : $params->{branch};
771 return $branchcode;
774 =head3 _set_found_trigger
776 $self->_set_found_trigger
778 Finds the most recent lost item charge for this item and refunds the patron
779 appropriatly, taking into account any payments or writeoffs already applied
780 against the charge.
782 Internal function, not exported, called only by Koha::Item->store.
784 =cut
786 sub _set_found_trigger {
787 my ( $self, $params ) = @_;
789 ## If item was lost, it has now been found, reverse any list item charges if necessary.
790 my $refund = 1;
791 my $no_refund_after_days =
792 C4::Context->preference('NoRefundOnLostReturnedItemsAge');
793 if ($no_refund_after_days) {
794 my $today = dt_from_string();
795 my $lost_age_in_days =
796 dt_from_string( $self->itemlost_on )->delta_days($today)
797 ->in_units('days');
799 return $self unless $lost_age_in_days < $no_refund_after_days;
802 return $self
803 unless Koha::CirculationRules->get_lostreturn_policy(
805 current_branch => C4::Context->userenv->{branch},
806 item => $self,
810 # check for charge made for lost book
811 my $accountlines = Koha::Account::Lines->search(
813 itemnumber => $self->itemnumber,
814 debit_type_code => 'LOST',
815 status => [ undef, { '<>' => 'FOUND' } ]
818 order_by => { -desc => [ 'date', 'accountlines_id' ] }
822 return $self unless $accountlines->count > 0;
824 my $accountline = $accountlines->next;
825 my $total_to_refund = 0;
827 return $self unless $accountline->borrowernumber;
829 my $patron = Koha::Patrons->find( $accountline->borrowernumber );
830 return $self
831 unless $patron; # Patron has been deleted, nobody to credit the return to
832 # FIXME Should not we notify this somehwere
834 my $account = $patron->account;
836 # Use cases
837 if ( $accountline->amount > $accountline->amountoutstanding ) {
839 # some amount has been cancelled. collect the offsets that are not writeoffs
840 # this works because the only way to subtract from this kind of a debt is
841 # using the UI buttons 'Pay' and 'Write off'
842 my $credits_offsets = Koha::Account::Offsets->search(
844 debit_id => $accountline->id,
845 credit_id => { '!=' => undef }, # it is not the debit itself
846 type => { '!=' => 'Writeoff' },
847 amount => { '<' => 0 } # credits are negative on the DB
851 $total_to_refund = ( $credits_offsets->count > 0 )
852 ? $credits_offsets->total * -1 # credits are negative on the DB
853 : 0;
856 my $credit_total = $accountline->amountoutstanding + $total_to_refund;
858 my $credit;
859 if ( $credit_total > 0 ) {
860 my $branchcode =
861 C4::Context->userenv ? C4::Context->userenv->{'branch'} : undef;
862 $credit = $account->add_credit(
864 amount => $credit_total,
865 description => 'Item found ' . $item_id,
866 type => 'LOST_FOUND',
867 interface => C4::Context->interface,
868 library_id => $branchcode,
869 item_id => $itemnumber
873 $credit->apply( { debits => [$accountline] } );
876 # Update the account status
877 $accountline->discard_changes->status('FOUND')
878 ; # FIXME JD Why discard_changes? $accountline has not been modified since last fetch
879 $accountline->store;
881 if ( defined $account and C4::Context->preference('AccountAutoReconcile') ) {
882 $account->reconcile_balance;
885 return $self;
888 =head3 to_api_mapping
890 This method returns the mapping for representing a Koha::Item object
891 on the API.
893 =cut
895 sub to_api_mapping {
896 return {
897 itemnumber => 'item_id',
898 biblionumber => 'biblio_id',
899 biblioitemnumber => undef,
900 barcode => 'external_id',
901 dateaccessioned => 'acquisition_date',
902 booksellerid => 'acquisition_source',
903 homebranch => 'home_library_id',
904 price => 'purchase_price',
905 replacementprice => 'replacement_price',
906 replacementpricedate => 'replacement_price_date',
907 datelastborrowed => 'last_checkout_date',
908 datelastseen => 'last_seen_date',
909 stack => undef,
910 notforloan => 'not_for_loan_status',
911 damaged => 'damaged_status',
912 damaged_on => 'damaged_date',
913 itemlost => 'lost_status',
914 itemlost_on => 'lost_date',
915 withdrawn => 'withdrawn',
916 withdrawn_on => 'withdrawn_date',
917 itemcallnumber => 'callnumber',
918 coded_location_qualifier => 'coded_location_qualifier',
919 issues => 'checkouts_count',
920 renewals => 'renewals_count',
921 reserves => 'holds_count',
922 restricted => 'restricted_status',
923 itemnotes => 'public_notes',
924 itemnotes_nonpublic => 'internal_notes',
925 holdingbranch => 'holding_library_id',
926 paidfor => undef,
927 timestamp => 'timestamp',
928 location => 'location',
929 permanent_location => 'permanent_location',
930 onloan => 'checked_out_date',
931 cn_source => 'call_number_source',
932 cn_sort => 'call_number_sort',
933 ccode => 'collection_code',
934 materials => 'materials_notes',
935 uri => 'uri',
936 itype => 'item_type',
937 more_subfields_xml => 'extended_subfields',
938 enumchron => 'serial_issue_number',
939 copynumber => 'copy_number',
940 stocknumber => 'inventory_number',
941 new_status => 'new_status'
945 =head3 itemtype
947 my $itemtype = $item->itemtype;
949 Returns Koha object for effective itemtype
951 =cut
953 sub itemtype {
954 my ( $self ) = @_;
955 return Koha::ItemTypes->find( $self->effective_itemtype );
958 =head2 Internal methods
960 =head3 _after_item_action_hooks
962 Helper method that takes care of calling all plugin hooks
964 =cut
966 sub _after_item_action_hooks {
967 my ( $self, $params ) = @_;
969 my $action = $params->{action};
971 Koha::Plugins->call(
972 'after_item_action',
974 action => $action,
975 item => $self,
976 item_id => $self->itemnumber,
981 =head3 _type
983 =cut
985 sub _type {
986 return 'Item';
989 =head1 AUTHOR
991 Kyle M Hall <kyle@bywatersolutions.com>
993 =cut