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 return $self->SUPER::store;
87 =head3 effective_itemtype
89 Returns the itemtype for the item based on whether item level itemtypes are set or not.
93 sub effective_itemtype {
96 return $self->_result()->effective_itemtype();
106 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
108 return $self->{_home_branch};
111 =head3 holding_branch
118 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
120 return $self->{_holding_branch};
125 my $biblio = $item->biblio;
127 Return the bibliographic record of this item
133 my $biblio_rs = $self->_result->biblio;
134 return Koha::Biblio->_new_from_dbic( $biblio_rs );
139 my $biblioitem = $item->biblioitem;
141 Return the biblioitem record of this item
147 my $biblioitem_rs = $self->_result->biblioitem;
148 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
153 my $checkout = $item->checkout;
155 Return the checkout for this item
161 my $checkout_rs = $self->_result->issue;
162 return unless $checkout_rs;
163 return Koha::Checkout->_new_from_dbic( $checkout_rs );
168 my $holds = $item->holds();
169 my $holds = $item->holds($params);
170 my $holds = $item->holds({ found => 'W'});
172 Return holds attached to an item, optionally accept a hashref of params to pass to search
177 my ( $self,$params ) = @_;
178 my $holds_rs = $self->_result->reserves->search($params);
179 return Koha::Holds->_new_from_dbic( $holds_rs );
184 my $transfer = $item->get_transfer;
186 Return the transfer if the item is in transit or undef
192 my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
193 return unless $transfer_rs;
194 return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
197 =head3 last_returned_by
199 Gets and sets the last borrower to return an item.
201 Accepts and returns Koha::Patron objects
203 $item->last_returned_by( $borrowernumber );
205 $last_returned_by = $item->last_returned_by();
209 sub last_returned_by {
210 my ( $self, $borrower ) = @_;
212 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
215 return $items_last_returned_by_rs->update_or_create(
216 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
219 unless ( $self->{_last_returned_by} ) {
220 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
222 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
226 return $self->{_last_returned_by};
230 =head3 can_article_request
232 my $bool = $item->can_article_request( $borrower )
234 Returns true if item can be specifically requested
236 $borrower must be a Koha::Patron object
240 sub can_article_request {
241 my ( $self, $borrower ) = @_;
243 my $rule = $self->article_request_type($borrower);
245 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
249 =head3 hidden_in_opac
251 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
253 Returns true if item fields match the hidding criteria defined in $rules.
254 Returns false otherwise.
256 Takes HASHref that can have the following parameters:
258 $rules : { <field> => [ value_1, ... ], ... }
260 Note: $rules inherits its structure from the parsed YAML from reading
261 the I<OpacHiddenItems> system preference.
266 my ( $self, $params ) = @_;
268 my $rules = $params->{rules} // {};
271 if C4::Context->preference('hidelostitems') and
274 my $hidden_in_opac = 0;
276 foreach my $field ( keys %{$rules} ) {
278 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
284 return $hidden_in_opac;
287 =head3 can_be_transferred
289 $item->can_be_transferred({ to => $to_library, from => $from_library })
290 Checks if an item can be transferred to given library.
292 This feature is controlled by two system preferences:
293 UseBranchTransferLimits to enable / disable the feature
294 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
295 for setting the limitations
297 Takes HASHref that can have the following parameters:
298 MANDATORY PARAMETERS:
301 $from : Koha::Library # if not given, item holdingbranch
302 # will be used instead
304 Returns 1 if item can be transferred to $to_library, otherwise 0.
306 To find out whether at least one item of a Koha::Biblio can be transferred, please
307 see Koha::Biblio->can_be_transferred() instead of using this method for
308 multiple items of the same biblio.
312 sub can_be_transferred {
313 my ($self, $params) = @_;
315 my $to = $params->{to};
316 my $from = $params->{from};
318 $to = $to->branchcode;
319 $from = defined $from ? $from->branchcode : $self->holdingbranch;
321 return 1 if $from eq $to; # Transfer to current branch is allowed
322 return 1 unless C4::Context->preference('UseBranchTransferLimits');
324 my $limittype = C4::Context->preference('BranchTransferLimitsType');
325 return Koha::Item::Transfer::Limits->search({
328 $limittype => $limittype eq 'itemtype'
329 ? $self->effective_itemtype : $self->ccode
333 =head3 pickup_locations
335 @pickup_locations = $item->pickup_locations( {patron => $patron } )
337 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)
338 and if item can be transferred to each pickup location.
342 sub pickup_locations {
343 my ($self, $params) = @_;
345 my $patron = $params->{patron};
347 my $circ_control_branch =
348 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
350 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
353 if(defined $patron) {
354 return @libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
355 return @libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
358 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
359 @libs = $self->home_branch->get_hold_libraries;
360 push @libs, $self->home_branch unless scalar(@libs) > 0;
361 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
362 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
363 @libs = $plib->get_hold_libraries;
364 push @libs, $self->home_branch unless scalar(@libs) > 0;
365 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
366 push @libs, $self->home_branch;
367 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
368 push @libs, $self->holding_branch;
370 @libs = Koha::Libraries->search({
373 order_by => ['branchname']
377 my @pickup_locations;
378 foreach my $library (@libs) {
379 if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
380 push @pickup_locations, $library;
384 return wantarray ? @pickup_locations : \@pickup_locations;
387 =head3 article_request_type
389 my $type = $item->article_request_type( $borrower )
391 returns 'yes', 'no', 'bib_only', or 'item_only'
393 $borrower must be a Koha::Patron object
397 sub article_request_type {
398 my ( $self, $borrower ) = @_;
400 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
402 $branch_control eq 'homebranch' ? $self->homebranch
403 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
405 my $borrowertype = $borrower->categorycode;
406 my $itemtype = $self->effective_itemtype();
407 my $rule = Koha::CirculationRules->get_effective_rule(
409 rule_name => 'article_requests',
410 categorycode => $borrowertype,
411 itemtype => $itemtype,
412 branchcode => $branchcode
416 return q{} unless $rule;
417 return $rule->rule_value || q{}
426 my $attributes = { order_by => 'priority' };
427 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
429 itemnumber => $self->itemnumber,
432 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
433 waitingdate => { '!=' => undef },
436 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
437 return Koha::Holds->_new_from_dbic($hold_rs);
440 =head3 stockrotationitem
442 my $sritem = Koha::Item->stockrotationitem;
444 Returns the stock rotation item associated with the current item.
448 sub stockrotationitem {
450 my $rs = $self->_result->stockrotationitem;
452 return Koha::StockRotationItem->_new_from_dbic( $rs );
457 my $item = $item->add_to_rota($rota_id);
459 Add this item to the rota identified by $ROTA_ID, which means associating it
460 with the first stage of that rota. Should this item already be associated
461 with a rota, then we will move it to the new rota.
466 my ( $self, $rota_id ) = @_;
467 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
471 =head3 has_pending_hold
473 my $is_pending_hold = $item->has_pending_hold();
475 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
479 sub has_pending_hold {
481 my $pending_hold = $self->_result->tmp_holdsqueues;
482 return $pending_hold->count ? 1: 0;
487 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
488 my $field = $item->as_marc_field({ [ mss => $mss ] });
490 This method returns a MARC::Field object representing the Koha::Item object
491 with the current mappings configuration.
496 my ( $self, $params ) = @_;
498 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
499 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
503 my @columns = $self->_result->result_source->columns;
505 foreach my $item_field ( @columns ) {
506 my $mapping = $mss->{ "items.$item_field"}[0];
507 my $tagfield = $mapping->{tagfield};
508 my $tagsubfield = $mapping->{tagsubfield};
509 next if !$tagfield; # TODO: Should we raise an exception instead?
510 # Feels like safe fallback is better
512 push @subfields, $tagsubfield => $self->$item_field;
515 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
516 push( @subfields, @{$unlinked_item_subfields} )
517 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
521 $field = MARC::Field->new(
522 "$item_tag", ' ', ' ', @subfields
528 =head3 to_api_mapping
530 This method returns the mapping for representing a Koha::Item object
537 itemnumber => 'item_id',
538 biblionumber => 'biblio_id',
539 biblioitemnumber => undef,
540 barcode => 'external_id',
541 dateaccessioned => 'acquisition_date',
542 booksellerid => 'acquisition_source',
543 homebranch => 'home_library_id',
544 price => 'purchase_price',
545 replacementprice => 'replacement_price',
546 replacementpricedate => 'replacement_price_date',
547 datelastborrowed => 'last_checkout_date',
548 datelastseen => 'last_seen_date',
550 notforloan => 'not_for_loan_status',
551 damaged => 'damaged_status',
552 damaged_on => 'damaged_date',
553 itemlost => 'lost_status',
554 itemlost_on => 'lost_date',
555 withdrawn => 'withdrawn',
556 withdrawn_on => 'withdrawn_date',
557 itemcallnumber => 'callnumber',
558 coded_location_qualifier => 'coded_location_qualifier',
559 issues => 'checkouts_count',
560 renewals => 'renewals_count',
561 reserves => 'holds_count',
562 restricted => 'restricted_status',
563 itemnotes => 'public_notes',
564 itemnotes_nonpublic => 'internal_notes',
565 holdingbranch => 'holding_library_id',
567 timestamp => 'timestamp',
568 location => 'location',
569 permanent_location => 'permanent_location',
570 onloan => 'checked_out_date',
571 cn_source => 'call_number_source',
572 cn_sort => 'call_number_sort',
573 ccode => 'collection_code',
574 materials => 'materials_notes',
576 itype => 'item_type',
577 more_subfields_xml => 'extended_subfields',
578 enumchron => 'serial_issue_number',
579 copynumber => 'copy_number',
580 stocknumber => 'inventory_number',
581 new_status => 'new_status'
585 =head2 Internal methods
597 Kyle M Hall <kyle@bywatersolutions.com>