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 under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 3 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 use List::MoreUtils qw(any);
26 use Koha::DateUtils qw( dt_from_string );
31 use Koha::IssuingRules;
32 use Koha::Item::Transfer::Limits;
33 use Koha::Item::Transfers;
36 use Koha::StockRotationItem;
37 use Koha::StockRotationRotas;
39 use base qw(Koha::Object);
43 Koha::Item - Koha Item object class
51 =head3 effective_itemtype
53 Returns the itemtype for the item based on whether item level itemtypes are set or not.
57 sub effective_itemtype {
60 return $self->_result()->effective_itemtype();
70 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
72 return $self->{_home_branch};
82 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
84 return $self->{_holding_branch};
89 my $biblio = $item->biblio;
91 Return the bibliographic record of this item
97 my $biblio_rs = $self->_result->biblio;
98 return Koha::Biblio->_new_from_dbic( $biblio_rs );
103 my $biblioitem = $item->biblioitem;
105 Return the biblioitem record of this item
111 my $biblioitem_rs = $self->_result->biblioitem;
112 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
117 my $checkout = $item->checkout;
119 Return the checkout for this item
125 my $checkout_rs = $self->_result->issue;
126 return unless $checkout_rs;
127 return Koha::Checkout->_new_from_dbic( $checkout_rs );
132 my $holds = $item->holds();
133 my $holds = $item->holds($params);
134 my $holds = $item->holds({ found => 'W'});
136 Return holds attached to an item, optionally accept a hashref of params to pass to search
141 my ( $self,$params ) = @_;
142 my $holds_rs = $self->_result->reserves->search($params);
143 return Koha::Holds->_new_from_dbic( $holds_rs );
148 my $transfer = $item->get_transfer;
150 Return the transfer if the item is in transit or undef
156 my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
157 return unless $transfer_rs;
158 return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
161 =head3 last_returned_by
163 Gets and sets the last borrower to return an item.
165 Accepts and returns Koha::Patron objects
167 $item->last_returned_by( $borrowernumber );
169 $last_returned_by = $item->last_returned_by();
173 sub last_returned_by {
174 my ( $self, $borrower ) = @_;
176 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
179 return $items_last_returned_by_rs->update_or_create(
180 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
183 unless ( $self->{_last_returned_by} ) {
184 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
186 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
190 return $self->{_last_returned_by};
194 =head3 can_article_request
196 my $bool = $item->can_article_request( $borrower )
198 Returns true if item can be specifically requested
200 $borrower must be a Koha::Patron object
204 sub can_article_request {
205 my ( $self, $borrower ) = @_;
207 my $rule = $self->article_request_type($borrower);
209 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
213 =head3 hidden_in_opac
215 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
217 Returns true if item fields match the hidding criteria defined in $rules.
218 Returns false otherwise.
220 Takes HASHref that can have the following parameters:
222 $rules : { <field> => [ value_1, ... ], ... }
224 Note: $rules inherits its structure from the parsed YAML from reading
225 the I<OpacHiddenItems> system preference.
230 my ( $self, $params ) = @_;
232 my $rules = $params->{rules} // {};
235 if C4::Context->preference('hidelostitems') and
238 my $hidden_in_opac = 0;
240 foreach my $field ( keys %{$rules} ) {
242 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
248 return $hidden_in_opac;
251 =head3 can_be_transferred
253 $item->can_be_transferred({ to => $to_library, from => $from_library })
254 Checks if an item can be transferred to given library.
256 This feature is controlled by two system preferences:
257 UseBranchTransferLimits to enable / disable the feature
258 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
259 for setting the limitations
261 Takes HASHref that can have the following parameters:
262 MANDATORY PARAMETERS:
265 $from : Koha::Library # if not given, item holdingbranch
266 # will be used instead
268 Returns 1 if item can be transferred to $to_library, otherwise 0.
270 To find out whether at least one item of a Koha::Biblio can be transferred, please
271 see Koha::Biblio->can_be_transferred() instead of using this method for
272 multiple items of the same biblio.
276 sub can_be_transferred {
277 my ($self, $params) = @_;
279 my $to = $params->{to};
280 my $from = $params->{from};
282 $to = $to->branchcode;
283 $from = defined $from ? $from->branchcode : $self->holdingbranch;
285 return 1 if $from eq $to; # Transfer to current branch is allowed
286 return 1 unless C4::Context->preference('UseBranchTransferLimits');
288 my $limittype = C4::Context->preference('BranchTransferLimitsType');
289 return Koha::Item::Transfer::Limits->search({
292 $limittype => $limittype eq 'itemtype'
293 ? $self->effective_itemtype : $self->ccode
297 =head3 pickup_locations
299 @pickup_locations = $item->pickup_locations( {patron => $patron } )
301 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)
302 and if item can be transfered to each pickup location.
306 sub pickup_locations {
307 my ($self, $params) = @_;
309 my $patron = $params->{patron};
311 my $circ_control_branch =
312 C4::Circulation::_GetCircControlBranch( $self->unblessed(), $patron );
314 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
316 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
317 my $library = $branch_control eq 'holdingbranch' ? $self->holding_branch : $self->home_branch;
320 if(defined $patron) {
321 return @libs if $branchitemrule->{holdallowed} == 3 && !$library->validate_hold_sibling( {branchcode => $patron->branchcode} );
322 return @libs if $branchitemrule->{holdallowed} == 1 && $library->branchcode ne $patron->branchcode;
325 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
326 @libs = $library->get_hold_libraries;
327 my $circ_control_library = Koha::Libraries->find($circ_control_branch);
328 push @libs, $circ_control_library unless scalar(@libs) > 0;
329 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
330 push @libs, $self->home_branch;
331 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
332 push @libs, $self->holding_branch;
334 @libs = Koha::Libraries->search({
337 order_by => ['branchname']
341 my @pickup_locations;
342 foreach my $library (@libs) {
343 if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
344 push @pickup_locations, $library->unblessed;
347 return wantarray ? @pickup_locations : \@pickup_locations;
350 =head3 article_request_type
352 my $type = $item->article_request_type( $borrower )
354 returns 'yes', 'no', 'bib_only', or 'item_only'
356 $borrower must be a Koha::Patron object
360 sub article_request_type {
361 my ( $self, $borrower ) = @_;
363 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
365 $branch_control eq 'homebranch' ? $self->homebranch
366 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
368 my $borrowertype = $borrower->categorycode;
369 my $itemtype = $self->effective_itemtype();
370 my $issuing_rule = Koha::IssuingRules->get_effective_issuing_rule({ categorycode => $borrowertype, itemtype => $itemtype, branchcode => $branchcode });
372 return q{} unless $issuing_rule;
373 return $issuing_rule->article_requests || q{}
382 my $attributes = { order_by => 'priority' };
383 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
385 itemnumber => $self->itemnumber,
388 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
389 waitingdate => { '!=' => undef },
392 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
393 return Koha::Holds->_new_from_dbic($hold_rs);
396 =head3 stockrotationitem
398 my $sritem = Koha::Item->stockrotationitem;
400 Returns the stock rotation item associated with the current item.
404 sub stockrotationitem {
406 my $rs = $self->_result->stockrotationitem;
408 return Koha::StockRotationItem->_new_from_dbic( $rs );
413 my $item = $item->add_to_rota($rota_id);
415 Add this item to the rota identified by $ROTA_ID, which means associating it
416 with the first stage of that rota. Should this item already be associated
417 with a rota, then we will move it to the new rota.
422 my ( $self, $rota_id ) = @_;
423 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
427 =head3 has_pending_hold
429 my $is_pending_hold = $item->has_pending_hold();
431 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
435 sub has_pending_hold {
437 my $pending_hold = $self->_result->tmp_holdsqueues;
438 return $pending_hold->count ? 1: 0;
443 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
444 my $field = $item->as_marc_field({ [ mss => $mss ] });
446 This method returns a MARC::Field object representing the Koha::Item object
447 with the current mappings configuration.
452 my ( $self, $params ) = @_;
454 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
455 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
459 my @columns = $self->_result->result_source->columns;
461 foreach my $item_field ( @columns ) {
462 my $mapping = $mss->{ "items.$item_field"}[0];
463 my $tagfield = $mapping->{tagfield};
464 my $tagsubfield = $mapping->{tagsubfield};
465 next if !$tagfield; # TODO: Should we raise an exception instead?
466 # Feels like safe fallback is better
468 push @subfields, $tagsubfield => $self->$item_field;
471 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
472 push( @subfields, @{$unlinked_item_subfields} )
473 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
477 $field = MARC::Field->new(
478 "$item_tag", ' ', ' ', @subfields
484 =head3 to_api_mapping
486 This method returns the mapping for representing a Koha::Item object
493 itemnumber => 'item_id',
494 biblionumber => 'biblio_id',
495 biblioitemnumber => undef,
496 barcode => 'external_id',
497 dateaccessioned => 'acquisition_date',
498 booksellerid => 'acquisition_source',
499 homebranch => 'home_library_id',
500 price => 'purchase_price',
501 replacementprice => 'replacement_price',
502 replacementpricedate => 'replacement_price_date',
503 datelastborrowed => 'last_checkout_date',
504 datelastseen => 'last_seen_date',
506 notforloan => 'not_for_loan_status',
507 damaged => 'damaged_status',
508 damaged_on => 'damaged_date',
509 itemlost => 'lost_status',
510 itemlost_on => 'lost_date',
511 withdrawn => 'withdrawn',
512 withdrawn_on => 'withdrawn_date',
513 itemcallnumber => 'callnumber',
514 coded_location_qualifier => 'coded_location_qualifier',
515 issues => 'checkouts_count',
516 renewals => 'renewals_count',
517 reserves => 'holds_count',
518 restricted => 'restricted_status',
519 itemnotes => 'public_notes',
520 itemnotes_nonpublic => 'internal_notes',
521 holdingbranch => 'holding_library_id',
523 timestamp => 'timestamp',
524 location => 'location',
525 permanent_location => 'permanent_location',
526 onloan => 'checked_out_date',
527 cn_source => 'call_number_source',
528 cn_sort => 'call_number_sort',
529 ccode => 'collection_code',
530 materials => 'materials_notes',
532 itype => 'item_type',
533 more_subfields_xml => 'extended_subfields',
534 enumchron => 'serial_issue_number',
535 copynumber => 'copy_number',
536 stocknumber => 'inventory_number',
537 new_status => 'new_status'
541 =head2 Internal methods
553 Kyle M Hall <kyle@bywatersolutions.com>