Bugzilla – Attachment 173842 Details for
Bug 31391
Staff-side recalls
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 31391: Request recalls and convert reserves on staff interface
Bug-31391-Request-recalls-and-convert-reserves-on-.patch (text/plain), 36.94 KB, created by
Aleisha Amohia
on 2024-10-31 23:30:42 UTC
(
hide
)
Description:
Bug 31391: Request recalls and convert reserves on staff interface
Filename:
MIME Type:
Creator:
Aleisha Amohia
Created:
2024-10-31 23:30:42 UTC
Size:
36.94 KB
patch
obsolete
>From 0886cf46daed52bc09fd9cbb77863c8a08501321 Mon Sep 17 00:00:00 2001 >From: Aleisha Amohia <aleishaamohia@hotmail.com> >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 <david@davidnind.com> >Signed-off-by: David Nind <david@davidnind.com> >--- > C4/Reserves.pm | 34 +++ > Koha/Biblio.pm | 4 +- > Koha/Item.pm | 6 +- > .../prog/en/includes/holds_table.inc | 6 + > .../prog/en/modules/recalls/request.tt | 256 ++++++++++++++++-- > .../prog/en/modules/reserve/request.tt | 8 + > .../intranet-tmpl/prog/js/staff-global.js | 2 + > opac/opac-recall.pl | 10 +- > recalls/request.pl | 98 ++++++- > reserve/modrequest.pl | 14 +- > reserve/request.pl | 5 + > t/db_dependent/Holds.t | 79 ++++++ > 12 files changed, 492 insertions(+), 30 deletions(-) > >diff --git a/C4/Reserves.pm b/C4/Reserves.pm >index 046bfd02357..3442d1d5bf9 100644 >--- a/C4/Reserves.pm >+++ b/C4/Reserves.pm >@@ -1054,6 +1054,40 @@ 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 d4d54eed42b..d37f07549bd 100644 >--- a/Koha/Biblio.pm >+++ b/Koha/Biblio.pm >@@ -2009,6 +2009,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 { >@@ -2034,7 +2036,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 572b5f1ff61..13f8836d16e 100644 >--- a/Koha/Item.pm >+++ b/Koha/Item.pm >@@ -2324,6 +2324,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 { >@@ -2375,7 +2377,9 @@ sub can_be_recalled { > return 0 if ( Koha::Checkouts->search({ itemnumber => $self->itemnumber, borrowernumber => $patron->borrowernumber })->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 dd99815cb2b..20e4ab81d41 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >@@ -72,6 +72,9 @@ > <option value="[% hold.priority | html %]" selected="selected">[% this_priority | html %]</option> > [% END %] > <option value="del">del</option> >+ [% IF Koha.Preference('UseRecalls') AND Koha.Preference('RecallsInterface') != 'opac' %] >+ <option value="recall">recall</option> >+ [% END %] > </select> > [% ELSE %] > <input type="hidden" name="rank-request" class="rank-request" value="[% hold.priority | html %]" data-hold-id="[% hold.reserve_id | html %]"> >@@ -87,6 +90,9 @@ > [% ELSE %] > <option value="[% hold.priority | html %]" selected="selected">[% this_priority | html %]</option> > [% END %] >+ [% IF Koha.Preference('UseRecalls') AND Koha.Preference('RecallsInterface') != 'opac' %] >+ <option value="recall">recall</option> >+ [% END %] > </select> > [% 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 823c9f87ee9..e05734ee49e 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/recalls/request.tt >@@ -2,11 +2,20 @@ > [% 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' %] > <title>[% FILTER collapse %] >- [% t("Existing recalls") | html %] › >+ [% t("Place a recall") | html %] › > [% t("Recalls") | html %] › > [% t("Koha") | html %] > [% END %]</title> >@@ -26,7 +35,7 @@ > [% INCLUDE 'biblio-title.inc' link = 1 %] > [% END %] > [% WRAPPER breadcrumb_item bc_active= 1 %] >- <span>Existing recalls</span> >+ <span>Place a recall</span> > [% END %] > [% END #/ WRAPPER breadcrumbs %] > [% END #/ WRAPPER sub-header.inc %] >@@ -37,27 +46,199 @@ > <main> > [% INCLUDE 'messages.inc' %] > >- <h1>Existing recalls</h1> >+ <h1>Recalls</h1> > >- [% IF Koha.Preference('UseRecalls') %] >- [% IF recalls.count %] >- <div class="page-section"> >- <form method="post" action="/cgi-bin/koha/recalls/request.pl"> >+ <h2>Place a recall on [% INCLUDE 'biblio-title.inc' link = 1 %]</h2> >+ >+ [% IF patron %] >+ >+ [% IF error %] >+ <div class="dialog alert"> >+ Unable to place a recall. Check your circulation rules. >+ </div> >+ [% END %] >+ >+ <fieldset class="rows"> >+ <legend>Recall details</legend> >+ <form id="recallform" action="/cgi-bin/koha/recalls/request.pl" method="post"> >+ <input type="hidden" name="biblionumber" value="[% biblio.biblionumber | html %]"> >+ >+ <ol> >+ <li> >+ <label for="borrowernumber">Patron:</label> <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% patron.borrowernumber | uri %]">[% INCLUDE 'patron-title.inc' %]</a> >+ <input type="hidden" name="borrowernumber" value="[% patron.borrowernumber | html %]"> >+ </li> >+ >+ [% UNLESS ( single_branch_mode ) %]<li> >+ <label for="pickup">Pick up at:</label> >+ <select name="pickup" id="pickup"> >+ [% FOREACH branch IN branches %] >+ [% IF branch.branchcode == patron.branchcode %] >+ <option value="[% branch.branchcode | html %]" selected>[% Branches.GetName( branch.branchcode ) | html %]</option> >+ [% ELSE %] >+ <option value="[% branch.branchcode | html %]">[% Branches.GetName( branch.branchcode ) | html %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ </li>[% END %] >+ >+ <li> >+ <label for="expirationdate">Recall not needed after:</label> <input type="text" name="expirationdate" id="expirationdate" size="20" class="flatpickr"> >+ </li> >+ <li> >+ <label for="type">Recall next available item</label> <input type="checkbox" name="type" id="type" checked> >+ </li> >+ </ol> >+ >+ <fieldset class="action"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-request"> >+ <input type="hidden" name="biblionumber" value="[% biblio.biblionumber | html %]"> >+ [% IF error %] >+ <input type="submit" class="btn btn-default" value="Place recall" disabled="disabled"> >+ [% ELSE %] >+ <input type="submit" class="btn btn-default" value="Place recall"> >+ [% END %] >+ <a href="/cgi-bin/koha/recalls/request.pl?biblionumber=[% biblio.biblionumber | uri %]" class="cancel">Cancel</a> >+ </fieldset> >+ >+ <table id="items"> >+ <caption>Place a recall on a specific item</caption> >+ <thead> >+ <tr> >+ <th> </th> >+ <th>Item type</th> >+ <th>Barcode</th> >+ [% UNLESS ( single_branch_mode ) %] >+ <th>Home library</th> >+ <th>Last location</th> >+ [% END %] >+ <th>Collection</th> >+ <th>Call number</th> >+ <th>Copy number</th> >+ <th>Vol no.</th> >+ <th>Information</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH item IN items %]<tr> >+ <td> >+ [% IF item.can_be_recalled( patron => patron ) %] >+ <input type="radio" class="itemnumber" name="itemnumber" value="[% item.itemnumber | html %]"> >+ [% ELSE %] >+ <span class="error"> >+ <i class="fa fa-times fa-lg" title="Cannot be recalled"></i> >+ </span> >+ [% END %] >+ </td> >+ <td>[% ItemTypes.GetDescription( item.effective_itemtype ) | html %]</td> >+ <td>[% item.barcode | html %]</td> >+ [% UNLESS ( single_branch_mode ) %] >+ <td>[% Branches.GetName( item.homebranch ) | html %]</td> >+ <td>[% Branches.GetName( item.holdingbranch) | html %]</td> >+ [% END %] >+ <td>[% AuthorisedValues.GetByCode( 'CCODE', item.ccode, 1 ) | html %]</td> >+ <td>[% item.itemcallnumber | html %]</td> >+ <td>[% item.copynumber | html %]</td> >+ <td>[% item.enumchron | html %]</td> >+ <td> >+ [% IF ( item.checkout ) %] >+ <span class="checkedout">Due [% item.checkout.date_due | $KohaDates %]</span> >+ [% ELSIF ( item.get_transfer ) %] >+ <span class="intransit">In transit from [% Branches.GetName( item.get_transfer.frombranch ) | html %] to [% Branches.GetName( item.get_transfer.tobranch ) | html %] since [% item.get_transfer.datesent | $KohaDates %]</span> >+ [% END %] >+ [% IF ( item.itemlost || item.withdrawn ) %] >+ <span class="lost">Unavailable (lost or missing)</span> >+ [% END %] >+ [% IF ( item.notforloan ) %] >+ <span class="notforloan">Not for loan ([% item.notforloan | html %])</span> >+ [% END %] >+ [% hold = item.current_holds.next %] >+ [% IF ( item.recall ) %] >+ <span class="waiting"> >+ [% 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 %] >+ </span> >+ [% ELSIF ( hold.waitingdate ) %] >+ <span class="waiting"> >+ Waiting for patron at [% Branches.GetName( hold.branchcode ) | html %] since [% hold.waitingdate | $KohaDates %]. >+ </span> >+ [% ELSIF ( hold.borrowernumber == logged_in_user.borrowernumber ) %] >+ <span class="waiting"> >+ Patron has already placed a <a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=[% patron.borrowernumber | uri %]#reserves">reserve</a> on this item. >+ </span> >+ [% END # / IF ( item.recall or hold ) %] >+ </td> >+ </tr>[% END %] >+ </tbody> >+ </table> >+ >+ <fieldset class="action"> > [% INCLUDE 'csrf-token.inc' %] >- <input type="hidden" name="op" value="cud-cancel_multiple_recalls"> >+ <input type="hidden" name="op" value="cud-request"> > <input type="hidden" name="biblionumber" value="[% biblio.biblionumber | html %]"> >- <input type="checkbox" id="select_all"> <span id="select_all_text">Select all</span> >- [% INCLUDE 'recalls.inc' %] >- <fieldset class="action"> >- <button type="submit" id="cancel_selected" class="btn btn-default btn-sm">Cancel selected recalls</button> >- </fieldset> >- </form> >- </div> <!-- /.page-section --> >+ [% IF error %] >+ <input type="submit" class="btn btn-default" value="Place recall" disabled="disabled"> >+ [% ELSE %] >+ <input type="submit" class="btn btn-default" value="Place recall"> >+ [% END %] >+ <a href="/cgi-bin/koha/recalls/request.pl?biblionumber=[% biblio.biblionumber | uri %]" class="cancel">Cancel</a> >+ </fieldset> >+ </form> >+ >+ </fieldset> >+ >+ [% ELSE %] >+ >+ [% IF Koha.Preference('RecallsInterface') == 'opac' %] >+ <div class="dialog alert"> >+ Due to the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=RecallsInterface">RecallsInterface system preference</a>, recalls cannot be placed through the staff client. >+ </div> > [% ELSE %] >- <div class="alert alert-info">No recalls have been made.</div> >+ >+ <fieldset> >+ <h2>Search patrons</h2> >+ <div id="circ_recalls_select" class="toptabs"> >+ <ul class="nav nav-tabs" role="tablist"> >+ <li role="presentation" class="active"><a href="#recalls_patronsearch_pane" aria-controls="recalls_patronsearch_pane" role="tab" data-toggle="tab">Patrons</a></li> >+ </ul> >+ <div class="tab-content"> >+ <div id="recalls_patronsearch_pane" role="tabpanel" class="tab-pane active"> >+ [% PROCESS patron_search_filters_simple %] >+ [% PROCESS patron_search_table table_id => 'table_borrowers', open_on_row_click => 1 %] >+ </div> >+ </div> <!-- /.tab-content --> >+ </div> >+ </fieldset> >+ > [% END %] >- [% ELSE %] >- <div class="alert alert-info">Recalls have not been enabled. Enable the <a href="/cgi-bin/koha/admin/preferences.pl?tab=circulation">UseRecalls</a> system preference to use recalls.</div> >+ >+ <h2>Existing recalls</h2> >+ >+ [% IF Koha.Preference('UseRecalls') %] >+ [% IF recalls.count %] >+ <div class="page-section"> >+ <form method="post" action="/cgi-bin/koha/recalls/request.pl"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <input type="hidden" name="op" value="cud-cancel_multiple_recalls"> >+ <input type="hidden" name="biblionumber" value="[% biblio.biblionumber | html %]"> >+ <input type="checkbox" id="select_all"> <span id="select_all_text">Select all</span> >+ [% INCLUDE 'recalls.inc' %] >+ <fieldset class="action"> >+ <button type="submit" id="cancel_selected" class="btn btn-default btn-sm">Cancel selected recalls</button> >+ </fieldset> >+ </form> >+ </div> <!-- /.page-section --> >+ [% ELSE %] >+ <div class="alert alert-info">No recalls have been made.</div> >+ [% END %] >+ [% ELSE %] >+ <div class="alert alert-info">Recalls have not been enabled. Enable the <a href="/cgi-bin/koha/admin/preferences.pl?tab=circulation">UseRecalls</a> system preference to use recalls.</div> >+ [% END %] >+ > [% END %] > > </main> >@@ -76,6 +257,45 @@ > [% Asset.js("js/recalls.js") | $raw %] > [% INCLUDE 'datatables.inc' %] > [% INCLUDE 'columns_settings.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' %] >+ <script> >+ table_settings = [% TablesSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %]; >+ $(document).ready(function(){ >+ $("#recalls_patronsearch").on("submit", filter); >+ [% UNLESS patron %] >+ [% IF ( PatronAutoComplete ) %] >+ patron_autocomplete($("#search_patron_filter"), { 'link-to': 'recall', 'url-params': '[% url_biblio_params | url %]' }); >+ [% END %] >+ [% END %] >+ $("#items").dataTable($.extend(true, {}, dataTablesDefaults, { >+ 'bPaginate': false, >+ "sDom": '<"top pager"ilf>t', >+ })); >+ >+ //Override fieldset styling for dataTables search box >+ $("div.top.pager").css("margin-left","1em"); >+ $(".dataTables_filter label").css({ >+ "width":"auto", >+ "margin-right":"0em" >+ }); >+ $("#type").click(function() { >+ if(this.checked){ >+ $("input[name=itemnumber]").each(function() { >+ $(this).prop("checked", false); >+ }); >+ } >+ }); >+ $("input[name=itemnumber]").click(function() { >+ $("input[name=itemnumber]").each(function() { >+ if(this.checked){ >+ $("#type").prop("checked", false); >+ } >+ }); >+ }); >+ }); >+ </script> > [% 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 8d1c1fddc3f..9bade1d1be2 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >@@ -255,6 +255,14 @@ > <p>No club with this name, please, try another</p> > </div> > [% END %] >+ >+ [% IF ( recallerror ) %] >+ <div class="dialog alert"> >+ <h3>Hold cannot be converted to a recall</h3> >+ <p>A recall cannot be placed on one or more holds. Check your <a href="/cgi-bin/koha/admin/smart-rules.pl">circulation and fine rules</a>.</p> >+ </div> >+ [% END %] >+ > <fieldset> > [% 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 b05936f48f9..4dab1e5379b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/staff-global.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/staff-global.js >@@ -676,6 +676,8 @@ function patron_autocomplete(node, options) { > ? "/cgi-bin/koha/circ/circulation.pl" > : link_to == "reserve" > ? "/cgi-bin/koha/reserve/request.pl" >+ : link_to == 'recall' >+ ? "/cgi-bin/koha/recalls/request.pl" > : "/cgi-bin/koha/members/moremember.pl"; > item.link += > (url_params ? "?" + url_params + "&" : "?") + >diff --git a/opac/opac-recall.pl b/opac/opac-recall.pl >index 4f0b675e5da..66cf3dad00a 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 ); >@@ -112,10 +118,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 b3cb7a3d97a..ec90eb8d9f1 100755 >--- a/recalls/request.pl >+++ b/recalls/request.pl >@@ -38,27 +38,115 @@ 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 $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 ( $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; >+ } > >-if ( $op eq 'list' ) { >- $recalls = Koha::Recalls->search({ biblio_id => $biblionumber, completed => 0 }); >- $biblio = Koha::Biblios->find( $biblionumber ); >+ $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, >+ 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 fbf960a5541..33d354646cc 100755 >--- a/reserve/modrequest.pl >+++ b/reserve/modrequest.pl >@@ -47,6 +47,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; > >@@ -69,6 +70,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 { >@@ -109,6 +117,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 b19fa3b868b..b7f0a2a27fb 100755 >--- a/reserve/request.pl >+++ b/reserve/request.pl >@@ -730,6 +730,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 b899c10ad18..8ba75a65397 100755 >--- a/t/db_dependent/Holds.t >+++ b/t/db_dependent/Holds.t >@@ -1879,3 +1879,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
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 31391
:
139442
|
139443
|
139444
|
139446
|
139642
|
139643
|
139644
|
139645
|
141474
|
142594
|
143155
|
144104
|
144105
|
144131
|
144132
|
144133
|
144134
|
144135
|
144136
|
147581
|
147582
|
150485
|
150486
|
150487
|
150488
|
150489
|
150490
|
150491
|
156995
|
156996
|
156997
|
156998
|
156999
|
157000
|
157001
|
158691
|
158692
|
158693
|
158694
|
158695
|
158825
|
158826
|
158827
|
158828
|
158829
|
158830
|
158831
|
158832
|
172166
|
172167
|
172168
|
172169
|
172170
|
172171
|
172172
|
172173
|
172174
|
172175
|
173839
|
173840
|
173841
|
173842
|
173843
|
173844
|
173845
|
173846
|
173847
|
174396
|
174822
|
174823
|
174824
|
174825
|
174826
|
174827
|
174828
|
174829
|
174830
|
174831