Bug 18501: Prepare the ground
[koha.git] / Koha / Item.pm
1 package Koha::Item;
2
3 # Copyright ByWater Solutions 2014
4 #
5 # This file is part of Koha.
6 #
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.
11 #
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.
16 #
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>.
19
20 use Modern::Perl;
21
22 use Carp;
23 use List::MoreUtils qw(any);
24 use Data::Dumper;
25 use Try::Tiny;
26
27 use Koha::Database;
28 use Koha::DateUtils qw( dt_from_string );
29
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 );
36
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;
47
48 use base qw(Koha::Object);
49
50 =head1 NAME
51
52 Koha::Item - Koha Item object class
53
54 =head1 API
55
56 =head2 Class methods
57
58 =cut
59
60 =head3 store
61
62     $item->store;
63
64 $params can take an optional 'skip_modzebra_update' parameter.
65 If set, the reindexation process will not happen (ModZebra not called)
66
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.
71
72 =cut
73
74 sub store {
75     my $self = shift;
76     my $params = @_ ? shift : {};
77
78     my $log_action = $params->{log_action} // 1;
79
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 );
84     }
85
86     # See related changes from C4::Items::AddItem
87     unless ( $self->itype ) {
88         $self->itype($self->biblio->biblioitem->itemtype);
89     }
90
91     my $today = dt_from_string;
92     unless ( $self->in_storage ) { #AddItem
93         unless ( $self->permanent_location ) {
94             $self->permanent_location($self->location);
95         }
96         unless ( $self->replacementpricedate ) {
97             $self->replacementpricedate($today);
98         }
99         unless ( $self->datelastseen ) {
100             $self->datelastseen($today);
101         }
102
103         unless ( $self->dateaccessioned ) {
104             $self->dateaccessioned($today);
105         }
106
107         if (   $self->itemcallnumber
108             or $self->cn_source )
109         {
110             my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
111             $self->cn_sort($cn_sort);
112         }
113
114         C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
115             unless $params->{skip_modzebra_update};
116
117         logaction( "CATALOGUING", "ADD", $self->itemnumber, "item" )
118           if $log_action && C4::Context->preference("CataloguingLog");
119
120         $self->_after_item_action_hooks({ action => 'create' });
121
122     } else { # ModItem
123
124         { # Update *_on  fields if needed
125           # Why not for AddItem as well?
126             my @fields = qw( itemlost withdrawn damaged );
127
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 )
134                     {
135                         my $field_on = "${field}_on";
136                         $self->$field_on(
137                           DateTime::Format::MySQL->format_datetime( dt_from_string() )
138                         );
139                     }
140                 }
141             }
142
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);
149                 }
150             }
151         }
152
153         my %updated_columns = $self->_result->get_dirty_columns;
154         return $self->SUPER::store unless %updated_columns;
155
156         if (   exists $updated_columns{itemcallnumber}
157             or exists $updated_columns{cn_source} )
158         {
159             my $cn_sort = GetClassSort( $self->cn_source, $self->itemcallnumber, "" );
160             $self->cn_sort($cn_sort);
161         }
162
163
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} )
168         {
169             $self->permanent_location( $self->location );
170         }
171
172         C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
173             unless $params->{skip_modzebra_update};
174
175         $self->_after_item_action_hooks({ action => 'modify' });
176
177         logaction( "CATALOGUING", "MODIFY", $self->itemnumber, "item " . Dumper($self->unblessed) )
178           if $log_action && C4::Context->preference("CataloguingLog");
179     }
180
181     unless ( $self->dateaccessioned ) {
182         $self->dateaccessioned($today);
183     }
184
185     return $self->SUPER::store;
186 }
187
188 =head3 delete
189
190 =cut
191
192 sub delete {
193     my $self = shift;
194     my $params = @_ ? shift : {};
195
196     # FIXME check the item has no current issues
197     # i.e. raise the appropriate exception
198
199     C4::Biblio::ModZebra( $self->biblionumber, "specialUpdate", "biblioserver" )
200         unless $params->{skip_modzebra_update};
201
202     $self->_after_item_action_hooks({ action => 'delete' });
203
204     logaction( "CATALOGUING", "DELETE", $self->itemnumber, "item" )
205       if C4::Context->preference("CataloguingLog");
206
207     return $self->SUPER::delete;
208 }
209
210 =head3 safe_delete
211
212 =cut
213
214 sub safe_delete {
215     my $self = shift;
216     my $params = @_ ? shift : {};
217
218     my $safe_to_delete = $self->safe_to_delete;
219     return $safe_to_delete unless $safe_to_delete eq '1';
220
221     $self->move_to_deleted;
222
223     return $self->delete($params);
224 }
225
226 =head3 safe_to_delete
227
228 returns 1 if the item is safe to delete,
229
230 "book_on_loan" if the item is checked out,
231
232 "not_same_branch" if the item is blocked by independent branches,
233
234 "book_reserved" if the there are holds aganst the item, or
235
236 "linked_analytics" if the item has linked analytic records.
237
238 "last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
239
240 =cut
241
242 sub safe_to_delete {
243     my ($self) = @_;
244
245     return "book_on_loan" if $self->checkout;
246
247     return "not_same_branch"
248       if defined C4::Context->userenv
249       and !C4::Context->IsSuperLibrarian()
250       and C4::Context->preference("IndependentBranches")
251       and ( C4::Context->userenv->{branch} ne $self->homebranch );
252
253     # check it doesn't have a waiting reserve
254     return "book_reserved"
255       if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
256
257     return "linked_analytics"
258       if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
259
260     return "last_item_for_hold"
261       if $self->biblio->items->count == 1
262       && $self->biblio->holds->search(
263           {
264               itemnumber => undef,
265           }
266         )->count;
267
268     return 1;
269 }
270
271 =head3 move_to_deleted
272
273 my $is_moved = $item->move_to_deleted;
274
275 Move an item to the deleteditems table.
276 This can be done before deleting an item, to make sure the data are not completely deleted.
277
278 =cut
279
280 sub move_to_deleted {
281     my ($self) = @_;
282     my $item_infos = $self->unblessed;
283     delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
284     return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
285 }
286
287
288 =head3 effective_itemtype
289
290 Returns the itemtype for the item based on whether item level itemtypes are set or not.
291
292 =cut
293
294 sub effective_itemtype {
295     my ( $self ) = @_;
296
297     return $self->_result()->effective_itemtype();
298 }
299
300 =head3 home_branch
301
302 =cut
303
304 sub home_branch {
305     my ($self) = @_;
306
307     $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
308
309     return $self->{_home_branch};
310 }
311
312 =head3 holding_branch
313
314 =cut
315
316 sub holding_branch {
317     my ($self) = @_;
318
319     $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
320
321     return $self->{_holding_branch};
322 }
323
324 =head3 biblio
325
326 my $biblio = $item->biblio;
327
328 Return the bibliographic record of this item
329
330 =cut
331
332 sub biblio {
333     my ( $self ) = @_;
334     my $biblio_rs = $self->_result->biblio;
335     return Koha::Biblio->_new_from_dbic( $biblio_rs );
336 }
337
338 =head3 biblioitem
339
340 my $biblioitem = $item->biblioitem;
341
342 Return the biblioitem record of this item
343
344 =cut
345
346 sub biblioitem {
347     my ( $self ) = @_;
348     my $biblioitem_rs = $self->_result->biblioitem;
349     return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
350 }
351
352 =head3 checkout
353
354 my $checkout = $item->checkout;
355
356 Return the checkout for this item
357
358 =cut
359
360 sub checkout {
361     my ( $self ) = @_;
362     my $checkout_rs = $self->_result->issue;
363     return unless $checkout_rs;
364     return Koha::Checkout->_new_from_dbic( $checkout_rs );
365 }
366
367 =head3 holds
368
369 my $holds = $item->holds();
370 my $holds = $item->holds($params);
371 my $holds = $item->holds({ found => 'W'});
372
373 Return holds attached to an item, optionally accept a hashref of params to pass to search
374
375 =cut
376
377 sub holds {
378     my ( $self,$params ) = @_;
379     my $holds_rs = $self->_result->reserves->search($params);
380     return Koha::Holds->_new_from_dbic( $holds_rs );
381 }
382
383 =head3 get_transfer
384
385 my $transfer = $item->get_transfer;
386
387 Return the transfer if the item is in transit or undef
388
389 =cut
390
391 sub get_transfer {
392     my ( $self ) = @_;
393     my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
394     return unless $transfer_rs;
395     return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
396 }
397
398 =head3 last_returned_by
399
400 Gets and sets the last borrower to return an item.
401
402 Accepts and returns Koha::Patron objects
403
404 $item->last_returned_by( $borrowernumber );
405
406 $last_returned_by = $item->last_returned_by();
407
408 =cut
409
410 sub last_returned_by {
411     my ( $self, $borrower ) = @_;
412
413     my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
414
415     if ($borrower) {
416         return $items_last_returned_by_rs->update_or_create(
417             { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
418     }
419     else {
420         unless ( $self->{_last_returned_by} ) {
421             my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
422             if ($result) {
423                 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
424             }
425         }
426
427         return $self->{_last_returned_by};
428     }
429 }
430
431 =head3 can_article_request
432
433 my $bool = $item->can_article_request( $borrower )
434
435 Returns true if item can be specifically requested
436
437 $borrower must be a Koha::Patron object
438
439 =cut
440
441 sub can_article_request {
442     my ( $self, $borrower ) = @_;
443
444     my $rule = $self->article_request_type($borrower);
445
446     return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
447     return q{};
448 }
449
450 =head3 hidden_in_opac
451
452 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
453
454 Returns true if item fields match the hidding criteria defined in $rules.
455 Returns false otherwise.
456
457 Takes HASHref that can have the following parameters:
458     OPTIONAL PARAMETERS:
459     $rules : { <field> => [ value_1, ... ], ... }
460
461 Note: $rules inherits its structure from the parsed YAML from reading
462 the I<OpacHiddenItems> system preference.
463
464 =cut
465
466 sub hidden_in_opac {
467     my ( $self, $params ) = @_;
468
469     my $rules = $params->{rules} // {};
470
471     return 1
472         if C4::Context->preference('hidelostitems') and
473            $self->itemlost > 0;
474
475     my $hidden_in_opac = 0;
476
477     foreach my $field ( keys %{$rules} ) {
478
479         if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
480             $hidden_in_opac = 1;
481             last;
482         }
483     }
484
485     return $hidden_in_opac;
486 }
487
488 =head3 can_be_transferred
489
490 $item->can_be_transferred({ to => $to_library, from => $from_library })
491 Checks if an item can be transferred to given library.
492
493 This feature is controlled by two system preferences:
494 UseBranchTransferLimits to enable / disable the feature
495 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
496                          for setting the limitations
497
498 Takes HASHref that can have the following parameters:
499     MANDATORY PARAMETERS:
500     $to   : Koha::Library
501     OPTIONAL PARAMETERS:
502     $from : Koha::Library  # if not given, item holdingbranch
503                            # will be used instead
504
505 Returns 1 if item can be transferred to $to_library, otherwise 0.
506
507 To find out whether at least one item of a Koha::Biblio can be transferred, please
508 see Koha::Biblio->can_be_transferred() instead of using this method for
509 multiple items of the same biblio.
510
511 =cut
512
513 sub can_be_transferred {
514     my ($self, $params) = @_;
515
516     my $to   = $params->{to};
517     my $from = $params->{from};
518
519     $to   = $to->branchcode;
520     $from = defined $from ? $from->branchcode : $self->holdingbranch;
521
522     return 1 if $from eq $to; # Transfer to current branch is allowed
523     return 1 unless C4::Context->preference('UseBranchTransferLimits');
524
525     my $limittype = C4::Context->preference('BranchTransferLimitsType');
526     return Koha::Item::Transfer::Limits->search({
527         toBranch => $to,
528         fromBranch => $from,
529         $limittype => $limittype eq 'itemtype'
530                         ? $self->effective_itemtype : $self->ccode
531     })->count ? 0 : 1;
532 }
533
534 =head3 pickup_locations
535
536 $pickup_locations = $item->pickup_locations( {patron => $patron } )
537
538 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)
539 and if item can be transferred to each pickup location.
540
541 =cut
542
543 sub pickup_locations {
544     my ($self, $params) = @_;
545
546     my $patron = $params->{patron};
547
548     my $circ_control_branch =
549       C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
550     my $branchitemrule =
551       C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
552
553     my @libs;
554     if(defined $patron) {
555         return \@libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
556         return \@libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
557     }
558
559     if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
560         @libs  = $self->home_branch->get_hold_libraries;
561         push @libs, $self->home_branch unless scalar(@libs) > 0;
562     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
563         my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
564         @libs  = $plib->get_hold_libraries;
565         push @libs, $self->home_branch unless scalar(@libs) > 0;
566     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
567         push @libs, $self->home_branch;
568     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
569         push @libs, $self->holding_branch;
570     } else {
571         @libs = Koha::Libraries->search({
572             pickup_location => 1
573         }, {
574             order_by => ['branchname']
575         })->as_list;
576     }
577
578     my @pickup_locations;
579     foreach my $library (@libs) {
580         if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
581             push @pickup_locations, $library;
582         }
583     }
584
585     return \@pickup_locations;
586 }
587
588 =head3 article_request_type
589
590 my $type = $item->article_request_type( $borrower )
591
592 returns 'yes', 'no', 'bib_only', or 'item_only'
593
594 $borrower must be a Koha::Patron object
595
596 =cut
597
598 sub article_request_type {
599     my ( $self, $borrower ) = @_;
600
601     my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
602     my $branchcode =
603         $branch_control eq 'homebranch'    ? $self->homebranch
604       : $branch_control eq 'holdingbranch' ? $self->holdingbranch
605       :                                      undef;
606     my $borrowertype = $borrower->categorycode;
607     my $itemtype = $self->effective_itemtype();
608     my $rule = Koha::CirculationRules->get_effective_rule(
609         {
610             rule_name    => 'article_requests',
611             categorycode => $borrowertype,
612             itemtype     => $itemtype,
613             branchcode   => $branchcode
614         }
615     );
616
617     return q{} unless $rule;
618     return $rule->rule_value || q{}
619 }
620
621 =head3 current_holds
622
623 =cut
624
625 sub current_holds {
626     my ( $self ) = @_;
627     my $attributes = { order_by => 'priority' };
628     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
629     my $params = {
630         itemnumber => $self->itemnumber,
631         suspend => 0,
632         -or => [
633             reservedate => { '<=' => $dtf->format_date(dt_from_string) },
634             waitingdate => { '!=' => undef },
635         ],
636     };
637     my $hold_rs = $self->_result->reserves->search( $params, $attributes );
638     return Koha::Holds->_new_from_dbic($hold_rs);
639 }
640
641 =head3 stockrotationitem
642
643   my $sritem = Koha::Item->stockrotationitem;
644
645 Returns the stock rotation item associated with the current item.
646
647 =cut
648
649 sub stockrotationitem {
650     my ( $self ) = @_;
651     my $rs = $self->_result->stockrotationitem;
652     return 0 if !$rs;
653     return Koha::StockRotationItem->_new_from_dbic( $rs );
654 }
655
656 =head3 add_to_rota
657
658   my $item = $item->add_to_rota($rota_id);
659
660 Add this item to the rota identified by $ROTA_ID, which means associating it
661 with the first stage of that rota.  Should this item already be associated
662 with a rota, then we will move it to the new rota.
663
664 =cut
665
666 sub add_to_rota {
667     my ( $self, $rota_id ) = @_;
668     Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
669     return $self;
670 }
671
672 =head3 has_pending_hold
673
674   my $is_pending_hold = $item->has_pending_hold();
675
676 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
677
678 =cut
679
680 sub has_pending_hold {
681     my ( $self ) = @_;
682     my $pending_hold = $self->_result->tmp_holdsqueues;
683     return $pending_hold->count ? 1: 0;
684 }
685
686 =head3 as_marc_field
687
688     my $mss   = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
689     my $field = $item->as_marc_field({ [ mss => $mss ] });
690
691 This method returns a MARC::Field object representing the Koha::Item object
692 with the current mappings configuration.
693
694 =cut
695
696 sub as_marc_field {
697     my ( $self, $params ) = @_;
698
699     my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
700     my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
701
702     my @subfields;
703
704     my @columns = $self->_result->result_source->columns;
705
706     foreach my $item_field ( @columns ) {
707         my $mapping = $mss->{ "items.$item_field"}[0];
708         my $tagfield    = $mapping->{tagfield};
709         my $tagsubfield = $mapping->{tagsubfield};
710         next if !$tagfield; # TODO: Should we raise an exception instead?
711                             # Feels like safe fallback is better
712
713         push @subfields, $tagsubfield => $self->$item_field
714             if defined $self->$item_field and $item_field ne '';
715     }
716
717     my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
718     push( @subfields, @{$unlinked_item_subfields} )
719         if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
720
721     my $field;
722
723     $field = MARC::Field->new(
724         "$item_tag", ' ', ' ', @subfields
725     ) if @subfields;
726
727     return $field;
728 }
729
730 =head3 renewal_branchcode
731
732 Returns the branchcode to be recorded in statistics renewal of the item
733
734 =cut
735
736 sub renewal_branchcode {
737
738     my ($self, $params ) = @_;
739
740     my $interface = C4::Context->interface;
741     my $branchcode;
742     if ( $interface eq 'opac' ){
743         my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
744         if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
745             $branchcode = 'OPACRenew';
746         }
747         elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
748             $branchcode = $self->homebranch;
749         }
750         elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
751             $branchcode = $self->checkout->patron->branchcode;
752         }
753         elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
754             $branchcode = $self->checkout->branchcode;
755         }
756         else {
757             $branchcode = "";
758         }
759     } else {
760         $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
761             ? C4::Context->userenv->{branch} : $params->{branch};
762     }
763     return $branchcode;
764 }
765
766 sub set_found {
767     my ($self, $params) = @_;
768
769     my $holdingbranch = $params->{holdingbranch} || $self->holdingbranch;
770     my $borrowernumber = $params->{borrowernumber} || undef;
771
772     ## If item was lost, it has now been found, reverse any list item charges if necessary.
773     my $refund = 1;
774     my $no_refund_after_days =
775       C4::Context->preference('NoRefundOnLostReturnedItemsAge');
776     if ($no_refund_after_days) {
777         my $today = dt_from_string();
778         my $lost_age_in_days =
779           dt_from_string( $self->itemlost_on )->delta_days($today)
780           ->in_units('days');
781
782         $refund = 0 unless ( $lost_age_in_days < $no_refund_after_days );
783     }
784
785     my $refunded;
786     if (
787         $refund
788         && Koha::CirculationRules->get_lostreturn_policy(
789             {
790                 current_branch => C4::Context->userenv->{branch},
791                 item           => $self,
792             }
793         )
794       )
795     {
796         _FixAccountForLostAndFound( $self->itemnumber, borrowernumber, $self->barcode );
797         $refunded = 1;
798     }
799
800     return $refunded;
801 }
802
803 =head3 to_api_mapping
804
805 This method returns the mapping for representing a Koha::Item object
806 on the API.
807
808 =cut
809
810 sub to_api_mapping {
811     return {
812         itemnumber               => 'item_id',
813         biblionumber             => 'biblio_id',
814         biblioitemnumber         => undef,
815         barcode                  => 'external_id',
816         dateaccessioned          => 'acquisition_date',
817         booksellerid             => 'acquisition_source',
818         homebranch               => 'home_library_id',
819         price                    => 'purchase_price',
820         replacementprice         => 'replacement_price',
821         replacementpricedate     => 'replacement_price_date',
822         datelastborrowed         => 'last_checkout_date',
823         datelastseen             => 'last_seen_date',
824         stack                    => undef,
825         notforloan               => 'not_for_loan_status',
826         damaged                  => 'damaged_status',
827         damaged_on               => 'damaged_date',
828         itemlost                 => 'lost_status',
829         itemlost_on              => 'lost_date',
830         withdrawn                => 'withdrawn',
831         withdrawn_on             => 'withdrawn_date',
832         itemcallnumber           => 'callnumber',
833         coded_location_qualifier => 'coded_location_qualifier',
834         issues                   => 'checkouts_count',
835         renewals                 => 'renewals_count',
836         reserves                 => 'holds_count',
837         restricted               => 'restricted_status',
838         itemnotes                => 'public_notes',
839         itemnotes_nonpublic      => 'internal_notes',
840         holdingbranch            => 'holding_library_id',
841         paidfor                  => undef,
842         timestamp                => 'timestamp',
843         location                 => 'location',
844         permanent_location       => 'permanent_location',
845         onloan                   => 'checked_out_date',
846         cn_source                => 'call_number_source',
847         cn_sort                  => 'call_number_sort',
848         ccode                    => 'collection_code',
849         materials                => 'materials_notes',
850         uri                      => 'uri',
851         itype                    => 'item_type',
852         more_subfields_xml       => 'extended_subfields',
853         enumchron                => 'serial_issue_number',
854         copynumber               => 'copy_number',
855         stocknumber              => 'inventory_number',
856         new_status               => 'new_status'
857     };
858 }
859
860 =head3 itemtype
861
862     my $itemtype = $item->itemtype;
863
864     Returns Koha object for effective itemtype
865
866 =cut
867
868 sub itemtype {
869     my ( $self ) = @_;
870     return Koha::ItemTypes->find( $self->effective_itemtype );
871 }
872
873 =head2 Internal methods
874
875 =head3 _after_item_action_hooks
876
877 Helper method that takes care of calling all plugin hooks
878
879 =cut
880
881 sub _after_item_action_hooks {
882     my ( $self, $params ) = @_;
883
884     my $action = $params->{action};
885
886     Koha::Plugins->call(
887         'after_item_action',
888         {
889             action  => $action,
890             item    => $self,
891             item_id => $self->itemnumber,
892         }
893     );
894 }
895
896 =head3 _type
897
898 =cut
899
900 sub _type {
901     return 'Item';
902 }
903
904 =head1 AUTHOR
905
906 Kyle M Hall <kyle@bywatersolutions.com>
907
908 =cut
909
910 1;