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