From 4dfac36d7d2861de87c3e73e9e725431c209a131 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Tue, 13 Dec 2016 16:06:26 +0200 Subject: [PATCH] Bug 17712: Item and biblio availability in search context Usage: my $availability = Koha::Availability::Search->item({ item => $item, })->in_opac; if ($availability->available) { # yes } --- Koha/Availability/Search.pm | 47 ++++ Koha/Biblio/Availability/Search.pm | 177 +++++++++++++++ Koha/Item/Availability/Search.pm | 76 +++++++ t/db_dependent/Koha/Biblio/Availability/Search.t | 164 ++++++++++++++ t/db_dependent/Koha/Item/Availability/Search.t | 266 +++++++++++++++++++++++ 5 files changed, 730 insertions(+) create mode 100644 Koha/Availability/Search.pm create mode 100644 Koha/Biblio/Availability/Search.pm create mode 100644 Koha/Item/Availability/Search.pm create mode 100644 t/db_dependent/Koha/Biblio/Availability/Search.t create mode 100644 t/db_dependent/Koha/Item/Availability/Search.t diff --git a/Koha/Availability/Search.pm b/Koha/Availability/Search.pm new file mode 100644 index 0000000..90b100e --- /dev/null +++ b/Koha/Availability/Search.pm @@ -0,0 +1,47 @@ +package Koha::Availability::Search; + +# Copyright Koha-Suomi Oy 2016 +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use Koha::Biblio::Availability::Search; +use Koha::Item::Availability::Search; + +use Koha::Exceptions; + +sub new { + my ($class, $params) = @_; + + my $self = {}; + + bless $self, $class; +} + +sub biblio { + my ($self, $params) = @_; + + return Koha::Biblio::Availability::Search->new($params); +} + +sub item { + my ($self, $params) = @_; + + return Koha::Item::Availability::Search->new($params); +} + +1; diff --git a/Koha/Biblio/Availability/Search.pm b/Koha/Biblio/Availability/Search.pm new file mode 100644 index 0000000..9c07450 --- /dev/null +++ b/Koha/Biblio/Availability/Search.pm @@ -0,0 +1,177 @@ +package Koha::Biblio::Availability::Search; + +# Copyright Koha-Suomi Oy 2016 +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use base qw(Koha::Biblio::Availability); + +use Koha::Item::Availability::Search; + +=head1 NAME + +Koha::Biblio::Availability::Search - Koha Biblio Availability Search object class + +=head1 SYNOPSIS + +my $searchability = Koha::Biblio::Availability::Search->new({ + biblio => $biblio, # which biblio this availability is for +}) + +=head1 DESCRIPTION + +Class for checking biblio search availability. + +This class contains subroutines to determine biblio's availability for search +result in different contexts. + +=head2 Class Methods + +=cut + +=head3 new + +Constructs an biblio search availability object. Biblio is always required. + +MANDATORY PARAMETERS + + biblio (or biblionumber) + +Biblio is a Koha::Biblio -object. + +OPTIONAL PARAMETERS + +Returns a Koha::Biblio::Availability::Search -object. + +=cut + +sub new { + my ($class, $params) = @_; + + my $self = $class->SUPER::new($params); + + return $self; +} + +sub in_intranet { + my ($self, $params) = @_; + + $self->reset; + + # Item looper + $self->_item_looper($params); + + return $self; +} + +sub in_opac { + my ($self, $params) = @_; + + $self->reset; + + $params->{'opac'} = 1; + + # Item looper + $self->_item_looper($params); + + return $self; +} + +sub _item_looper { + my ($self, $params) = @_; + + my @items = $self->biblio->items; + my @hostitemnumbers = C4::Items::get_hostitemnumbers_of($self->biblio->biblionumber); + if (@hostitemnumbers) { + my @hostitems = Koha::Items->search({ + itemnumber => { 'in' => @hostitemnumbers } + }); + push @items, @hostitems; + } + + if (scalar(@items) == 0) { + $self->unavailable(Koha::Exceptions::Biblio::NoAvailableItems->new); + return; + } + + my $opac = $params->{'opac'} ? 1:0; + my $hidelostitems = 0; + if (!$opac) { + $hidelostitems = C4::Context->preference('hidelostitems'); + } + + # Stop calculating item availabilities after $limit available items are found. + # E.g. parameter 'limit' with value 1 will find only one available item and + # return biblio as available if no other unavailabilities are found. If you + # want to calculate availability of every item in this biblio, do not give this + # parameter. + my $limit = $params->{'limit'}; + my $avoid_queries_after = + C4::Context->preference('MaxSearchResultsItemsPerRecordStatusCheck'); + my $count = 0; + foreach my $item (@items) { + # Break out of loop after $limit items are found available + if (defined $limit && @{$self->{'item_availabilities'}} >= $limit) { + last; + } + + my $item_availability = Koha::Item::Availability::Search->new({ + item => $item, + }); + if ($count >= $avoid_queries_after) { + # A couple heuristics to limit how many times + # we query the database for item transfer information, sacrificing + # accuracy in some cases for speed; + # + # 1. don't query if item has one of the other statuses (done inside + # item availability calculation) + # 2. don't check holds status if ignore_holds parameter is given + # 3. don't check transfer status if ignore_transfer parameter is given + $params->{'ignore_holds'} = 1; + $params->{'ignore_transfer'} = 1; + } + if ($opac) { + $item_availability = $item_availability->in_opac($params); + my $unavails = $item_availability->unavailabilities; + # Hide item in OPAC context if system preference hidelostitems is + # enabled. + my $lost = exists $unavails->{'Koha::Exceptions::Item::Lost'}; + if ($hidelostitems && $lost) { + next; + } + } else { + $item_availability = $item_availability->in_intranet($params); + } + if ($item_availability->available) { + push $self->{'item_availabilities'}, $item_availability; + } else { + push $self->{'item_unavailabilities'}, $item_availability; + } + $count++; + } + + # After going through items, if none are found available, set the biblio + # unavailable + if (@{$self->{'item_availabilities'}} == 0) { + $self->unavailable(Koha::Exceptions::Biblio::NoAvailableItems->new); + } + + return $self; +} + +1; diff --git a/Koha/Item/Availability/Search.pm b/Koha/Item/Availability/Search.pm new file mode 100644 index 0000000..5fa6960 --- /dev/null +++ b/Koha/Item/Availability/Search.pm @@ -0,0 +1,76 @@ +package Koha::Item::Availability::Search; + +# Copyright Koha-Suomi Oy 2016 +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use base qw(Koha::Item::Availability); + +use Koha::Availability::Checks::Item; + +sub new { + my ($class, $params) = @_; + + my $self = $class->SUPER::new($params); + + return $self; +} + +sub in_opac { + my ($self, $params) = @_; + my $reason; + + $self->reset; + + my $item = $self->item; + + my $itemcalc = Koha::Availability::Checks::Item->new($item); + $self->unavailable($reason) if $reason = $itemcalc->lost; + $self->unavailable($reason) if $reason = $itemcalc->damaged; + $self->unavailable($reason) if $reason = $itemcalc->from_another_library; + $self->unavailable($reason) if $reason = $itemcalc->notforloan; + $self->unavailable($reason) if $reason = $itemcalc->restricted; + $self->unavailable($reason) if $reason = $itemcalc->unknown_barcode; + $self->unavailable($reason) if $reason = $itemcalc->withdrawn; + if ($itemcalc->onloan) { + $self->unavailable($reason) if $reason = $itemcalc->checked_out; + } + + if (!$params->{'ignore_holds'} && $self->available) { + $self->unavailable($reason) if $reason = $itemcalc->held; + } + if (!$params->{'ignore_transfer'} && $self->available) { + $self->unavailable($reason) if $reason = $itemcalc->transfer; + } + + return $self; +} + +sub in_intranet { + my ($self, $params) = @_; + + # TODO: + # Do we want to show some additional information in intranet context? + # Perhaps make some additional checks, or override some unavailabilities + # found in OPAC. + # + # For now, just run the same checks as OPAC context. + return $self->in_opac($params); +} + +1; diff --git a/t/db_dependent/Koha/Biblio/Availability/Search.t b/t/db_dependent/Koha/Biblio/Availability/Search.t new file mode 100644 index 0000000..9d876e1 --- /dev/null +++ b/t/db_dependent/Koha/Biblio/Availability/Search.t @@ -0,0 +1,164 @@ +#!/usr/bin/perl + +# Copyright Koha-Suomi Oy 2016 +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More tests => 4; +use t::lib::Mocks; +use t::lib::TestBuilder; +require t::db_dependent::Koha::Availability::Helpers; +use Benchmark; + +use Koha::Database; +use Koha::IssuingRules; +use Koha::Items; +use Koha::ItemTypes; + +use Koha::Availability::Search; +use Koha::Biblio::Availability::Search; + +my $schema = Koha::Database->new->schema; +$schema->storage->txn_begin; + +my $builder = t::lib::TestBuilder->new; + +set_default_system_preferences(); +set_default_circulation_rules(); + +subtest 'Biblio with zero available items' => \&t_no_available_items; +sub t_no_available_items { + plan tests => 6; + + my $item1 = build_a_test_item(); + my $biblio = Koha::Biblios->find($item1->biblionumber); + my $item2 = build_a_test_item(); + $item2->biblionumber($biblio->biblionumber)->store; + $item2->biblioitemnumber($item1->biblioitemnumber)->store; + $item1->withdrawn('1')->store; + $item2->notforloan('1')->store; + my $patron = build_a_test_patron(); + + my $availability = Koha::Biblio::Availability::Search->new({ + biblio => $biblio, + })->in_opac; + my $expecting = 'Koha::Exceptions::Biblio::NoAvailableItems'; + + ok(!Koha::Item::Availability::Search->new({ + item => $item1, + })->in_opac->available, + 'When I look at the first item of two in this biblio, it is not available.'); + ok(!Koha::Item::Availability::Search->new({ + item => $item2 + })->in_opac->available, + 'When I look at the second item of two in this biblio, it is not available.'); + ok(!$availability->available, 'Then, the biblio is not available.'); + is($availability->unavailable, 1, 'Then, there is exactly one reason for unavailability.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, 'The reason says there are no' + .' available items in this biblio.'); + is(@{$availability->item_unavailabilities}, 2, 'There seems to be two items that are unavailable.'); +}; + +subtest 'Biblio with one available items out of two' => \&t_one_out_of_two_items_available; +sub t_one_out_of_two_items_available { + plan tests => 5; + + my $item1 = build_a_test_item(); + my $biblio = Koha::Biblios->find($item1->biblionumber); + my $item2 = build_a_test_item(); + $item2->biblionumber($biblio->biblionumber)->store; + $item2->biblioitemnumber($item1->biblioitemnumber)->store; + + my $patron = build_a_test_patron(); + $item1->withdrawn('1')->store; + + my $availability = Koha::Biblio::Availability::Search->new({biblio => $biblio})->in_opac; + my $item_availabilities = $availability->item_availabilities; + ok(!Koha::Item::Availability::Search->new({ item => $item1,})->in_opac->available, + 'When I look at the first item of two in this biblio, it is not available.'); + ok(Koha::Item::Availability::Search->new({ item => $item2,})->in_opac->available, + 'When I look at the second item of two in this biblio, it seems to be available.'); + ok($availability->available, 'Then, the biblio is available.'); + is(@{$item_availabilities}, 1, 'There seems to be one available item in this biblio.'); + is($item_availabilities->[0]->item->itemnumber, $item2->itemnumber, 'Then the only available item' + .' is the second item of this biblio.'); +}; + +subtest 'Biblio with two items out of two available' => \&t_all_items_available; +sub t_all_items_available { + plan tests => 4; + + my $item1 = build_a_test_item(); + my $biblio = Koha::Biblios->find($item1->biblionumber); + my $item2 = build_a_test_item(); + $item2->biblionumber($biblio->biblionumber)->store; + $item2->biblioitemnumber($item1->biblioitemnumber)->store; + + my $patron = build_a_test_patron(); + + my $availability = Koha::Biblio::Availability::Search->new({biblio => $biblio})->in_opac; + my $item_availabilities = $availability->item_availabilities; + ok(Koha::Item::Availability::Search->new({ item => $item1,})->in_opac->available, + 'When I look at the first item of two in this biblio, it seems to be available.'); + ok(Koha::Item::Availability::Search->new({ item => $item2,})->in_opac->available, + 'When I look at the second item of two in this biblio, it seems to be available.'); + ok($availability->available, 'Then, the biblio is available.'); + is(@{$item_availabilities}, 2, 'There seems to be two available items in this biblio.'); +}; + +subtest 'Prove that lower MaxSearchResultsItemsPerRecordStatusCheck boosts performance' => \&t_performance_proof; +sub t_performance_proof { + plan tests => 6; + + my $item = build_a_test_item(); + my $biblio = Koha::Biblios->find($item->biblionumber); + my $biblioitem = Koha::Biblioitems->find($item->biblioitemnumber); + + # add some items to biblio + my $count1 = 20; + my $count2 = 200; + for my $i (1..($count2-1)) { # one already built earlier + build_a_test_item($biblio, $biblioitem); + } + + my $availability_iterations = 10; + my @items = $biblio->items; + is(@items, $count2, "We will get availability for $count2 items."); + t::lib::Mocks::mock_preference('MaxSearchResultsItemsPerRecordStatusCheck', + $count1); + is(C4::Context->preference('MaxSearchResultsItemsPerRecordStatusCheck'), + $count1, "First test will be done with" + ." MaxSearchResultsItemsPerRecordStatusCheck = $count1"); + my $res1 = timethis($availability_iterations, sub { + Koha::Availability::Search->new->biblio({ biblio => $biblio })->in_opac; + }); + ok($res1, "Calculated search availability $availability_iterations times."); + t::lib::Mocks::mock_preference('MaxSearchResultsItemsPerRecordStatusCheck', + $count2); + is(C4::Context->preference('MaxSearchResultsItemsPerRecordStatusCheck'), + $count2, "Second test will be done with" + ." MaxSearchResultsItemsPerRecordStatusCheck = $count2"); + my $res2 = timethis($availability_iterations, sub { + Koha::Availability::Search->new->biblio({ biblio => $biblio })->in_opac; + }); + ok($res2, "Calculated search availability $availability_iterations times."); + ok($res1->cpu_a < $res2->cpu_a, 'First test was faster than second.'); +}; + +$schema->storage->txn_rollback; + +1; diff --git a/t/db_dependent/Koha/Item/Availability/Search.t b/t/db_dependent/Koha/Item/Availability/Search.t new file mode 100644 index 0000000..0f63446 --- /dev/null +++ b/t/db_dependent/Koha/Item/Availability/Search.t @@ -0,0 +1,266 @@ +#!/usr/bin/perl + +# Copyright Koha-Suomi Oy 2016 +# +# This file is part of Koha +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope t1hat it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More tests => 10; +use t::lib::Mocks; +use t::lib::TestBuilder; +require t::db_dependent::Koha::Availability::Helpers; + +use Koha::Database; +use Koha::IssuingRules; +use Koha::Items; +use Koha::ItemTypes; + +use Koha::Item::Availability::Search; + +my $schema = Koha::Database->new->schema; +$schema->storage->txn_begin; + +my $builder = t::lib::TestBuilder->new; + +set_default_system_preferences(); +set_default_circulation_rules(); + +subtest 'Given item is in a good state for availability' => \&t_ok_availability; +sub t_ok_availability { + plan tests => 3; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item(); + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + + ok($availability->available, 'When I request availability, then the item is available.'); + ok(!$availability->confirm, 'Then nothing needs to be confirmed.'); + ok(!$availability->unavailable, 'Then there are no reasons to be unavailable.'); +} + +subtest 'Given item is damaged' => sub { + plan tests => 2; + + subtest 'Given AllowHoldsOnDamagedItems is disabled' => \&t_damaged_item_allow_disabled; + subtest 'Given AllowHoldsOnDamagedItems is enabled' => \&t_damaged_item_allow_enabled; + sub t_damaged_item_allow_disabled { + plan tests => 4; + + t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 0); + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({damaged=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::Damaged'; + + is($item->damaged, 1, 'When I look at the item, I see that it is damaged.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating damaged item.'); + }; + sub t_damaged_item_allow_enabled { + plan tests => 4; + + t::lib::Mocks::mock_preference('AllowHoldsOnDamagedItems', 1); + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({damaged=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + + is($item->damaged, 1, 'When I look at the item, I see that it is damaged.'); + ok($availability->available, 'When I request availability, then the item is available.'); + ok(!$availability->unavailable, 'Then there are no statuses for unavailability.'); + ok(!$availability->confirm, 'Then there is no reason to have availability confirmed.'); + }; +}; + +subtest 'Given item is lost' => \&t_lost; +sub t_lost { + plan tests => 4; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({itemlost=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::Lost'; + + is($item->itemlost, 1, 'When I try to look at the item, I find out that it is lost.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating lost item.'); +}; + +subtest 'Given item is not for loan' => \&t_notforloan; +sub t_notforloan { + plan tests => 4; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({notforloan=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::NotForLoan'; + + is($item->notforloan, 1, 'When I look at the item, I see that it is not for loan.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating the item is not for loan.'); +}; + +subtest 'Given item type is not for loan' => sub { + + subtest 'Given item-level_itypes is on (item-itemtype)' => \&t_itemlevel_itemtype_notforloan_item_level_itypes_on; + subtest 'Given item-level_itypes is off (biblioitem-itemtype)' => \&t_itemlevel_itemtype_notforloan_item_level_itypes_off; + sub t_itemlevel_itemtype_notforloan_item_level_itypes_on { + plan tests => 5; + + t::lib::Mocks::mock_preference('item-level_itypes', 1); + + my $patron = build_a_test_patron(); + my $item = build_a_test_item(); + my $biblioitem = Koha::Biblioitems->find($item->biblioitemnumber); + my $itemtype = Koha::ItemTypes->find($item->itype); + $itemtype->set({notforloan=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::ItemType::NotForLoan'; + + is(Koha::ItemTypes->find($biblioitem->itemtype)->notforloan, 0, 'Biblioitem itemtype is for loan.'); + is(Koha::ItemTypes->find($item->itype)->notforloan, 1, 'Item itemtype is not for loan.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + "Then there is an unavailability status indicating the itemtype is not forloan."); + }; + sub t_itemlevel_itemtype_notforloan_item_level_itypes_off { + plan tests => 5; + + t::lib::Mocks::mock_preference('item-level_itypes', 0); + + my $patron = build_a_test_patron(); + my $item = build_a_test_item(); + my $biblioitem = Koha::Biblioitems->find($item->biblioitemnumber); + my $itemtype = Koha::ItemTypes->find($biblioitem->itemtype); + $itemtype->set({notforloan=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::ItemType::NotForLoan'; + + is(Koha::ItemTypes->find($biblioitem->itemtype)->notforloan, 1, 'Biblioitem itemtype is not for loan.'); + is(Koha::ItemTypes->find($item->itype)->notforloan, 0, 'Item itemtype is for loan.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + "Then there is an unavailability status indicating the itemtype is not forloan."); + }; +}; + +subtest 'Given item is ordered' => \&t_ordered; +sub t_ordered { + plan tests => 4; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({notforloan=>-1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::NotForLoan'; + + ok(!$availability->available, 'When I request availability, then the item is not available.'); + ok($availability->unavailable, 'Then there is one reason for unavailability.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then the unavailability status is indicating not for loan status.'); + is($availability->unavailabilities->{$expecting}->code, 'Ordered', 'Not for loan code says the item is ordered.') +}; + +subtest 'Given item is restricted' => \&t_restricted; +sub t_restricted { + plan tests => 4; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({restricted=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::Restricted'; + + is($item->restricted, 1, 'When I look at the item, I see that it is restricted.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating restricted item.'); +}; + +subtest 'Given item has no barcode' => \&t_unknown_barcode; +sub t_unknown_barcode { + plan tests => 4; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({barcode=>undef})->store; + my $expecting = 'Koha::Exceptions::Item::UnknownBarcode'; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + + is($item->barcode, undef, 'When I look at the item, we see that it has undefined barcode.'); + ok($availability->unavailable, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating unknown barcode.'); +}; + +subtest 'Given item is withdrawn' => \&t_withdrawn; +sub t_withdrawn { + plan tests => 4; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item()->set({restricted=>1})->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::Restricted'; + + is($item->restricted, 1, 'When I look at the item, I see that it is restricted.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + is($availability->unavailable, 1, 'Then there is only one unavailability reason.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating restricted item.'); +}; + +subtest 'Held' => \&t_held; +sub t_held { + plan tests => 8; + + my $patron = build_a_test_patron(); + my $item = build_a_test_item(); + my $reserve_id = add_item_level_hold($item, $patron, $item->homebranch); + my $hold = Koha::Holds->find({ borrowernumber => $patron->borrowernumber }); + Koha::IssuingRules->search->delete; + my $rule = Koha::IssuingRule->new({ + branchcode => $item->homebranch, + itemtype => $item->effective_itemtype, + categorycode => $patron->categorycode, + holds_per_record => 9001, + reservesallowed => 9001, + opacitemholds => 'Y', + })->store; + my $availability = Koha::Item::Availability::Search->new({item => $item})->in_opac; + my $expecting = 'Koha::Exceptions::Item::Held'; + + is($rule->reservesallowed, 9001, 'As I look at circulation rules, I can see that many reserves are allowed.'); + ok($reserve_id, 'I have placed a hold on an item.'); + is($hold->itemnumber, $item->itemnumber, 'The item I have hold for is the same item I will check availability for.'); + ok(!$availability->available, 'When I request availability, then the item is not available.'); + ok(!$availability->confirm, 'Then there is nothing to be confirmed.'); + ok(!$availability->note, 'Then there are no additional notes.'); + is($availability->unavailable, 1, 'Then there is only one reason for unavailability.'); + is(ref($availability->unavailabilities->{$expecting}), $expecting, + 'Then there is an unavailability status indicating that I have already held this.'); +}; + +$schema->storage->txn_rollback; + +1; -- 1.9.1