3 # Copyright ByWater Solutions 2014
5 # This file is part of Koha.
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.
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.
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>.
23 use List::MoreUtils qw(any);
26 use Koha::DateUtils qw( dt_from_string );
32 use Koha::CirculationRules;
33 use Koha::Item::Transfer::Limits;
34 use Koha::Item::Transfers;
37 use Koha::StockRotationItem;
38 use Koha::StockRotationRotas;
40 use base qw(Koha::Object);
44 Koha::Item - Koha Item object class
59 # We do not want to oblige callers to pass this value
60 # Dev conveniences vs performance?
61 unless ( $self->biblioitemnumber ) {
62 $self->biblioitemnumber( $self->biblio->biblioitem->biblioitemnumber );
65 # See related changes from C4::Items::AddItem
66 unless ( $self->itype ) {
67 $self->itype($self->biblio->biblioitem->itemtype);
70 unless ( $self->in_storage ) { #AddItem
71 my $today = dt_from_string;
72 unless ( $self->permanent_location ) {
73 $self->permanent_location($self->location);
75 unless ( $self->replacementpricedate ) {
76 $self->replacementpricedate($today);
78 unless ( $self->datelastseen ) {
79 $self->datelastseen($today);
84 unless ( $self->dateaccessioned ) {
85 $self->dateaccessioned($today);
88 return $self->SUPER::store;
91 =head3 effective_itemtype
93 Returns the itemtype for the item based on whether item level itemtypes are set or not.
97 sub effective_itemtype {
100 return $self->_result()->effective_itemtype();
110 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
112 return $self->{_home_branch};
115 =head3 holding_branch
122 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
124 return $self->{_holding_branch};
129 my $biblio = $item->biblio;
131 Return the bibliographic record of this item
137 my $biblio_rs = $self->_result->biblio;
138 return Koha::Biblio->_new_from_dbic( $biblio_rs );
143 my $biblioitem = $item->biblioitem;
145 Return the biblioitem record of this item
151 my $biblioitem_rs = $self->_result->biblioitem;
152 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
157 my $checkout = $item->checkout;
159 Return the checkout for this item
165 my $checkout_rs = $self->_result->issue;
166 return unless $checkout_rs;
167 return Koha::Checkout->_new_from_dbic( $checkout_rs );
172 my $holds = $item->holds();
173 my $holds = $item->holds($params);
174 my $holds = $item->holds({ found => 'W'});
176 Return holds attached to an item, optionally accept a hashref of params to pass to search
181 my ( $self,$params ) = @_;
182 my $holds_rs = $self->_result->reserves->search($params);
183 return Koha::Holds->_new_from_dbic( $holds_rs );
188 my $transfer = $item->get_transfer;
190 Return the transfer if the item is in transit or undef
196 my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
197 return unless $transfer_rs;
198 return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
201 =head3 last_returned_by
203 Gets and sets the last borrower to return an item.
205 Accepts and returns Koha::Patron objects
207 $item->last_returned_by( $borrowernumber );
209 $last_returned_by = $item->last_returned_by();
213 sub last_returned_by {
214 my ( $self, $borrower ) = @_;
216 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
219 return $items_last_returned_by_rs->update_or_create(
220 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
223 unless ( $self->{_last_returned_by} ) {
224 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
226 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
230 return $self->{_last_returned_by};
234 =head3 can_article_request
236 my $bool = $item->can_article_request( $borrower )
238 Returns true if item can be specifically requested
240 $borrower must be a Koha::Patron object
244 sub can_article_request {
245 my ( $self, $borrower ) = @_;
247 my $rule = $self->article_request_type($borrower);
249 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
253 =head3 hidden_in_opac
255 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
257 Returns true if item fields match the hidding criteria defined in $rules.
258 Returns false otherwise.
260 Takes HASHref that can have the following parameters:
262 $rules : { <field> => [ value_1, ... ], ... }
264 Note: $rules inherits its structure from the parsed YAML from reading
265 the I<OpacHiddenItems> system preference.
270 my ( $self, $params ) = @_;
272 my $rules = $params->{rules} // {};
275 if C4::Context->preference('hidelostitems') and
278 my $hidden_in_opac = 0;
280 foreach my $field ( keys %{$rules} ) {
282 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
288 return $hidden_in_opac;
291 =head3 can_be_transferred
293 $item->can_be_transferred({ to => $to_library, from => $from_library })
294 Checks if an item can be transferred to given library.
296 This feature is controlled by two system preferences:
297 UseBranchTransferLimits to enable / disable the feature
298 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
299 for setting the limitations
301 Takes HASHref that can have the following parameters:
302 MANDATORY PARAMETERS:
305 $from : Koha::Library # if not given, item holdingbranch
306 # will be used instead
308 Returns 1 if item can be transferred to $to_library, otherwise 0.
310 To find out whether at least one item of a Koha::Biblio can be transferred, please
311 see Koha::Biblio->can_be_transferred() instead of using this method for
312 multiple items of the same biblio.
316 sub can_be_transferred {
317 my ($self, $params) = @_;
319 my $to = $params->{to};
320 my $from = $params->{from};
322 $to = $to->branchcode;
323 $from = defined $from ? $from->branchcode : $self->holdingbranch;
325 return 1 if $from eq $to; # Transfer to current branch is allowed
326 return 1 unless C4::Context->preference('UseBranchTransferLimits');
328 my $limittype = C4::Context->preference('BranchTransferLimitsType');
329 return Koha::Item::Transfer::Limits->search({
332 $limittype => $limittype eq 'itemtype'
333 ? $self->effective_itemtype : $self->ccode
337 =head3 pickup_locations
339 @pickup_locations = $item->pickup_locations( {patron => $patron } )
341 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)
342 and if item can be transferred to each pickup location.
346 sub pickup_locations {
347 my ($self, $params) = @_;
349 my $patron = $params->{patron};
351 my $circ_control_branch =
352 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
354 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
357 if(defined $patron) {
358 return @libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
359 return @libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
362 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
363 @libs = $self->home_branch->get_hold_libraries;
364 push @libs, $self->home_branch unless scalar(@libs) > 0;
365 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
366 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
367 @libs = $plib->get_hold_libraries;
368 push @libs, $self->home_branch unless scalar(@libs) > 0;
369 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
370 push @libs, $self->home_branch;
371 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
372 push @libs, $self->holding_branch;
374 @libs = Koha::Libraries->search({
377 order_by => ['branchname']
381 my @pickup_locations;
382 foreach my $library (@libs) {
383 if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
384 push @pickup_locations, $library;
388 return wantarray ? @pickup_locations : \@pickup_locations;
391 =head3 article_request_type
393 my $type = $item->article_request_type( $borrower )
395 returns 'yes', 'no', 'bib_only', or 'item_only'
397 $borrower must be a Koha::Patron object
401 sub article_request_type {
402 my ( $self, $borrower ) = @_;
404 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
406 $branch_control eq 'homebranch' ? $self->homebranch
407 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
409 my $borrowertype = $borrower->categorycode;
410 my $itemtype = $self->effective_itemtype();
411 my $rule = Koha::CirculationRules->get_effective_rule(
413 rule_name => 'article_requests',
414 categorycode => $borrowertype,
415 itemtype => $itemtype,
416 branchcode => $branchcode
420 return q{} unless $rule;
421 return $rule->rule_value || q{}
430 my $attributes = { order_by => 'priority' };
431 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
433 itemnumber => $self->itemnumber,
436 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
437 waitingdate => { '!=' => undef },
440 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
441 return Koha::Holds->_new_from_dbic($hold_rs);
444 =head3 stockrotationitem
446 my $sritem = Koha::Item->stockrotationitem;
448 Returns the stock rotation item associated with the current item.
452 sub stockrotationitem {
454 my $rs = $self->_result->stockrotationitem;
456 return Koha::StockRotationItem->_new_from_dbic( $rs );
461 my $item = $item->add_to_rota($rota_id);
463 Add this item to the rota identified by $ROTA_ID, which means associating it
464 with the first stage of that rota. Should this item already be associated
465 with a rota, then we will move it to the new rota.
470 my ( $self, $rota_id ) = @_;
471 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
475 =head3 has_pending_hold
477 my $is_pending_hold = $item->has_pending_hold();
479 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
483 sub has_pending_hold {
485 my $pending_hold = $self->_result->tmp_holdsqueues;
486 return $pending_hold->count ? 1: 0;
491 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
492 my $field = $item->as_marc_field({ [ mss => $mss ] });
494 This method returns a MARC::Field object representing the Koha::Item object
495 with the current mappings configuration.
500 my ( $self, $params ) = @_;
502 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
503 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
507 my @columns = $self->_result->result_source->columns;
509 foreach my $item_field ( @columns ) {
510 my $mapping = $mss->{ "items.$item_field"}[0];
511 my $tagfield = $mapping->{tagfield};
512 my $tagsubfield = $mapping->{tagsubfield};
513 next if !$tagfield; # TODO: Should we raise an exception instead?
514 # Feels like safe fallback is better
516 push @subfields, $tagsubfield => $self->$item_field;
519 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
520 push( @subfields, @{$unlinked_item_subfields} )
521 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
525 $field = MARC::Field->new(
526 "$item_tag", ' ', ' ', @subfields
532 =head3 to_api_mapping
534 This method returns the mapping for representing a Koha::Item object
541 itemnumber => 'item_id',
542 biblionumber => 'biblio_id',
543 biblioitemnumber => undef,
544 barcode => 'external_id',
545 dateaccessioned => 'acquisition_date',
546 booksellerid => 'acquisition_source',
547 homebranch => 'home_library_id',
548 price => 'purchase_price',
549 replacementprice => 'replacement_price',
550 replacementpricedate => 'replacement_price_date',
551 datelastborrowed => 'last_checkout_date',
552 datelastseen => 'last_seen_date',
554 notforloan => 'not_for_loan_status',
555 damaged => 'damaged_status',
556 damaged_on => 'damaged_date',
557 itemlost => 'lost_status',
558 itemlost_on => 'lost_date',
559 withdrawn => 'withdrawn',
560 withdrawn_on => 'withdrawn_date',
561 itemcallnumber => 'callnumber',
562 coded_location_qualifier => 'coded_location_qualifier',
563 issues => 'checkouts_count',
564 renewals => 'renewals_count',
565 reserves => 'holds_count',
566 restricted => 'restricted_status',
567 itemnotes => 'public_notes',
568 itemnotes_nonpublic => 'internal_notes',
569 holdingbranch => 'holding_library_id',
571 timestamp => 'timestamp',
572 location => 'location',
573 permanent_location => 'permanent_location',
574 onloan => 'checked_out_date',
575 cn_source => 'call_number_source',
576 cn_sort => 'call_number_sort',
577 ccode => 'collection_code',
578 materials => 'materials_notes',
580 itype => 'item_type',
581 more_subfields_xml => 'extended_subfields',
582 enumchron => 'serial_issue_number',
583 copynumber => 'copy_number',
584 stocknumber => 'inventory_number',
585 new_status => 'new_status'
589 =head2 Internal methods
601 Kyle M Hall <kyle@bywatersolutions.com>