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