From 6722f54cbf4cefec9d90cbefc22daaee3bbb9da2 Mon Sep 17 00:00:00 2001 From: Aleisha Amohia Date: Mon, 4 Oct 2021 17:22:38 +1300 Subject: [PATCH] Bug 31391: Request recalls and convert reserves on staff interface This enhancement adds the ability to place recalls via the staff interface by introducing a system preference, RecallsInterface, to configure where recalls can be placed. Recalls can either be placed via the OPAC, or the staff interface, or both. This is set to OPAC by default to be consistent with current behaviour. This enhancement also adds the ability to convert existing reserves to recalls easily via the staff interface. The reserve will be cancelled and a recall placed using the reserve's information. To test: 1) Update database and restart services 2) Enable UseRecalls and set the relevant recalls circulation rules 3) View the new RecallsInterface system preference. Confirm it is set to OPAC only by default 4) Check out Item A to Patron B. 5) Log into the OPAC as Patron A. Confirm you can place, view and cancel recalls as normal. 6) In the staff interface, set RecallsInterface to Staff interface only. 7) In the OPAC, confirm you can still view and cancel recalls, but can no longer place recalls. 8) In the staff interface, search for Item A and go to the Reserves tab. Place a reserve for Patron C. 9) Under the Priority column, select the dropdown and choose the recall option next to your reserve. 10) Click Update holds. Confirm when the page refreshes that your reserve has disappeared. 11) Go to the recalls tab. Confirm your reserve has been converted to a recall, and details like the patron, expiration, and pickup location have carried across to the recall. 12) On this recalls page, use the patron search to find Patron D. Test placing a record-level recall by keeping the 'recall next available item' checkbox. 13) When the page refreshes, confirm the recall was successfully placed with the correct details stored. 14) Repeat steps 12 and 13 with Patron E, this time place an item-level recall by choosing an item in the 'place a recall on a specific item' table. When selecting one of these items, the 'recall next available item' checkbox should de-select. 15) Set RecallsInterface back to OPAC only. Go back to the record and view the Recalls tab. 16) Confirm you cannot search for a patron to place a recall, but can still view and cancel recalls. Confirm you also cannot convert reserves to recalls. 17) Set RecallsInterface to both staff interface and OPAC. Confirm you can place recalls on both interfaces. 18) On the staff interface, test placing recalls that your circulation rules do not allow. For example, if 'recalls per record' is 1, ensure you're blocked from placing a second recall on a record for a patron. 19) Place a reserve for one of the patron's that already has a recall. Once complete, try to convert this reserve into a recall. Confirm you are blocked and shown a message that the hold cannot be converted to a recall. 20) Ensure tests pass t/db_dependent/Holds.t Sponsored-by: Auckland University of Technology Signed-off-by: David Signed-off-by: David Nind Signed-off-by: Pedro Amorim --- C4/Reserves.pm | 37 +++ Koha/Biblio.pm | 4 +- Koha/Item.pm | 11 +- .../prog/en/includes/holds_table.inc | 6 + .../prog/en/modules/recalls/request.tt | 249 ++++++++++++++++-- .../prog/en/modules/reserve/request.tt | 8 + .../intranet-tmpl/prog/js/staff-global.js | 4 +- opac/opac-recall.pl | 10 +- recalls/request.pl | 131 ++++++++- reserve/modrequest.pl | 14 +- reserve/request.pl | 5 + t/db_dependent/Holds.t | 79 ++++++ 12 files changed, 516 insertions(+), 42 deletions(-) diff --git a/C4/Reserves.pm b/C4/Reserves.pm index 1ffddd5b9f3..93c735fa2b4 100644 --- a/C4/Reserves.pm +++ b/C4/Reserves.pm @@ -1115,6 +1115,43 @@ sub ModReserve { if ( $rank eq "del" ) { $hold->cancel( { cancellation_reason => $cancellation_reason } ); + } elsif ( $rank eq "recall" ) { + if ( !$biblionumber ) { + $biblionumber = $hold->biblionumber; + } + my $biblio = Koha::Biblios->find($biblionumber); + if ( !$borrowernumber ) { + $borrowernumber = $hold->borrowernumber; + } + my $patron = Koha::Patrons->find($borrowernumber); + if ( $hold->item_level_hold ) { + if ( $hold->item->can_be_recalled( { patron => $patron, hold_convert => 1 } ) ) { + my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall( + { + patron => $patron, + biblio => $biblio, + branchcode => $branchcode, + expirationdate => $date, + interface => 'intranet', + item => $hold->item, + } + ); + $hold->cancel( { cancellation_reason => 'RECALLED' } ); + } + } else { + if ( $biblio->can_be_recalled( { patron => $patron, hold_convert => 1 } ) ) { + my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall( + { + patron => $patron, + biblio => $biblio, + branchcode => $branchcode, + expirationdate => $date, + interface => 'intranet', + } + ); + $hold->cancel( { cancellation_reason => 'RECALLED' } ); + } + } } elsif ( $hold->found && $hold->priority eq '0' && $date ) { # The only column that can be updated for a found hold is the expiration date diff --git a/Koha/Biblio.pm b/Koha/Biblio.pm index 4c88bcd8a58..a8cacc7aa00 100644 --- a/Koha/Biblio.pm +++ b/Koha/Biblio.pm @@ -2165,6 +2165,8 @@ sub recalls { Does biblio-level checks and returns the items attached to this biblio that are available for recall +if hold_convert $param is included, this is to say that this a check to convert a hold to a recall, so we should not check for an existing hold. + =cut sub can_be_recalled { @@ -2190,7 +2192,7 @@ sub can_be_recalled { my @all_itemnumbers; foreach my $item (@all_items) { push( @all_itemnumbers, $item->itemnumber ); - if ( $item->can_be_recalled( { patron => $patron } ) ) { + if ( $item->can_be_recalled( { patron => $patron, hold_convert => $params->{hold_convert} } ) ) { push( @itemtypes, $item->effective_itemtype ); push( @itemnumbers, $item->itemnumber ); push( @items, $item ); diff --git a/Koha/Item.pm b/Koha/Item.pm index 75877478032..43f275d68d2 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -2439,6 +2439,8 @@ sub recall { Does item-level checks and returns if items can be recalled by this borrower +if hold_convert $param is included, this is to say that this a check to convert a hold to a recall, so we should not check for an existing hold. + =cut sub can_be_recalled { @@ -2500,9 +2502,12 @@ sub can_be_recalled { ->count > 0 ); # check if this patron has already reserved this item - return 0 - if ( Koha::Holds->search( { itemnumber => $self->itemnumber, borrowernumber => $patron->borrowernumber } ) - ->count > 0 ); + unless ( $params->{hold_convert} ) { + return 0 + if ( + Koha::Holds->search( { itemnumber => $self->itemnumber, borrowernumber => $patron->borrowernumber } ) + ->count > 0 ); + } } # check item availability diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc index 70ff63bf366..894924841b3 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc @@ -66,6 +66,9 @@ [% END %] + [% IF Koha.Preference('UseRecalls') AND Koha.Preference('RecallsInterface') != 'opac' %] + + [% END %] [% ELSE %] @@ -81,6 +84,9 @@ [% ELSE %] [% END %] + [% IF Koha.Preference('UseRecalls') AND Koha.Preference('RecallsInterface') != 'opac' %] + + [% END %] [% END %] [% ELSE %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt index 09798b55970..ddba943ec9e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt @@ -2,12 +2,21 @@ [% USE Asset %] [% USE Koha %] [% USE KohaDates %] +[% USE TablesSettings %] +[% USE Branches %] +[% USE Categories %] +[% USE AuthorisedValues %] +[% USE ItemTypes %] +[% SET libraries = Branches.all %] +[% SET categories = Categories.all.unblessed %] +[% SET columns = ['name', 'cardnumber', 'dateofbirth', 'category', 'branch', 'address', 'phone'] %] +[% PROCESS "patron-search.inc" %] [% PROCESS 'i18n.inc' %] [% SET footerjs = 1 %] [% INCLUDE 'doc-head-open.inc' %] [% FILTER collapse %] - [% t("Existing recalls") | html %] + [% t("Place a recall") | html %] › [% t("Recalls") | html %] › [% t("Koha") | html %] [% END %] @@ -27,40 +36,242 @@ [% INCLUDE 'biblio-title.inc' link = 1 %] [% END %] [% WRAPPER breadcrumb_item bc_active= 1 %] - Existing recalls + Place a recall [% END %] [% END #/ WRAPPER breadcrumbs %] [% END #/ WRAPPER sub-header.inc %] [% WRAPPER 'main-container.inc' aside='biblio-view-menu' %] -

