Bug 24569: (QA follow-up) Fix closing <a> tag
[koha.git] / Koha / Item.pm
blobe8e5e21529035d0d427d1beb27e65d8fbb69f7ab
1 package Koha::Item;
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
10 # version.
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.
20 use Modern::Perl;
22 use Carp;
23 use List::MoreUtils qw(any);
25 use Koha::Database;
26 use Koha::DateUtils qw( dt_from_string );
28 use C4::Context;
29 use C4::Circulation;
30 use C4::Reserves;
31 use Koha::Checkouts;
32 use Koha::CirculationRules;
33 use Koha::Item::Transfer::Limits;
34 use Koha::Item::Transfers;
35 use Koha::Patrons;
36 use Koha::Libraries;
37 use Koha::StockRotationItem;
38 use Koha::StockRotationRotas;
40 use base qw(Koha::Object);
42 =head1 NAME
44 Koha::Item - Koha Item object class
46 =head1 API
48 =head2 Class methods
50 =cut
52 =head3 effective_itemtype
54 Returns the itemtype for the item based on whether item level itemtypes are set or not.
56 =cut
58 sub effective_itemtype {
59 my ( $self ) = @_;
61 return $self->_result()->effective_itemtype();
64 =head3 home_branch
66 =cut
68 sub home_branch {
69 my ($self) = @_;
71 $self->{_home_branch} ||= Koha::Libraries->find( $self->homebranch() );
73 return $self->{_home_branch};
76 =head3 holding_branch
78 =cut
80 sub holding_branch {
81 my ($self) = @_;
83 $self->{_holding_branch} ||= Koha::Libraries->find( $self->holdingbranch() );
85 return $self->{_holding_branch};
88 =head3 biblio
90 my $biblio = $item->biblio;
92 Return the bibliographic record of this item
94 =cut
96 sub biblio {
97 my ( $self ) = @_;
98 my $biblio_rs = $self->_result->biblio;
99 return Koha::Biblio->_new_from_dbic( $biblio_rs );
102 =head3 biblioitem
104 my $biblioitem = $item->biblioitem;
106 Return the biblioitem record of this item
108 =cut
110 sub biblioitem {
111 my ( $self ) = @_;
112 my $biblioitem_rs = $self->_result->biblioitem;
113 return Koha::Biblioitem->_new_from_dbic( $biblioitem_rs );
116 =head3 checkout
118 my $checkout = $item->checkout;
120 Return the checkout for this item
122 =cut
124 sub checkout {
125 my ( $self ) = @_;
126 my $checkout_rs = $self->_result->issue;
127 return unless $checkout_rs;
128 return Koha::Checkout->_new_from_dbic( $checkout_rs );
131 =head3 holds
133 my $holds = $item->holds();
134 my $holds = $item->holds($params);
135 my $holds = $item->holds({ found => 'W'});
137 Return holds attached to an item, optionally accept a hashref of params to pass to search
139 =cut
141 sub holds {
142 my ( $self,$params ) = @_;
143 my $holds_rs = $self->_result->reserves->search($params);
144 return Koha::Holds->_new_from_dbic( $holds_rs );
147 =head3 get_transfer
149 my $transfer = $item->get_transfer;
151 Return the transfer if the item is in transit or undef
153 =cut
155 sub get_transfer {
156 my ( $self ) = @_;
157 my $transfer_rs = $self->_result->branchtransfers->search({ datearrived => undef })->first;
158 return unless $transfer_rs;
159 return Koha::Item::Transfer->_new_from_dbic( $transfer_rs );
162 =head3 last_returned_by
164 Gets and sets the last borrower to return an item.
166 Accepts and returns Koha::Patron objects
168 $item->last_returned_by( $borrowernumber );
170 $last_returned_by = $item->last_returned_by();
172 =cut
174 sub last_returned_by {
175 my ( $self, $borrower ) = @_;
177 my $items_last_returned_by_rs = Koha::Database->new()->schema()->resultset('ItemsLastBorrower');
179 if ($borrower) {
180 return $items_last_returned_by_rs->update_or_create(
181 { borrowernumber => $borrower->borrowernumber, itemnumber => $self->id } );
183 else {
184 unless ( $self->{_last_returned_by} ) {
185 my $result = $items_last_returned_by_rs->single( { itemnumber => $self->id } );
186 if ($result) {
187 $self->{_last_returned_by} = Koha::Patrons->find( $result->get_column('borrowernumber') );
191 return $self->{_last_returned_by};
195 =head3 can_article_request
197 my $bool = $item->can_article_request( $borrower )
199 Returns true if item can be specifically requested
201 $borrower must be a Koha::Patron object
203 =cut
205 sub can_article_request {
206 my ( $self, $borrower ) = @_;
208 my $rule = $self->article_request_type($borrower);
210 return 1 if $rule && $rule ne 'no' && $rule ne 'bib_only';
211 return q{};
214 =head3 hidden_in_opac
216 my $bool = $item->hidden_in_opac({ [ rules => $rules ] })
218 Returns true if item fields match the hidding criteria defined in $rules.
219 Returns false otherwise.
221 Takes HASHref that can have the following parameters:
222 OPTIONAL PARAMETERS:
223 $rules : { <field> => [ value_1, ... ], ... }
225 Note: $rules inherits its structure from the parsed YAML from reading
226 the I<OpacHiddenItems> system preference.
228 =cut
230 sub hidden_in_opac {
231 my ( $self, $params ) = @_;
233 my $rules = $params->{rules} // {};
235 return 1
236 if C4::Context->preference('hidelostitems') and
237 $self->itemlost > 0;
239 my $hidden_in_opac = 0;
241 foreach my $field ( keys %{$rules} ) {
243 if ( any { $self->$field eq $_ } @{ $rules->{$field} } ) {
244 $hidden_in_opac = 1;
245 last;
249 return $hidden_in_opac;
252 =head3 can_be_transferred
254 $item->can_be_transferred({ to => $to_library, from => $from_library })
255 Checks if an item can be transferred to given library.
257 This feature is controlled by two system preferences:
258 UseBranchTransferLimits to enable / disable the feature
259 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
260 for setting the limitations
262 Takes HASHref that can have the following parameters:
263 MANDATORY PARAMETERS:
264 $to : Koha::Library
265 OPTIONAL PARAMETERS:
266 $from : Koha::Library # if not given, item holdingbranch
267 # will be used instead
269 Returns 1 if item can be transferred to $to_library, otherwise 0.
271 To find out whether at least one item of a Koha::Biblio can be transferred, please
272 see Koha::Biblio->can_be_transferred() instead of using this method for
273 multiple items of the same biblio.
275 =cut
277 sub can_be_transferred {
278 my ($self, $params) = @_;
280 my $to = $params->{to};
281 my $from = $params->{from};
283 $to = $to->branchcode;
284 $from = defined $from ? $from->branchcode : $self->holdingbranch;
286 return 1 if $from eq $to; # Transfer to current branch is allowed
287 return 1 unless C4::Context->preference('UseBranchTransferLimits');
289 my $limittype = C4::Context->preference('BranchTransferLimitsType');
290 return Koha::Item::Transfer::Limits->search({
291 toBranch => $to,
292 fromBranch => $from,
293 $limittype => $limittype eq 'itemtype'
294 ? $self->effective_itemtype : $self->ccode
295 })->count ? 0 : 1;
298 =head3 pickup_locations
300 @pickup_locations = $item->pickup_locations( {patron => $patron } )
302 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)
303 and if item can be transferred to each pickup location.
305 =cut
307 sub pickup_locations {
308 my ($self, $params) = @_;
310 my $patron = $params->{patron};
312 my $circ_control_branch =
313 C4::Reserves::GetReservesControlBranch( $self->unblessed(), $patron->unblessed );
314 my $branchitemrule =
315 C4::Circulation::GetBranchItemRule( $circ_control_branch, $self->itype );
317 my @libs;
318 if(defined $patron) {
319 return @libs if $branchitemrule->{holdallowed} == 3 && !$self->home_branch->validate_hold_sibling( {branchcode => $patron->branchcode} );
320 return @libs if $branchitemrule->{holdallowed} == 1 && $self->home_branch->branchcode ne $patron->branchcode;
323 if ($branchitemrule->{hold_fulfillment_policy} eq 'holdgroup') {
324 @libs = $self->home_branch->get_hold_libraries;
325 push @libs, $self->home_branch unless scalar(@libs) > 0;
326 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'patrongroup') {
327 my $plib = Koha::Libraries->find({ branchcode => $patron->branchcode});
328 @libs = $plib->get_hold_libraries;
329 push @libs, $self->home_branch unless scalar(@libs) > 0;
330 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'homebranch') {
331 push @libs, $self->home_branch;
332 } elsif ($branchitemrule->{hold_fulfillment_policy} eq 'holdingbranch') {
333 push @libs, $self->holding_branch;
334 } else {
335 @libs = Koha::Libraries->search({
336 pickup_location => 1
337 }, {
338 order_by => ['branchname']
339 })->as_list;
342 my @pickup_locations;
343 foreach my $library (@libs) {
344 if ($library->pickup_location && $self->can_be_transferred({ to => $library })) {
345 push @pickup_locations, $library;
349 return wantarray ? @pickup_locations : \@pickup_locations;
352 =head3 article_request_type
354 my $type = $item->article_request_type( $borrower )
356 returns 'yes', 'no', 'bib_only', or 'item_only'
358 $borrower must be a Koha::Patron object
360 =cut
362 sub article_request_type {
363 my ( $self, $borrower ) = @_;
365 my $branch_control = C4::Context->preference('HomeOrHoldingBranch');
366 my $branchcode =
367 $branch_control eq 'homebranch' ? $self->homebranch
368 : $branch_control eq 'holdingbranch' ? $self->holdingbranch
369 : undef;
370 my $borrowertype = $borrower->categorycode;
371 my $itemtype = $self->effective_itemtype();
372 my $rule = Koha::CirculationRules->get_effective_rule(
374 rule_name => 'article_requests',
375 categorycode => $borrowertype,
376 itemtype => $itemtype,
377 branchcode => $branchcode
381 return q{} unless $rule;
382 return $rule->rule_value || q{}
385 =head3 current_holds
387 =cut
389 sub current_holds {
390 my ( $self ) = @_;
391 my $attributes = { order_by => 'priority' };
392 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
393 my $params = {
394 itemnumber => $self->itemnumber,
395 suspend => 0,
396 -or => [
397 reservedate => { '<=' => $dtf->format_date(dt_from_string) },
398 waitingdate => { '!=' => undef },
401 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
402 return Koha::Holds->_new_from_dbic($hold_rs);
405 =head3 stockrotationitem
407 my $sritem = Koha::Item->stockrotationitem;
409 Returns the stock rotation item associated with the current item.
411 =cut
413 sub stockrotationitem {
414 my ( $self ) = @_;
415 my $rs = $self->_result->stockrotationitem;
416 return 0 if !$rs;
417 return Koha::StockRotationItem->_new_from_dbic( $rs );
420 =head3 add_to_rota
422 my $item = $item->add_to_rota($rota_id);
424 Add this item to the rota identified by $ROTA_ID, which means associating it
425 with the first stage of that rota. Should this item already be associated
426 with a rota, then we will move it to the new rota.
428 =cut
430 sub add_to_rota {
431 my ( $self, $rota_id ) = @_;
432 Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber);
433 return $self;
436 =head3 has_pending_hold
438 my $is_pending_hold = $item->has_pending_hold();
440 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
442 =cut
444 sub has_pending_hold {
445 my ( $self ) = @_;
446 my $pending_hold = $self->_result->tmp_holdsqueues;
447 return $pending_hold->count ? 1: 0;
450 =head3 as_marc_field
452 my $mss = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
453 my $field = $item->as_marc_field({ [ mss => $mss ] });
455 This method returns a MARC::Field object representing the Koha::Item object
456 with the current mappings configuration.
458 =cut
460 sub as_marc_field {
461 my ( $self, $params ) = @_;
463 my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
464 my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
466 my @subfields;
468 my @columns = $self->_result->result_source->columns;
470 foreach my $item_field ( @columns ) {
471 my $mapping = $mss->{ "items.$item_field"}[0];
472 my $tagfield = $mapping->{tagfield};
473 my $tagsubfield = $mapping->{tagsubfield};
474 next if !$tagfield; # TODO: Should we raise an exception instead?
475 # Feels like safe fallback is better
477 push @subfields, $tagsubfield => $self->$item_field;
480 my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
481 push( @subfields, @{$unlinked_item_subfields} )
482 if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
484 my $field;
486 $field = MARC::Field->new(
487 "$item_tag", ' ', ' ', @subfields
488 ) if @subfields;
490 return $field;
493 =head3 to_api_mapping
495 This method returns the mapping for representing a Koha::Item object
496 on the API.
498 =cut
500 sub to_api_mapping {
501 return {
502 itemnumber => 'item_id',
503 biblionumber => 'biblio_id',
504 biblioitemnumber => undef,
505 barcode => 'external_id',
506 dateaccessioned => 'acquisition_date',
507 booksellerid => 'acquisition_source',
508 homebranch => 'home_library_id',
509 price => 'purchase_price',
510 replacementprice => 'replacement_price',
511 replacementpricedate => 'replacement_price_date',
512 datelastborrowed => 'last_checkout_date',
513 datelastseen => 'last_seen_date',
514 stack => undef,
515 notforloan => 'not_for_loan_status',
516 damaged => 'damaged_status',
517 damaged_on => 'damaged_date',
518 itemlost => 'lost_status',
519 itemlost_on => 'lost_date',
520 withdrawn => 'withdrawn',
521 withdrawn_on => 'withdrawn_date',
522 itemcallnumber => 'callnumber',
523 coded_location_qualifier => 'coded_location_qualifier',
524 issues => 'checkouts_count',
525 renewals => 'renewals_count',
526 reserves => 'holds_count',
527 restricted => 'restricted_status',
528 itemnotes => 'public_notes',
529 itemnotes_nonpublic => 'internal_notes',
530 holdingbranch => 'holding_library_id',
531 paidfor => undef,
532 timestamp => 'timestamp',
533 location => 'location',
534 permanent_location => 'permanent_location',
535 onloan => 'checked_out_date',
536 cn_source => 'call_number_source',
537 cn_sort => 'call_number_sort',
538 ccode => 'collection_code',
539 materials => 'materials_notes',
540 uri => 'uri',
541 itype => 'item_type',
542 more_subfields_xml => 'extended_subfields',
543 enumchron => 'serial_issue_number',
544 copynumber => 'copy_number',
545 stocknumber => 'inventory_number',
546 new_status => 'new_status'
550 =head2 Internal methods
552 =head3 _type
554 =cut
556 sub _type {
557 return 'Item';
560 =head1 AUTHOR
562 Kyle M Hall <kyle@bywatersolutions.com>
564 =cut