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