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