Bug 25421: Update POD
[srvgit] / 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 "last_item_for_hold" if the item is the last one on a record on which a biblio-level hold is placed
238
239 =cut
240
241 sub safe_to_delete {
242     my ($self) = @_;
243
244     return "book_on_loan" if $self->checkout;
245
246     return "not_same_branch"
247       if defined C4::Context->userenv
248       and !C4::Context->IsSuperLibrarian()
249       and C4::Context->preference("IndependentBranches")
250       and ( C4::Context->userenv->{branch} ne $self->homebranch );
251
252     # check it doesn't have a waiting reserve
253     return "book_reserved"
254       if $self->holds->search( { found => [ 'W', 'T' ] } )->count;
255
256     return "linked_analytics"
257       if C4::Items::GetAnalyticsCount( $self->itemnumber ) > 0;
258
259     return "last_item_for_hold"
260       if $self->biblio->items->count == 1
261       && $self->biblio->holds->search(
262           {
263               itemnumber => undef,
264           }
265         )->count;
266
267     return 1;
268 }
269
270 =head3 move_to_deleted
271
272 my $is_moved = $item->move_to_deleted;
273
274 Move an item to the deleteditems table.
275 This can be done before deleting an item, to make sure the data are not completely deleted.
276
277 =cut
278
279 sub move_to_deleted {
280     my ($self) = @_;
281     my $item_infos = $self->unblessed;
282     delete $item_infos->{timestamp}; #This ensures the timestamp date in deleteditems will be set to the current timestamp
283     return Koha::Database->new->schema->resultset('Deleteditem')->create($item_infos);
284 }
285
286
287 =head3 effective_itemtype
288
289 Returns the itemtype for the item based on whether item level itemtypes are set or not.
290
291 =cut
292
293 sub effective_itemtype {
294     my ( $self ) = @_;
295
296     return $self->_result()->effective_itemtype();
297 }
298
299 =head3 home_branch
300
301 =cut
302
303 sub home_branch {
304     my ($self) = @_;
305
306     $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
307
308     return $self->{_home_branch};
309 }
310
311 =head3 holding_branch
312
313 =cut
314
315 sub holding_branch {
316     my ($self) = @_;
317
318     $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
319
320     return $self->{_holding_branch};
321 }
322
323 =head3 biblio
324
325 my $biblio = $item->biblio;
326
327 Return the bibliographic record of this item
328
329 =cut
330
331 sub biblio {
332     my ( $self ) = @_;
333     my $biblio_rs = $self->_result->biblio;
334     return Koha::Biblio->_new_from_dbic( $biblio_rs );
335 }
336
337 =head3 biblioitem
338
339 my $biblioitem = $item->biblioitem;
340
341 Return the biblioitem record of this item
342
343 =cut
344
345 sub biblioitem {
346     my ( $self ) = @_;
347     my $biblioitem_rs = $self->_result->biblioitem;
348     return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
349 }
350
351 =head3 checkout
352
353 my $checkout = $item->checkout;
354
355 Return the checkout for this item
356
357 =cut
358
359 sub checkout {
360     my ( $self ) = @_;
361     my $checkout_rs = $self->_result->issue;
362     return unless $checkout_rs;
363     return Koha::Checkout->_new_from_dbic( $checkout_rs );
364 }
365
366 =head3 holds
367
368 my $holds = $item->holds();
369 my $holds = $item->holds($params);
370 my $holds = $item->holds({ found => 'W'});
371
372 Return holds attached to an item, optionally accept a hashref of params to pass to search
373
374 =cut
375
376 sub holds {
377     my ( $self,$params ) = @_;
378     my $holds_rs = $self->_result->reserves->search($params);
379     return Koha::Holds->_new_from_dbic( $holds_rs );
380 }
381
382 =head3 get_transfer
383
384 my $transfer = $item->get_transfer;
385
386 Return the transfer if the item is in transit or undef
387
388 =cut
389
390 sub get_transfer {
391     my ( $self ) = @_;
392     my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
393     return unless $transfer_rs;
394     return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
395 }
396
397 =head3 last_returned_by
398
399 Gets and sets the last borrower to return an item.
400
401 Accepts and returns Koha::Patron objects
402
403 $item->last_returned_by( $borrowernumber );
404
405 $last_returned_by = $item->last_returned_by();
406
407 =cut
408
409 sub last_returned_by {
410     my ( $self, $borrower ) = @_;
411
412     my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
413
414     if ($borrower) {
415         return $items_last_returned_by_rs->update_or_create(
416             { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
417     }
418     else {
419         unless ( $self->{_last_returned_by} ) {
420             my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
421             if ($result) {
422                 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
423             }
424         }
425
426         return $self->{_last_returned_by};
427     }
428 }
429
430 =head3 can_article_request
431
432 my $bool = $item->can_article_request( $borrower )
433
434 Returns true if item can be specifically requested
435
436 $borrower must be a Koha::Patron object
437
438 =cut
439
440 sub can_article_request {
441     my ( $self, $borrower ) = @_;
442
443     my $rule = $self->article_request_type($borrower);
444
445     return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
446     return q{};
447 }
448
449 =head3 hidden_in_opac
450
451 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
452
453 Returns true if item fields match the hidding criteria defined in $rules.
454 Returns false otherwise.
455
456 Takes HASHref that can have the following parameters:
457     OPTIONAL PARAMETERS:
458     $rules : { <field> => [ value_1, ... ], ... }
459
460 Note: $rules inherits its structure from the parsed YAML from reading
461 the I<OpacHiddenItems> system preference.
462
463 =cut
464
465 sub hidden_in_opac {
466     my ( $self, $params ) = @_;
467
468     my $rules = $params->{rules} // {};
469
470     return 1
471         if C4::Context->preference('hidelostitems') and
472            $self->itemlost > 0;
473
474     my $hidden_in_opac = 0;
475
476     foreach my $field ( keys %{$rules} ) {
477
478         if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
479             $hidden_in_opac = 1;
480             last;
481         }
482     }
483
484     return $hidden_in_opac;
485 }
486
487 =head3 can_be_transferred
488
489 $item->can_be_transferred({ to => $to_library, from => $from_library })
490 Checks if an item can be transferred to given library.
491
492 This feature is controlled by two system preferences:
493 UseBranchTransferLimits to enable / disable the feature
494 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
495                          for setting the limitations
496
497 Takes HASHref that can have the following parameters:
498     MANDATORY PARAMETERS:
499     $to   : Koha::Library
500     OPTIONAL PARAMETERS:
501     $from : Koha::Library  # if not given, item holdingbranch
502                            # will be used instead
503
504 Returns 1 if item can be transferred to $to_library, otherwise 0.
505
506 To find out whether at least one item of a Koha::Biblio can be transferred, please
507 see Koha::Biblio->can_be_transferred() instead of using this method for
508 multiple items of the same biblio.
509
510 =cut
511
512 sub can_be_transferred {
513     my ($self, $params) = @_;
514
515     my $to   = $params->{to};
516     my $from = $params->{from};
517
518     $to   = $to->branchcode;
519     $from = defined $from ? $from->branchcode : $self->holdingbranch;
520
521     return 1 if $from eq $to; # Transfer to current branch is allowed
522     return 1 unless C4::Context->preference('UseBranchTransferLimits');
523
524     my $limittype = C4::Context->preference('BranchTransferLimitsType');
525     return Koha::Item::Transfer::Limits->search({
526         toBranch => $to,
527         fromBranch => $from,
528         $limittype => $limittype eq 'itemtype'
529                         ? $self->effective_itemtype : $self->ccode
530     })->count ? 0 : 1;
531 }
532
533 =head3 pickup_locations
534
535 $pickup_locations = $item->pickup_locations( {patron => $patron } )
536
537 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)
538 and if item can be transferred to each pickup location.
539
540 =cut
541
542 sub pickup_locations {
543     my ($self, $params) = @_;
544
545     my $patron = $params->{patron};
546
547     my $circ_control_branch =
548       C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
549     my $branchitemrule =
550       C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
551
552     my @libs;
553     if(defined $patron) {
554         return \@libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
555         return \@libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
556     }
557
558     if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
559         @libs  = $self->home_branch->get_hold_libraries;
560         push @libs, $self->home_branch unless scalar(@libs) > 0;
561     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
562         my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
563         @libs  = $plib->get_hold_libraries;
564         push @libs, $self->home_branch unless scalar(@libs) > 0;
565     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
566         push @libs, $self->home_branch;
567     } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
568         push @libs, $self->holding_branch;
569     } else {
570         @libs = Koha::Libraries->search({
571             pickup_location => 1
572         }, {
573             order_by => ['branchname']
574         })->as_list;
575     }
576
577     my @pickup_locations;
578     foreach my $library (@libs) {
579         if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
580             push @pickup_locations, $library;
581         }
582     }
583
584     return \@pickup_locations;
585 }
586
587 =head3 article_request_type
588
589 my $type = $item->article_request_type( $borrower )
590
591 returns 'yes', 'no', 'bib_only', or 'item_only'
592
593 $borrower must be a Koha::Patron object
594
595 =cut
596
597 sub article_request_type {
598     my ( $self, $borrower ) = @_;
599
600     my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
601     my $branchcode =
602         $branch_control eq 'homebranch'    ? $self->homebranch
603       : $branch_control eq 'holdingbranch' ? $self->holdingbranch
604       :                                      undef;
605     my $borrowertype = $borrower->categorycode;
606     my $itemtype = $self->effective_itemtype();
607     my $rule = Koha::CirculationRules->get_effective_rule(
608         {
609             rule_name    => 'article_requests',
610             categorycode => $borrowertype,
611             itemtype     => $itemtype,
612             branchcode   => $branchcode
613         }
614     );
615
616     return q{} unless $rule;
617     return $rule->rule_value || q{}
618 }
619
620 =head3 current_holds
621
622 =cut
623
624 sub current_holds {
625     my ( $self ) = @_;
626     my $attributes = { order_by => 'priority' };
627     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
628     my $params = {
629         itemnumber => $self->itemnumber,
630         suspend => 0,
631         -or => [
632             reservedate => { '<=' => $dtf->format_date(dt_from_string) },
633             waitingdate => { '!=' => undef },
634         ],
635     };
636     my $hold_rs = $self->_result->reserves->search( $params, $attributes );
637     return Koha::Holds->_new_from_dbic($hold_rs);
638 }
639
640 =head3 stockrotationitem
641
642   my $sritem = Koha::Item->stockrotationitem;
643
644 Returns the stock rotation item associated with the current item.
645
646 =cut
647
648 sub stockrotationitem {
649     my ( $self ) = @_;
650     my $rs = $self->_result->stockrotationitem;
651     return 0 if !$rs;
652     return Koha::StockRotationItem->_new_from_dbic( $rs );
653 }
654
655 =head3 add_to_rota
656
657   my $item = $item->add_to_rota($rota_id);
658
659 Add this item to the rota identified by $ROTA_ID, which means associating it
660 with the first stage of that rota.  Should this item already be associated
661 with a rota, then we will move it to the new rota.
662
663 =cut
664
665 sub add_to_rota {
666     my ( $self, $rota_id ) = @_;
667     Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
668     return $self;
669 }
670
671 =head3 has_pending_hold
672
673   my $is_pending_hold = $item->has_pending_hold();
674
675 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
676
677 =cut
678
679 sub has_pending_hold {
680     my ( $self ) = @_;
681     my $pending_hold = $self->_result->tmp_holdsqueues;
682     return $pending_hold->count ? 1: 0;
683 }
684
685 =head3 as_marc_field
686
687     my $mss   = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
688     my $field = $item->as_marc_field({ [ mss => $mss ] });
689
690 This method returns a MARC::Field object representing the Koha::Item object
691 with the current mappings configuration.
692
693 =cut
694
695 sub as_marc_field {
696     my ( $self, $params ) = @_;
697
698     my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
699     my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
700
701     my @subfields;
702
703     my @columns = $self->_result->result_source->columns;
704
705     foreach my $item_field ( @columns ) {
706         my $mapping = $mss->{ "items.$item_field"}[0];
707         my $tagfield    = $mapping->{tagfield};
708         my $tagsubfield = $mapping->{tagsubfield};
709         next if !$tagfield; # TODO: Should we raise an exception instead?
710                             # Feels like safe fallback is better
711
712         push @subfields, $tagsubfield => $self->$item_field
713             if defined $self->$item_field and $item_field ne '';
714     }
715
716     my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
717     push( @subfields, @{$unlinked_item_subfields} )
718         if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
719
720     my $field;
721
722     $field = MARC::Field->new(
723         "$item_tag", ' ', ' ', @subfields
724     ) if @subfields;
725
726     return $field;
727 }
728
729 =head3 renewal_branchcode
730
731 Returns the branchcode to be recorded in statistics renewal of the item
732
733 =cut
734
735 sub renewal_branchcode {
736
737     my ($self, $params ) = @_;
738
739     my $interface = C4::Context->interface;
740     my $branchcode;
741     if ( $interface eq 'opac' ){
742         my $renewal_branchcode = C4::Context->preference('OpacRenewalBranch');
743         if( !defined $renewal_branchcode || $renewal_branchcode eq 'opacrenew' ){
744             $branchcode = 'OPACRenew';
745         }
746         elsif ( $renewal_branchcode eq 'itemhomebranch' ) {
747             $branchcode = $self->homebranch;
748         }
749         elsif ( $renewal_branchcode eq 'patronhomebranch' ) {
750             $branchcode = $self->checkout->patron->branchcode;
751         }
752         elsif ( $renewal_branchcode eq 'checkoutbranch' ) {
753             $branchcode = $self->checkout->branchcode;
754         }
755         else {
756             $branchcode = "";
757         }
758     } else {
759         $branchcode = ( C4::Context->userenv && defined C4::Context->userenv->{branch} )
760             ? C4::Context->userenv->{branch} : $params->{branch};
761     }
762     return $branchcode;
763 }
764
765 =head3 to_api_mapping
766
767 This method returns the mapping for representing a Koha::Item object
768 on the API.
769
770 =cut
771
772 sub to_api_mapping {
773     return {
774         itemnumber               => 'item_id',
775         biblionumber             => 'biblio_id',
776         biblioitemnumber         => undef,
777         barcode                  => 'external_id',
778         dateaccessioned          => 'acquisition_date',
779         booksellerid             => 'acquisition_source',
780         homebranch               => 'home_library_id',
781         price                    => 'purchase_price',
782         replacementprice         => 'replacement_price',
783         replacementpricedate     => 'replacement_price_date',
784         datelastborrowed         => 'last_checkout_date',
785         datelastseen             => 'last_seen_date',
786         stack                    => undef,
787         notforloan               => 'not_for_loan_status',
788         damaged                  => 'damaged_status',
789         damaged_on               => 'damaged_date',
790         itemlost                 => 'lost_status',
791         itemlost_on              => 'lost_date',
792         withdrawn                => 'withdrawn',
793         withdrawn_on             => 'withdrawn_date',
794         itemcallnumber           => 'callnumber',
795         coded_location_qualifier => 'coded_location_qualifier',
796         issues                   => 'checkouts_count',
797         renewals                 => 'renewals_count',
798         reserves                 => 'holds_count',
799         restricted               => 'restricted_status',
800         itemnotes                => 'public_notes',
801         itemnotes_nonpublic      => 'internal_notes',
802         holdingbranch            => 'holding_library_id',
803         paidfor                  => undef,
804         timestamp                => 'timestamp',
805         location                 => 'location',
806         permanent_location       => 'permanent_location',
807         onloan                   => 'checked_out_date',
808         cn_source                => 'call_number_source',
809         cn_sort                  => 'call_number_sort',
810         ccode                    => 'collection_code',
811         materials                => 'materials_notes',
812         uri                      => 'uri',
813         itype                    => 'item_type',
814         more_subfields_xml       => 'extended_subfields',
815         enumchron                => 'serial_issue_number',
816         copynumber               => 'copy_number',
817         stocknumber              => 'inventory_number',
818         new_status               => 'new_status'
819     };
820 }
821
822 =head2 Internal methods
823
824 =head3 _after_item_action_hooks
825
826 Helper method that takes care of calling all plugin hooks
827
828 =cut
829
830 sub _after_item_action_hooks {
831     my ( $self, $params ) = @_;
832
833     my $action = $params->{action};
834
835     if ( C4::Context->config("enable_plugins") ) {
836
837         my @plugins = Koha::Plugins->new->GetPlugins({
838             method => 'after_item_action',
839         });
840
841         if (@plugins) {
842
843             foreach my $plugin ( @plugins ) {
844                 try {
845                     $plugin->after_item_action({ action => $action, item => $self, item_id => $self->itemnumber });
846                 }
847                 catch {
848                     warn "$_";
849                 };
850             }
851         }
852     }
853 }
854
855 =head3 _type
856
857 =cut
858
859 sub _type {
860     return 'Item';
861 }
862
863 =head1 AUTHOR
864
865 Kyle M Hall <kyle@bywatersolutions.com>
866
867 =cut
868
869 1;