From e22e91aae4912b97a4bd8729e8c97efbfd8f3e44 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Wed, 18 May 2022 14:05:04 -0300 Subject: [PATCH] Bug 22456: Add cancellation request methods to Koha::Hold This patch adds the following helper methods for dealing with hold cancellation requests to the Koha::Hold class: * cancellation_requests * add_cancellation_request * cancellation_requestable_from_opac * cancellation_requested_by_owner To test: 1. Apply this patch 2. Run: $ kshell k$ prove t/db_dependent/Koha/Hold.t => SUCCESS: Tests pass! 3. Sign off :-D Sponsored-by: Montgomery County Public Libraries Signed-off-by: David Nind Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- Koha/Exceptions.pm | 5 + Koha/Hold.pm | 89 +++++++++++++++++ Koha/Schema/Result/Reserve.pm | 7 ++ t/db_dependent/Koha/Hold.t | 179 +++++++++++++++++++++++++++++++++- 4 files changed, 279 insertions(+), 1 deletion(-) diff --git a/Koha/Exceptions.pm b/Koha/Exceptions.pm index 56ab691df2..d257de87a3 100644 --- a/Koha/Exceptions.pm +++ b/Koha/Exceptions.pm @@ -15,6 +15,11 @@ use Exception::Class ( isa => 'Koha::Exception', description => 'Same object already exists', }, + 'Koha::Exceptions::InvalidStatus' => { + isa => 'Koha::Exception', + description => 'The current status is not valid in context', + fields => ['invalid_status'], + }, 'Koha::Exceptions::ObjectNotFound' => { isa => 'Koha::Exception', description => 'The required object doesn\'t exist', diff --git a/Koha/Hold.pm b/Koha/Hold.pm index 68d0c8581d..74d1584aef 100644 --- a/Koha/Hold.pm +++ b/Koha/Hold.pm @@ -30,6 +30,7 @@ use Koha::AuthorisedValues; use Koha::DateUtils qw( dt_from_string ); use Koha::Patrons; use Koha::Biblios; +use Koha::Hold::CancellationRequests; use Koha::Items; use Koha::Libraries; use Koha::Old::Holds; @@ -38,6 +39,7 @@ use Koha::Plugins; use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue; +use Koha::Exceptions; use Koha::Exceptions::Hold; use base qw(Koha::Object); @@ -416,6 +418,57 @@ sub is_cancelable_from_opac { return 0; # if ->is_in_transit or if ->is_waiting or ->is_in_processing } +=head3 cancellation_requestable_from_opac + + if ( $hold->cancellation_requestable_from_opac ) { ... } + +Returns a I representing if a cancellation request can be placed on the hold +from the OPAC. It targets holds that cannot be cancelled from the OPAC (see the +B method above), but for which circulation rules allow +requesting cancellation. + +Throws a B exception with the following I +values: + +=over 4 + +=item B<'hold_not_waiting'>: the hold is expected to be waiting and it is not. + +=item B<'no_item_linked'>: the waiting hold doesn't have an item properly linked. + +=back + +=cut + +sub cancellation_requestable_from_opac { + my ( $self ) = @_; + + Koha::Exceptions::InvalidStatus->throw( invalid_status => 'hold_not_waiting' ) + unless $self->is_waiting; + + my $item = $self->item; + + Koha::Exceptions::InvalidStatus->throw( invalid_status => 'no_item_linked' ) + unless $item; + + my $patron = $self->patron; + + my $controlbranch = $patron->branchcode; + + if ( C4::Context->preference('ReservesControlBranch') eq 'ItemHomeLibrary' ) { + $controlbranch = $item->homebranch; + } + + return Koha::CirculationRules->get_effective_rule( + { + categorycode => $patron->categorycode, + itemtype => $item->itype, + branchcode => $controlbranch, + rule_name => 'waiting_hold_cancellation', + } + )->rule_value ? 1 : 0; +} + =head3 is_at_destination Returns true if hold is waiting @@ -525,6 +578,42 @@ sub is_suspended { return $self->suspend(); } +=head3 add_cancellation_request + + my $cancellation_request = $hold->add_cancellation_request({ [ creation_date => $creation_date ] }); + +Adds a cancellation request to the hold. Returns the generated +I object. + +=cut + +sub add_cancellation_request { + my ( $self, $params ) = @_; + + my $request = Koha::Hold::CancellationRequest->new( + { hold_id => $self->id, + ( $params->{creation_date} ? ( creation_date => $params->{creation_date} ) : () ), + } + )->store; + + $request->discard_changes; + + return $request; +} + +=head3 cancellation_requests + + my $cancellation_requests = $hold->cancellation_requests; + +Returns related a I resultset. + +=cut + +sub cancellation_requests { + my ($self) = @_; + + return Koha::Hold::CancellationRequests->search( { hold_id => $self->id } ); +} =head3 cancel diff --git a/Koha/Schema/Result/Reserve.pm b/Koha/Schema/Result/Reserve.pm index c280e27759..08aac12c2e 100644 --- a/Koha/Schema/Result/Reserve.pm +++ b/Koha/Schema/Result/Reserve.pm @@ -486,4 +486,11 @@ __PACKAGE__->belongs_to( }, ); +__PACKAGE__->has_many( + "cancellation_requests", + "Koha::Schema::Result::HoldCancellationRequest", + { "foreign.hold_id" => "self.reserve_id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + 1; diff --git a/t/db_dependent/Koha/Hold.t b/t/db_dependent/Koha/Hold.t index db5189db40..5aeebd073a 100755 --- a/t/db_dependent/Koha/Hold.t +++ b/t/db_dependent/Koha/Hold.t @@ -19,7 +19,7 @@ use Modern::Perl; -use Test::More tests => 7; +use Test::More tests => 9; use Test::Exception; use Test::MockModule; @@ -686,3 +686,180 @@ subtest 'suspend_hold() and resume() tests' => sub { $schema->storage->txn_rollback; }; + +subtest 'cancellation_requests() and add_cancellation_request() tests' => sub { + + plan tests => 4; + + $schema->storage->txn_begin; + + t::lib::Mocks::mock_preference( 'RealTimeHoldsQueue', 0 ); + + my $hold = $builder->build_object( { class => 'Koha::Holds', } ); + + is( $hold->cancellation_requests->count, 0 ); + + # Add two cancellation requests + my $request_1 = $hold->add_cancellation_request; + isnt( $request_1->creation_date, undef, 'creation_date is set' ); + + my $requester = $builder->build_object( { class => 'Koha::Patrons' } ); + my $creation_date = '2021-06-25 14:05:35'; + + my $request_2 = $hold->add_cancellation_request( + { + creation_date => $creation_date, + } + ); + + is( $request_2->creation_date, $creation_date, 'Passed creation_date set' ); + + is( $hold->cancellation_requests->count, 2 ); + + $schema->storage->txn_rollback; +}; + +subtest 'cancellation_requestable_from_opac() tests' => sub { + + plan tests => 5; + + $schema->storage->txn_begin; + + my $category = + $builder->build_object( { class => 'Koha::Patron::Categories' } ); + my $item_home_library = + $builder->build_object( { class => 'Koha::Libraries' } ); + my $patron_home_library = + $builder->build_object( { class => 'Koha::Libraries' } ); + + my $item = + $builder->build_sample_item( { library => $item_home_library->id } ); + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { branchcode => $patron_home_library->id } + } + ); + + subtest 'Exception cases' => sub { + + plan tests => 4; + + my $hold = $builder->build_object( + { + class => 'Koha::Holds', + value => { + itemnumber => undef, + found => undef, + borrowernumber => $patron->id + } + } + ); + + throws_ok { $hold->cancellation_requestable_from_opac; } + 'Koha::Exceptions::InvalidStatus', + 'Exception thrown because hold is not waiting'; + + is( $@->invalid_status, 'hold_not_waiting' ); + + $hold = $builder->build_object( + { + class => 'Koha::Holds', + value => { + itemnumber => undef, + found => 'W', + borrowernumber => $patron->id + } + } + ); + + throws_ok { $hold->cancellation_requestable_from_opac; } + 'Koha::Exceptions::InvalidStatus', + 'Exception thrown because waiting hold has no item linked'; + + is( $@->invalid_status, 'no_item_linked' ); + }; + + # set default rule to enabled + Koha::CirculationRules->set_rule( + { + categorycode => '*', + itemtype => '*', + branchcode => '*', + rule_name => 'waiting_hold_cancellation', + rule_value => 1, + } + ); + + my $hold = $builder->build_object( + { + class => 'Koha::Holds', + value => { + itemnumber => $item->id, + found => 'W', + borrowernumber => $patron->id + } + } + ); + + t::lib::Mocks::mock_preference( 'ReservesControlBranch', + 'ItemHomeLibrary' ); + + Koha::CirculationRules->set_rule( + { + categorycode => $patron->categorycode, + itemtype => $item->itype, + branchcode => $item->homebranch, + rule_name => 'waiting_hold_cancellation', + rule_value => 0, + } + ); + + ok( !$hold->cancellation_requestable_from_opac ); + + Koha::CirculationRules->set_rule( + { + categorycode => $patron->categorycode, + itemtype => $item->itype, + branchcode => $item->homebranch, + rule_name => 'waiting_hold_cancellation', + rule_value => 1, + } + ); + + ok( + $hold->cancellation_requestable_from_opac, + 'Make sure it is picking the right circulation rule' + ); + + t::lib::Mocks::mock_preference( 'ReservesControlBranch', 'PatronLibrary' ); + + Koha::CirculationRules->set_rule( + { + categorycode => $patron->categorycode, + itemtype => $item->itype, + branchcode => $patron->branchcode, + rule_name => 'waiting_hold_cancellation', + rule_value => 0, + } + ); + + ok( !$hold->cancellation_requestable_from_opac ); + + Koha::CirculationRules->set_rule( + { + categorycode => $patron->categorycode, + itemtype => $item->itype, + branchcode => $patron->branchcode, + rule_name => 'waiting_hold_cancellation', + rule_value => 1, + } + ); + + ok( + $hold->cancellation_requestable_from_opac, + 'Make sure it is picking the right circulation rule' + ); + + $schema->storage->txn_rollback; +}; -- 2.39.5