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