Existing recalls

+

Recalls

- [% IF Koha.Preference('UseRecalls') %] - [% IF recalls.count %] -
-
+

Place a recall on [% INCLUDE 'biblio-title.inc' link = 1 %]

+ + [% IF patron %] + + [% IF error %] +
Unable to place a recall. Check your circulation rules.
+ [% END %] + +
+ Recall details + + + +
    +
  1. + [% INCLUDE 'patron-title.inc' %] + +
  2. + + [% UNLESS ( single_branch_mode ) %] +
  3. + + +
  4. + [% END %] + +
  5. +
  6. +
+ +
+ [% INCLUDE 'csrf-token.inc' %] + + + [% IF error %] + + [% ELSE %] + + [% END %] + Cancel +
+ + + + + + + + + [% UNLESS ( single_branch_mode ) %] + + + [% END %] + + + + + + + + + [% FOREACH item IN items %] + + + + + [% UNLESS ( single_branch_mode ) %] + + + [% END %] + + + + + + + [% END %] + +
Place a recall on a specific item
 Item typeBarcodeHome libraryLast locationCollectionCall numberCopy numberVol no.Information
+ [% IF item.can_be_recalled( patron => patron ) %] + + [% ELSE %] + + + + [% END %] + [% ItemTypes.GetDescription( item.effective_itemtype ) | html %][% item.barcode | html %][% Branches.GetName( item.homebranch ) | html %][% Branches.GetName( item.holdingbranch) | html %][% AuthorisedValues.GetByCode( 'CCODE', item.ccode, 1 ) | html %][% item.itemcallnumber | html %][% item.copynumber | html %][% item.enumchron | html %] + [% IF ( item.checkout ) %] + Due [% item.checkout.date_due | $KohaDates %] + [% ELSIF ( item.get_transfer ) %] + In transit from [% Branches.GetName( item.get_transfer.frombranch ) | html %] to [% Branches.GetName( item.get_transfer.tobranch ) | html %] since + [% item.get_transfer.datesent | $KohaDates %] + [% END %] + [% IF ( item.itemlost || item.withdrawn ) %] + Unavailable (lost or missing) + [% END %] + [% IF ( item.notforloan ) %] + Not for loan ([% item.notforloan | html %]) + [% END %] + [% hold = item.current_holds.next %] + [% IF ( item.recall ) %] + + [% IF ( item.recall.waiting_date ) %] + Waiting for patron at [% Branches.GetName( item.recall.pickup_library_id ) | html %] since [% item.recall.waiting_date | $KohaDates %]. + [% ELSIF ( item.recall.item_id == item.itemnumber ) %] + Recalled by patron expected at [% Branches.GetName( item.recall.pickup_library_id ) | html %] since [% item.recall.created_date | $KohaDates %]. + [% END %] + + [% ELSIF ( hold.waitingdate ) %] + Waiting for patron at [% Branches.GetName( hold.branchcode ) | html %] since [% hold.waitingdate | $KohaDates %]. + [% ELSIF ( hold.borrowernumber == logged_in_user.borrowernumber ) %] + Patron has already placed a reserve on this item. + [% END # / IF ( item.recall or hold ) %] +
+ +
[% INCLUDE 'csrf-token.inc' %] - + - Select all - [% INCLUDE 'recalls.inc' %] -
- -
- -
- + [% IF error %] + + [% ELSE %] + + [% END %] + Cancel + + + + [% ELSE %] + + [% IF Koha.Preference('RecallsInterface') == 'opac' %] +
Due to the RecallsInterface system preference, recalls cannot be placed through the staff client.
[% ELSE %] -
No recalls have been made.
+
+

Search patrons

+
+ +
+
+ [% PROCESS patron_search_filters_simple %] + [% PROCESS patron_search_table table_id => 'table_borrowers', open_on_row_click => 1 %] +
+
+ +
+
+ [% END %] + +

Existing recalls

+ + [% IF Koha.Preference('UseRecalls') %] + [% IF recalls.count %] +
+
+ [% INCLUDE 'csrf-token.inc' %] + + + Select all + [% INCLUDE 'recalls.inc' %] +
+ +
+
+
+ + [% ELSE %] +
No recalls have been made.
+ [% END %] + [% ELSE %] +
Recalls have not been enabled. Enable the UseRecalls system preference to use recalls.
[% END %] - [% ELSE %] -
Recalls have not been enabled. Enable the UseRecalls system preference to use recalls.
[% END %] [% END %] [% MACRO jsinclude BLOCK %] [% Asset.js("js/recalls.js") | $raw %] [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'calendar.inc' %] + [% SET url_biblio_params = "biblionumber=" _ biblio.biblionumber %] + [% PROCESS patron_search_js table_id => 'table_borrowers', categories => categories, libraries => libraries, extended_attribute_types => attribute_type_codes, columns => columns, open_on_row_click => 1, on_click_url => '/cgi-bin/koha/recalls/request.pl?' _ url_biblio_params, redirect_if_one_result => 1, redirect_url => '/cgi-bin/koha/recalls/request.pl?' _ url_biblio_params, redirect_if_attribute_equal => 'cardnumber' %] + [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt index f0e3e2c18c1..3282be812cc 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt @@ -251,6 +251,14 @@

No club with this name, please, try another

[% END %] + + [% IF ( recallerror ) %] +
+

Hold cannot be converted to a recall

+

A recall cannot be placed on one or more holds. Check your circulation and fine rules.

+
+ [% END %] +
[% UNLESS multi_hold %] [% IF clubcount %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js b/koha-tmpl/intranet-tmpl/prog/js/staff-global.js index ed6f0b35f7b..989c7fa76f4 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js +++ b/koha-tmpl/intranet-tmpl/prog/js/staff-global.js @@ -756,7 +756,9 @@ function patron_autocomplete(node, options) { ? "/cgi-bin/koha/circ/circulation.pl" : link_to == "reserve" ? "/cgi-bin/koha/reserve/request.pl" - : "/cgi-bin/koha/members/moremember.pl"; + : link_to == "recall" + ? "/cgi-bin/koha/recalls/request.pl" + : "/cgi-bin/koha/members/moremember.pl"; item.link += (url_params ? "?" + url_params + "&" : "?") + "borrowernumber=" + diff --git a/opac/opac-recall.pl b/opac/opac-recall.pl index 86546bc6dee..524977a277e 100755 --- a/opac/opac-recall.pl +++ b/opac/opac-recall.pl @@ -36,6 +36,12 @@ my $op = $query->param('op') || ''; my $biblionumber = $query->param('biblionumber'); my $biblio = Koha::Biblios->find($biblionumber); +if ( $op eq 'cud-cancel' ) { + my $recall_id = $query->param('recall_id'); + Koha::Recalls->find($recall_id)->set_cancelled; + print $query->redirect('/cgi-bin/koha/opac-user.pl'); +} + if ( C4::Context->preference('UseRecalls') and C4::Context->preference('RecallsInterface') ne 'staff' ) { my $patron = Koha::Patrons->find($borrowernumber); @@ -123,10 +129,6 @@ if ( C4::Context->preference('UseRecalls') and C4::Context->preference('RecallsI $error = 'failed'; } } - } elsif ( $op eq 'cud-cancel' ) { - my $recall_id = $query->param('recall_id'); - Koha::Recalls->find($recall_id)->set_cancelled; - print $query->redirect('/cgi-bin/koha/opac-user.pl'); } my $branches = Koha::Libraries->search(); diff --git a/recalls/request.pl b/recalls/request.pl index 6dc22c28d96..36e86c5d4ca 100755 --- a/recalls/request.pl +++ b/recalls/request.pl @@ -35,30 +35,135 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( } ); -my $op = $input->param('op') || 'list'; -my @recall_ids = $input->multi_param('recall_ids'); -my $biblionumber = $input->param('biblionumber'); -my $recalls = Koha::Recalls->search( { biblio_id => $biblionumber, completed => 0 } ); -my $biblio = Koha::Biblios->find($biblionumber); +my $op = $input->param('op') || 'list'; +my @recall_ids = $input->multi_param('recall_ids'); +my $biblionumber = $input->param('biblionumber'); +my $biblio = Koha::Biblios->find($biblionumber); +my $borrowernumber = $input->param('borrowernumber'); +my $error = $input->param('error'); if ( $op eq 'cud-cancel_multiple_recalls' ) { foreach my $id (@recall_ids) { Koha::Recalls->find($id)->set_cancelled; } - $op = 'list'; +} elsif ( $op eq 'cud-request' ) { + + if ( C4::Context->preference('UseRecalls') + and C4::Context->preference('RecallsInterface') ne 'opac' + and $borrowernumber ) + { + my $patron = Koha::Patrons->find($borrowernumber); + + unless ( $biblio->can_be_recalled( { patron => $patron } ) ) { $error = 'unavailable'; } + my $items = Koha::Items->search( { biblionumber => $biblionumber } )->as_list; + + # check if already recalled + my $recalled = $biblio->recalls->filter_by_current->search( { patron_id => $borrowernumber } )->count; + if ( defined $recalled and $recalled > 0 ) { + my $recalls_per_record = Koha::CirculationRules->get_effective_rule( + { + categorycode => $patron->categorycode, + branchcode => undef, + itemtype => undef, + rule_name => 'recalls_per_record' + } + ); + if ( defined $recalls_per_record + and $recalls_per_record->rule_value + and $recalled >= $recalls_per_record->rule_value ) + { + $error = 'duplicate'; + } + } + + if ( !defined $error ) { + my $pickuploc = $input->param('pickup'); + my $expdate = $input->param('expirationdate'); + my $level = $input->param('type'); + my $itemnumber = $input->param('itemnumber'); + + my ( $recall, $due_interval, $due_date ); + + if ( !defined $level and defined $itemnumber ) { + my $item = Koha::Items->find($itemnumber); + if ( $item->can_be_recalled( { patron => $patron } ) ) { + ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall( + { + patron => $patron, + biblio => $biblio, + branchcode => $pickuploc, + item => $item, + expirationdate => $expdate, + interface => 'staff', + } + ); + } else { + $error = 'cannot'; + } + } else { + if ( $biblio->can_be_recalled( { patron => $patron } ) ) { + ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall( + { + patron => $patron, + biblio => $biblio, + branchcode => $pickuploc, + expirationdate => $expdate, + interface => 'staff', + } + ); + } else { + $error = 'cannot'; + } + } + if ( !defined $recall ) { + $error = 'failed'; + } else { + + # successful recall, go back to Recalls tab + print $input->redirect("/cgi-bin/koha/recalls/request.pl?biblionumber=$biblionumber"); + } + } + } } -if ( $op eq 'list' ) { - $recalls = Koha::Recalls->search( { biblio_id => $biblionumber, completed => 0 } ); - $biblio = Koha::Biblios->find($biblionumber); +if ($borrowernumber) { + my $patron = Koha::Patrons->find($borrowernumber); + + if ( C4::Context->preference('UseRecalls') + and C4::Context->preference('RecallsInterface') ne 'opac' + and $patron + and $patron->borrowernumber ) + { + if ( !$biblio->can_be_recalled( { patron => $patron } ) ) { + $error = 1; + } + + $template->param( + patron => $patron, + ); + } } +my $recalls = Koha::Recalls->search( { biblio_id => $biblionumber, completed => 0 } ); +my $branches = Koha::Libraries->search; +my $single_branch_mode = $branches->count == 1; +my $items = Koha::Items->search( { biblionumber => $biblionumber } ); + $template->param( - recalls => $recalls, - recallsview => 1, - biblio => $biblio, - checkboxes => 1, + recalls => $recalls, + recallsview => 1, + biblio => $biblio, + checkboxes => 1, + branches => $branches, + items => $items, + error => $error, + single_branch_mode => $single_branch_mode, C4::Search::enabled_staff_search_views, + attribute_type_codes => ( + C4::Context->preference('ExtendedPatronAttributes') + ? [ Koha::Patron::Attribute::Types->search( { staff_searchable => 1 } )->get_column('code') ] + : [] + ), ); output_html_with_http_headers $input, $cookie, $template->output; diff --git a/reserve/modrequest.pl b/reserve/modrequest.pl index 63f7a23e1bc..4d22125ba87 100755 --- a/reserve/modrequest.pl +++ b/reserve/modrequest.pl @@ -46,6 +46,7 @@ my @branch = $query->multi_param('pickup'); my @itemnumber = $query->multi_param('itemnumber'); my @biblionumber = $query->multi_param('biblionumber'); my $count = @rank; +my $recallerror; @biblionumber = uniq @biblionumber; @@ -68,6 +69,13 @@ if ( $op eq 'cud-cancelall' || $op eq 'cud-modifyall' ) { $params->{reservedate} = $reservedates[$i] || undef; } + if ( $rank[$i] eq 'recall' and !$recallerror ) { + my $hold = Koha::Holds->find( $reserve_id[$i] ); + if ( !$hold->biblio->can_be_recalled( { patron => $hold->patron } ) ) { + $recallerror = 1; + } + } + try { ModReserve($params); } catch { @@ -105,6 +113,10 @@ if ( $from eq 'borrower' ) { print $query->redirect("/cgi-bin/koha/circ/circulation.pl?borrowernumber=$borrower[0]"); } else { my $url = URI->new("/cgi-bin/koha/reserve/request.pl"); - $url->query_form( biblionumber => [@biblionumber] ); + if ($recallerror) { + $url->query_form( biblionumber => [@biblionumber], recallerror => 1 ); + } else { + $url->query_form( biblionumber => [@biblionumber] ); + } print $query->redirect($url); } diff --git a/reserve/request.pl b/reserve/request.pl index 304fc3f47e2..b0f8a565321 100755 --- a/reserve/request.pl +++ b/reserve/request.pl @@ -750,6 +750,11 @@ $template->param( borrowernumber => $borrowernumber_hold ); $template->param( failed_holds => \@failed_holds ); +my $recallerror = $input->param('recallerror'); +if ($recallerror) { + $template->param( recallerror => 1 ); +} + # printout the page output_html_with_http_headers $input, $cookie, $template->output; diff --git a/t/db_dependent/Holds.t b/t/db_dependent/Holds.t index 65b735676f6..5a2871a1cc9 100755 --- a/t/db_dependent/Holds.t +++ b/t/db_dependent/Holds.t @@ -2109,3 +2109,82 @@ subtest 'EmailPatronWhenHoldIsPlaced tests' => sub { $schema->storage->txn_rollback; }; + +subtest 'ModReserve to convert a hold to a recall' => sub { + plan tests => 2; + + $schema->storage->txn_begin; + + my $category = $builder->build( { source => 'Category' } ); + my $branch = $builder->build( { source => 'Branch' } )->{branchcode}; + my $biblio = $builder->build_sample_biblio( { itemtype => 'DUMMY' } ); + my $item = $builder->build_sample_item( { library => $branch, biblionumber => $biblio->biblionumber } ); + + my $patron = Koha::Patron->new( + { + firstname => 'my firstname', + surname => 'whatever surname', + categorycode => $category->{categorycode}, + branchcode => $branch, + } + )->store; + my $borrowernumber = $patron->borrowernumber; + + my $patron2 = Koha::Patron->new( + { + firstname => 'my firstname', + surname => 'whatever surname', + categorycode => $category->{categorycode}, + branchcode => $branch, + } + )->store; + + t::lib::Mocks::mock_preference( "UseRecalls", 1 ); + t::lib::Mocks::mock_userenv( { branchcode => $branch } ); + + C4::Circulation::AddIssue( $patron2->unblessed, $item->barcode ); + + Koha::CirculationRules->set_rules( + { + branchcode => undef, + categorycode => undef, + itemtype => undef, + rules => { + recalls_allowed => 5, + recalls_per_record => 1, + on_shelf_recalls => 'any', + }, + } + ); + + my $reserve_id = AddReserve( + { + branchcode => $branch, + borrowernumber => $borrowernumber, + biblionumber => $biblio->biblionumber, + priority => C4::Reserves::CalculatePriority( $biblio->biblionumber ), + itemnumber => $item->id, + } + ); + + my $hold = Koha::Holds->find($reserve_id); + my $before_recalls = Koha::Recalls->search->filter_by_current->count; + + ModReserve( + { + reserve_id => $hold->id, + expirationdate => '1981-06-10', + priority => 99, + rank => "recall", + itemnumber => $item->id, + } + ); + + $hold = Koha::Holds->find($reserve_id); + my $after_recalls = Koha::Recalls->search->filter_by_current->count; + + ok( !defined $hold, 'Hold was cancelled' ); + is( $after_recalls, $before_recalls + 1, 'Recall count increased by 1 as hold was converted to a recall' ); + + $schema->storage->txn_rollback; +}; -- 2.39.5