Bug 31391: Request recalls and convert reserves on staff interface
This enhancement adds the ability to place recalls via the staff interface by changing the UseRecalls system preference to have choices which describe where recalls can be placed. Recalls can either be placed via the OPAC, or the staff interface, or both. To be set to OPAC would be consistent with current 'on' behaviour. UseRecalls is turned off by default. This enhancement also adds the ability to convert existing holds to recalls easily via the staff interface. The hold will be cancelled and a recall placed using the hold's information. To test: 1) Update database and restart services 2) Set UseRecalls to OPAC and set the relevant recalls circulation rules OPAC only by default 3) Check out Item A to Patron B. 4) Log into the OPAC as Patron A. Confirm you can place, view and cancel recalls as normal. 5) In the staff interface, set UseRecalls to Staff interface only. 6) In the OPAC, confirm you can still view and cancel recalls, but can no longer place recalls. 7) In the staff interface, search for Item A and go to the Holds tab. Place a hold for Patron C. 8) Check the checkbox for your hold and confirm the counts in the buttons above the Holds table are incremented as expected. 9) Click the button to Convert selected holds to recalls, a modal should pop up. Confirm all of the information is correct. 10) Confirm the conversion and once the page refreshes, your hold should be correctly cancelled using the new RECALLED reason. 11) Go to the Recalls tab. Confirm your hold 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 choosing to recall the next available item. 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' section should become disabled. 15) Set UseRecalls 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 holds to recalls. 17) Set UseRecalls 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. Another example, testing converting a hold to a recall when there are NO checked out items, as recalls can only be placed if something is checked out. 19) Place a hold for one of the patron's that already has a recall. Once complete, try to convert this hold into a recall. Confirm you are blocked and shown a message that the hold cannot be converted to a recall. 20) Confirm patron messaging preferences for recalls notices can still be configured as expected. 21) Ensure tests pass: * t/db_dependent/Circulation.t * t/db_dependent/Circulation/transferbook.t * t/db_dependent/Holds.t * t/db_dependent/Koha/Biblio.t * t/db_dependent/Koha/Item.t * t/db_dependent/SIP/Transaction.t * t/db_dependent/Search.t * t/db_dependent/XSLT.t Sponsored-by: Auckland University of Technology Signed-off-by: David Nind <david@davidnind.com> Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de> Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
parent
8211dd8d0f
commit
98c8add185
|
|
@ -1183,6 +1183,8 @@ for a bib.
|
|||
|
||||
If C<$rank> is 'del', the hold request is cancelled.
|
||||
|
||||
If C<$rank> is 'recall', the hold request is converted to a recall request.
|
||||
|
||||
If C<$rank> is an integer greater than zero, the priority of
|
||||
the request is set to that value. Since priority != 0 means
|
||||
that the item is not waiting on the hold shelf, setting the
|
||||
|
|
@ -1240,6 +1242,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 => $cancellation_reason } );
|
||||
}
|
||||
} 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 => $cancellation_reason } );
|
||||
}
|
||||
}
|
||||
} elsif ( $hold->found && $hold->priority eq '0' && $date ) {
|
||||
|
||||
# The only column that can be updated for a found hold is the expiration date
|
||||
|
|
|
|||
|
|
@ -2239,6 +2239,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 a check to convert a hold to a recall, so we should not check for an existing hold.
|
||||
|
||||
=cut
|
||||
|
||||
sub can_be_recalled {
|
||||
|
|
@ -2264,7 +2266,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 );
|
||||
|
|
|
|||
11
Koha/Item.pm
11
Koha/Item.pm
|
|
@ -2643,6 +2643,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 a check to convert a hold to a recall, so we should not check for an existing hold.
|
||||
|
||||
=cut
|
||||
|
||||
sub can_be_recalled {
|
||||
|
|
@ -2704,9 +2706,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
|
||||
|
|
|
|||
|
|
@ -95,3 +95,48 @@
|
|||
</div>
|
||||
</div>
|
||||
[% END %]
|
||||
|
||||
[% BLOCK 'convert-to-recall-modal' %]
|
||||
<div id="convertModal" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title">Confirm conversion</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form id="convert_to_recall_modal_form" method="post" action="[% form_action | html %]">
|
||||
[% INCLUDE 'csrf-token.inc' %]
|
||||
[% IF (from_param) %]
|
||||
<input type="hidden" name="from" value="[% patronpage | html %]" />
|
||||
[% END %]
|
||||
<div id="inputs">
|
||||
<input type="hidden" name="op" value="cud-convertall" />
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to convert this hold to a recall?</p>
|
||||
<div id="convert_hold_alert" class="alert alert-danger" style="display:none;"></div>
|
||||
<fieldset class="action">
|
||||
[% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
|
||||
[% IF hold_cancellation.count %]
|
||||
<label for="modal-cancellation-reason">Hold cancellation reason: </label>
|
||||
<select class="cancellation-reason" name="cancellation-reason" id="modal-cancellation-reason">
|
||||
<option value="">No reason given</option>
|
||||
[% FOREACH reason IN hold_cancellation %]
|
||||
<option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
|
||||
[% END %]
|
||||
</select>
|
||||
[% END %]
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button id="convertModalConfirmBtn" type="submit" class="btn btn-danger">Confirm conversion</button>
|
||||
<button type="button" class="btn btn-default" data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
[% END %]
|
||||
|
|
|
|||
|
|
@ -2,18 +2,36 @@
|
|||
[% USE Asset %]
|
||||
[% USE Koha %]
|
||||
[% USE KohaDates %]
|
||||
[% USE Branches %]
|
||||
[% USE Categories %]
|
||||
[% USE AuthorisedValues %]
|
||||
[% USE ItemTypes %]
|
||||
[% PROCESS 'i18n.inc' %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
[% SET libraries = Branches.all %]
|
||||
[% SET categories = Categories.all.unblessed %]
|
||||
[% SET columns = ['name', 'cardnumber', 'dateofbirth', 'category', 'branch', 'address', 'phone'] %]
|
||||
[% PROCESS "patron-search.inc" %]
|
||||
<title
|
||||
>[% FILTER collapse %]
|
||||
[% t("Existing recalls") | html %]
|
||||
[% t("Place a recall") | html %]
|
||||
› [% t("Recalls") | html %] › [% t("Koha") | html %]
|
||||
[% END %]</title
|
||||
>
|
||||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
[% FILTER collapse %]
|
||||
<style nonce="[% Koha.CSPNonce | $raw %]">
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
#circ_request fieldset.disabled {
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
[% END %]
|
||||
</head>
|
||||
<body id="recalls-request" class="catalog">
|
||||
<body id="circ_request" class="recall">
|
||||
[% WRAPPER 'header.inc' %]
|
||||
[% INCLUDE 'circ-search.inc' %]
|
||||
[% END %]
|
||||
|
|
@ -27,40 +45,264 @@
|
|||
[% 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 %]
|
||||
|
||||
[% WRAPPER 'main-container.inc' aside='biblio-view-menu' %]
|
||||
<h1>Existing recalls</h1>
|
||||
[% WRAPPER 'main-container.inc' aside='biblio-view-menu' wide_centered=1 %]
|
||||
<h1>Recalls</h1>
|
||||
|
||||
[% IF Koha.Preference('UseRecalls') != "off" %]
|
||||
[% 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>
|
||||
<h2>Place a recall on [% INCLUDE 'biblio-title.inc' link = 1 %]</h2>
|
||||
|
||||
[% IF patron %]
|
||||
|
||||
[% IF error %]
|
||||
<div class="alert alert-warning"> <strong>Cannot place recalls:</strong> check your circulation rules. </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 %]
|
||||
|
||||
[% IF patron_holds_count %]
|
||||
<div class="alert alert-info"> Patron has placed one or more record-level holds. <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% biblio.biblionumber | uri %]">Convert a hold to a recall</a>. </div>
|
||||
[% END %]
|
||||
|
||||
<form id="recallform" action="/cgi-bin/koha/recalls/request.pl" method="post">
|
||||
<fieldset class="rows">
|
||||
<legend>Recall details</legend>
|
||||
|
||||
<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>
|
||||
|
||||
<li> <label for="expirationdate">Recall not needed after:</label> <input type="text" name="expirationdate" id="expirationdate" size="20" class="flatpickr" /> </li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="rows enabled recall_next">
|
||||
<legend>Recall next available item</legend>
|
||||
|
||||
<ol>
|
||||
[% 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 %]
|
||||
</ol>
|
||||
|
||||
<fieldset class="action enabled recall_next">
|
||||
[% INCLUDE 'csrf-token.inc' %]
|
||||
<input type="hidden" name="op" value="cud-request" />
|
||||
<input type="hidden" name="biblionumber" value="[% biblio.biblionumber | html %]" />
|
||||
<input type="hidden" name="type" value="next" />
|
||||
[% IF error %]
|
||||
<input type="submit" class="btn btn-primary" value="Place recall" disabled="disabled" />
|
||||
[% ELSE %]
|
||||
<input type="submit" class="btn btn-primary" value="Place recall" />
|
||||
[% END %]
|
||||
<a href="/cgi-bin/koha/recalls/request.pl?biblionumber=[% biblio.biblionumber | uri %]" class="cancel">Cancel</a>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="rows disabled recall_specific">
|
||||
<legend>Recall a specific item</legend>
|
||||
|
||||
<table id="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Item type</th>
|
||||
<th>Barcode</th>
|
||||
[% UNLESS ( single_branch_mode ) %]
|
||||
<th>Home library</th>
|
||||
<th>Holding library</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 withdrawn)</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">hold</a> on this item. </span>
|
||||
[% END # / IF ( item.recall or hold ) %]
|
||||
</td>
|
||||
</tr>
|
||||
[% END # FOREACH item IN items %]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<fieldset class="action disabled recall_specific">
|
||||
[% 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-primary" value="Place recall" disabled="disabled" />
|
||||
[% ELSE %]
|
||||
<input type="submit" class="btn btn-primary" value="Place recall" />
|
||||
[% END %]
|
||||
<a href="/cgi-bin/koha/recalls/request.pl?biblionumber=[% biblio.biblionumber | uri %]" class="cancel">Cancel</a>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</form>
|
||||
[% ELSE # patron %]
|
||||
|
||||
[% IF Koha.Preference('UseRecalls') == 'opac' %]
|
||||
<div class="alert alert-warning"> Due to the <a href="/cgi-bin/koha/admin/preferences.pl?op=search&searchfield=UseRecalls">UseRecalls</a> system preference, recalls cannot be placed through the staff interface. </div>
|
||||
[% ELSE %]
|
||||
<fieldset>
|
||||
<h2>Search patrons</h2>
|
||||
[% WRAPPER tabs id= "circ_recalls_select" %]
|
||||
[% WRAPPER tabs_nav %]
|
||||
[% WRAPPER tab_item tabname= "recalls_patronsearch_pane" bt_active= 1 %]<span>Patrons</span>[% END %]
|
||||
[% END # /WRAPPER tabs_nav %]
|
||||
|
||||
[% WRAPPER tab_panels %]
|
||||
[% WRAPPER tab_panel tabname="recalls_patronsearch_pane" bt_active= 1 %]
|
||||
[% PROCESS patron_search_filters_simple %]
|
||||
[% PROCESS patron_search_table table_id => 'table_borrowers', open_on_row_click => 1 %]
|
||||
[% END # /tab_panel# %]
|
||||
[% END # /WRAPPER tab_panels %]
|
||||
[% END # /WRAPPER tabs %]
|
||||
</fieldset>
|
||||
[% END %]
|
||||
|
||||
<div class="page-section">
|
||||
<h2>Existing recalls</h2>
|
||||
|
||||
[% IF Koha.Preference('UseRecalls') != "off" %]
|
||||
[% 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 %]
|
||||
</div>
|
||||
[% END # patron %]
|
||||
[% END # WRAPPER %]
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% Asset.js("js/recalls.js") | $raw %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% INCLUDE 'calendar.inc' %]
|
||||
[% INCLUDE 'select2.inc' %]
|
||||
[% SET url_biblio_params = "biblionumber=" _ biblio.biblionumber %]
|
||||
[% UNLESS patron %]
|
||||
[% SET search_results_block_id = 'recalls_patronsearch_pane_panel' %]
|
||||
[%# adjusting variable for patron-search.inc %]
|
||||
[% PROCESS patron_search_js table_id => 'table_borrowers', categories => categories, libraries => libraries, 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 nonce="[% Koha.CSPNonce | $raw %]">
|
||||
$(document).ready(function () {
|
||||
$("#recalls_patronsearch").on("submit", filter);
|
||||
});
|
||||
</script>
|
||||
[% END %]
|
||||
<script nonce="[% Koha.CSPNonce | $raw %]" data-tt-allowed="true">
|
||||
const apply_patron_autocomplete = [% !patron && PatronAutoComplete ? 1 : 0 | html %];
|
||||
const url_biblio_params = '[% url_biblio_params | url %]';
|
||||
</script>
|
||||
<script nonce="[% Koha.CSPNonce | $raw %]">
|
||||
$.fn.select2.defaults.set("width", "100%");
|
||||
$.fn.select2.defaults.set("dropdownAutoWidth", true);
|
||||
$(document).ready(function () {
|
||||
if (apply_patron_autocomplete) {
|
||||
patron_autocomplete($(".search_patron_filter"), { "link-to": "recall", "url-params": url_biblio_params });
|
||||
}
|
||||
$("#items").dataTable(
|
||||
$.extend(true, {}, dataTablesDefaults, {
|
||||
paginate: false,
|
||||
dom: '<"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",
|
||||
});
|
||||
$(".recall_specific").click(function () {
|
||||
$(".recall_specific").removeClass("disabled").addClass("enabled");
|
||||
$(".recall_next").removeClass("enabled").addClass("disabled");
|
||||
});
|
||||
$(".recall_next").click(function () {
|
||||
$(".recall_next").removeClass("disabled").addClass("enabled");
|
||||
$(".recall_specific").removeClass("enabled").addClass("disabled");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'intranet-bottom.inc' %]
|
||||
|
|
|
|||
|
|
@ -327,6 +327,14 @@
|
|||
<p>No club with this name, please, try another</p>
|
||||
</div>
|
||||
[% END %]
|
||||
|
||||
[% IF ( recallerror ) %]
|
||||
<div class="alert alert-warning">
|
||||
<h3>Hold cannot be converted to a recall</h3>
|
||||
<p>A recall cannot be placed for 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 %]
|
||||
|
|
@ -1383,6 +1391,11 @@
|
|||
</ul>
|
||||
</div>
|
||||
[% END %]
|
||||
[% IF Koha.Preference('UseRecalls').match('staff') %]
|
||||
<div class="btn-group">
|
||||
<button class="btn convert_selected_holds" data-bulk="true"></button>
|
||||
</div>
|
||||
[% END %]
|
||||
</div>
|
||||
<div class="page-section">
|
||||
[% FOREACH biblioloo IN biblioloop %]
|
||||
|
|
@ -1465,6 +1478,8 @@
|
|||
|
||||
[% PROCESS 'cancel-hold-modal' form_action = 'request.pl' %]
|
||||
|
||||
[% PROCESS 'convert-to-recall-modal' form_action = 'modrequest.pl' %]
|
||||
|
||||
<div id="moveHoldItemModal" class="modal modal-lg" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
|
@ -1599,6 +1614,7 @@
|
|||
},
|
||||
[% END %][% END %][% END %]
|
||||
};
|
||||
var UseRecalls = [% ( Koha.Preference('UseRecalls').match('staff') ) ? 1 : 0 | html %];
|
||||
|
||||
const active_tab = [% clubs ? 1 : 0 | html %];
|
||||
|
||||
|
|
|
|||
|
|
@ -756,9 +756,13 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
var MSG_MOVE_SELECTED = __("Move selected (%s)");
|
||||
var MSG_RECALL_SELECTED = __("Convert selected to recall (%s)");
|
||||
var MSG_CANCEL_ALERT = __(
|
||||
"This action will cancel <span class='badge bg-danger'>%s</span> hold(s)."
|
||||
);
|
||||
var MSG_CONVERT_ALERT = __(
|
||||
"This action will cancel <span class='badge bg-danger'>%s</span> hold(s) and create <span class='badge bg-danger'>%s</span> recall(s)."
|
||||
);
|
||||
|
||||
// Confirm cancellation of hold
|
||||
let cancel_link;
|
||||
|
|
@ -820,10 +824,9 @@ $(document).ready(function () {
|
|||
var hasSelectedHolds = selectedHolds.length > 0;
|
||||
var hasMultipleSelectedHolds = selectedHolds.length >= 2;
|
||||
|
||||
$(".cancel_selected_holds, .suspend_selected_holds").prop(
|
||||
"disabled",
|
||||
!hasSelectedHolds
|
||||
);
|
||||
$(
|
||||
".cancel_selected_holds, .suspend_selected_holds, .convert_selected_holds"
|
||||
).prop("disabled", !hasSelectedHolds);
|
||||
$(".group_selected_holds").prop("disabled", !hasMultipleSelectedHolds);
|
||||
}
|
||||
|
||||
|
|
@ -876,6 +879,26 @@ $(document).ready(function () {
|
|||
)
|
||||
.join(",") +
|
||||
"]";
|
||||
$("#convert_hold_alert").html(
|
||||
MSG_CONVERT_ALERT.format(
|
||||
$(".holds_table .select_hold:checked").length,
|
||||
$(".holds_table .select_hold:checked").length
|
||||
)
|
||||
);
|
||||
$("#convert_hold_alert").show();
|
||||
localStorage.selectedHolds =
|
||||
"[" +
|
||||
$(".holds_table .select_hold:checked")
|
||||
.toArray()
|
||||
.map(el =>
|
||||
JSON.stringify({
|
||||
hold: $(el).data("reserve_id"),
|
||||
borrowernumber: $(el).data("borrowernumber"),
|
||||
biblionumber: $(el).data("biblionumber"),
|
||||
})
|
||||
)
|
||||
.join(",") +
|
||||
"]";
|
||||
});
|
||||
|
||||
$(".holds_table").on("click", ".select_hold", function () {
|
||||
|
|
@ -935,6 +958,38 @@ $(document).ready(function () {
|
|||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$(".convert_selected_holds").click(function (e) {
|
||||
e.preventDefault();
|
||||
if ($(".holds_table .select_hold:checked").length) {
|
||||
$("#convert_to_recall_modal_form #inputs").empty();
|
||||
$("#convert_to_recall_modal_form #inputs").append(
|
||||
'<input type="hidden" name="op" value="cud-convertall">'
|
||||
);
|
||||
$(
|
||||
"#convert_to_recall_modal_form #modal-cancellation-reason"
|
||||
).val("RECALLED");
|
||||
let hold_data =
|
||||
"[" +
|
||||
$(".holds_table .select_hold:checked")
|
||||
.toArray()
|
||||
.map(el =>
|
||||
JSON.stringify({
|
||||
hold: $(el).data("reserve_id"),
|
||||
borrowernumber: $(el).data("borrowernumber"),
|
||||
biblionumber: $(el).data("biblionumber"),
|
||||
})
|
||||
)
|
||||
.join(",") +
|
||||
"]";
|
||||
JSON.parse(hold_data).forEach(function (hold) {
|
||||
_append_patron_page_convert_hold_modal_data(hold);
|
||||
});
|
||||
delete localStorage.selectedHolds;
|
||||
$("#convertModal").modal("show");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
$("#itemSearchForm").on("submit", function (event) {
|
||||
event.preventDefault();
|
||||
|
|
@ -1906,9 +1961,13 @@ async function load_patron_holds_table(biblio_id, split_data) {
|
|||
function updateMSGCounters() {
|
||||
var MSG_CANCEL_SELECTED = __("Cancel selected (%s)");
|
||||
var MSG_MOVE_SELECTED = __("Move selected (%s)");
|
||||
var MSG_RECALL_SELECTED = __("Convert selected to recall (%s)");
|
||||
var MSG_CANCEL_ALERT = __(
|
||||
"This action will cancel <span class='badge bg-danger'>%s</span> hold(s)."
|
||||
);
|
||||
var MSG_CONVERT_ALERT = __(
|
||||
"This action will cancel <span class='badge bg-danger'>%s</span> hold(s) and create <span class='badge bg-danger'>%s</span> recall(s)."
|
||||
);
|
||||
var selectedCount = JSON.parse(
|
||||
localStorage.selectedHolds || "[]"
|
||||
).length;
|
||||
|
|
@ -1921,10 +1980,18 @@ async function load_patron_holds_table(biblio_id, split_data) {
|
|||
$("#cancel_hold_alert").html(
|
||||
MSG_CANCEL_ALERT.format(selectedCount)
|
||||
);
|
||||
$("#convert_hold_alert").html(
|
||||
MSG_CONVERT_ALERT.format(selectedCount, selectedCount)
|
||||
);
|
||||
$(".convert_selected_holds").html(
|
||||
MSG_RECALL_SELECTED.format(selectedCount)
|
||||
);
|
||||
if (selectedCount > 0) {
|
||||
$("#cancel_hold_alert").show();
|
||||
$("#convert_hold_alert").show();
|
||||
} else {
|
||||
$("#cancel_hold_alert").hide();
|
||||
$("#convert_hold_alert").hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2076,6 +2143,29 @@ async function load_patron_holds_table(biblio_id, split_data) {
|
|||
}
|
||||
return false;
|
||||
});
|
||||
$(".convert_selected_holds").click(function (e) {
|
||||
e.preventDefault();
|
||||
const selectedHolds = JSON.parse(
|
||||
localStorage.selectedHolds || "[]"
|
||||
);
|
||||
if (selectedHolds.length) {
|
||||
$("#convert_to_recall_modal_form #inputs").empty();
|
||||
$("#convert_to_recall_modal_form #inputs").append(
|
||||
'<input type="hidden" name="op" value="cud-convertall">'
|
||||
);
|
||||
$(
|
||||
"#convert_to_recall_modal_form #modal-cancellation-reason"
|
||||
).val("RECALLED");
|
||||
|
||||
selectedHolds.forEach(function (hold) {
|
||||
_append_patron_page_convert_hold_modal_data(hold);
|
||||
});
|
||||
|
||||
//delete localStorage.selectedHolds;
|
||||
$("#convertModal").modal("show");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
function _append_patron_page_cancel_hold_modal_data(hold) {
|
||||
$("#cancel_modal_form #inputs").append(
|
||||
'<input type="hidden" name="rank-request" value="del">'
|
||||
|
|
@ -2096,6 +2186,26 @@ async function load_patron_holds_table(biblio_id, split_data) {
|
|||
'">'
|
||||
);
|
||||
}
|
||||
function _append_patron_page_convert_hold_modal_data(hold) {
|
||||
$("#convert_to_recall_modal_form #inputs").append(
|
||||
'<input type="hidden" name="rank-request" value="recall">'
|
||||
);
|
||||
$("#convert_to_recall_modal_form #inputs").append(
|
||||
'<input type="hidden" name="biblionumber" value="' +
|
||||
hold.biblionumber +
|
||||
'">'
|
||||
);
|
||||
$("#convert_to_recall_modal_form #inputs").append(
|
||||
'<input type="hidden" name="borrowernumber" value="' +
|
||||
hold.borrowernumber +
|
||||
'">'
|
||||
);
|
||||
$("#convert_to_recall_modal_form #inputs").append(
|
||||
'<input type="hidden" name="reserve_id" value="' +
|
||||
hold.hold +
|
||||
'">'
|
||||
);
|
||||
}
|
||||
// Remove any previously attached handlers
|
||||
$("#cancelModalConfirmBtn").off("click");
|
||||
// Attach the handler to the button
|
||||
|
|
|
|||
|
|
@ -845,7 +845,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=" +
|
||||
|
|
|
|||
|
|
@ -35,30 +35,139 @@ 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';
|
||||
print $input->redirect( '/cgi-bin/koha/recalls/request.pl?biblionumber=' . $biblionumber );
|
||||
|
||||
} elsif ( $op eq 'cud-request' ) {
|
||||
|
||||
if ( C4::Context->preference('UseRecalls') =~ m/staff/
|
||||
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') =~ m/staff/
|
||||
and $patron
|
||||
and $patron->borrowernumber )
|
||||
{
|
||||
if ( !$biblio->can_be_recalled( { patron => $patron } ) ) {
|
||||
$error = 1;
|
||||
}
|
||||
|
||||
my $patron_holds_count = Koha::Holds->search(
|
||||
{ borrowernumber => $borrowernumber, biblionumber => $biblio->biblionumber, item_level_hold => 0 } )->count;
|
||||
|
||||
$template->param(
|
||||
patron => $patron,
|
||||
patron_holds_count => $patron_holds_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
C4::Search::enabled_staff_search_views,
|
||||
branches => $branches,
|
||||
items => $items,
|
||||
error => $error,
|
||||
single_branch_mode => $single_branch_mode,
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -99,6 +100,41 @@ if ( $op eq 'cud-cancelall' || $op eq 'cud-modifyall' ) {
|
|||
foreach my $biblio_id (@biblio_ids) {
|
||||
FixPriority( { biblionumber => $biblio_id } );
|
||||
}
|
||||
|
||||
} elsif ( $op eq 'cud-convertall' ) {
|
||||
for ( my $i = 0 ; $i < $count ; $i++ ) {
|
||||
undef $itemnumber[$i] if !$itemnumber[$i];
|
||||
my $cancellation_reason = $query->param("cancellation-reason");
|
||||
my $params = {
|
||||
rank => $rank[$i],
|
||||
reserve_id => $reserve_id[$i],
|
||||
expirationdate => $expirationdates[$i] || undef,
|
||||
branchcode => $branch[$i],
|
||||
itemnumber => $itemnumber[$i],
|
||||
cancellation_reason => $cancellation_reason,
|
||||
};
|
||||
|
||||
try {
|
||||
ModReserve($params);
|
||||
} catch {
|
||||
if ( $_->isa('Koha::Exceptions::ObjectNotFound') ) {
|
||||
warn $_;
|
||||
} else {
|
||||
$_->rethrow;
|
||||
}
|
||||
};
|
||||
|
||||
my $hold_still_exists = Koha::Holds->find( $reserve_id[$i] );
|
||||
if ($hold_still_exists) {
|
||||
$recallerror = 1;
|
||||
}
|
||||
}
|
||||
my @biblio_ids = uniq @biblionumber;
|
||||
Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue( { biblio_ids => \@biblio_ids } )
|
||||
if C4::Context->preference('RealTimeHoldsQueue');
|
||||
foreach my $biblio_id (@biblio_ids) {
|
||||
FixPriority( { biblionumber => $biblio_id } );
|
||||
}
|
||||
}
|
||||
|
||||
my $from = $query->param('from');
|
||||
|
|
@ -109,6 +145,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -806,6 +806,11 @@ $template->param( pickup => $pickup || C4::Context->userenv->{branch} );
|
|||
$template->param( borrowernumber => $borrowernumber_hold );
|
||||
$template->param( failed_holds => \@failed_holds, successful_biblionumbers => $successful_biblionumbers );
|
||||
|
||||
my $recallerror = $input->param('recallerror');
|
||||
if ($recallerror) {
|
||||
$template->param( recallerror => 1 );
|
||||
}
|
||||
|
||||
# printout the page
|
||||
output_html_with_http_headers $input, $cookie, $template->output;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use t::lib::TestBuilder;
|
|||
|
||||
use C4::Context;
|
||||
|
||||
use Test::More tests => 74;
|
||||
use Test::More tests => 75;
|
||||
use Test::NoWarnings;
|
||||
use Test::Exception;
|
||||
|
||||
|
|
@ -2151,3 +2151,98 @@ subtest 'current_holds with future holds' => 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 $itype = $builder->build( { source => 'Itemtype' } );
|
||||
my $item = $builder->build_sample_item(
|
||||
{ library => $branch, biblionumber => $biblio->biblionumber, itype => $itype->{itemtype} } );
|
||||
|
||||
my $patron = Koha::Patron->new(
|
||||
{
|
||||
firstname => 'my firstname',
|
||||
surname => 'whatever surname',
|
||||
categorycode => $category->{categorycode},
|
||||
branchcode => $branch,
|
||||
email => 'name@email.com',
|
||||
}
|
||||
)->store;
|
||||
my $borrowernumber = $patron->borrowernumber;
|
||||
|
||||
my $patron2 = Koha::Patron->new(
|
||||
{
|
||||
firstname => 'my firstname',
|
||||
surname => 'whatever surname',
|
||||
categorycode => $category->{categorycode},
|
||||
branchcode => $branch,
|
||||
email => 'name2@email.com',
|
||||
}
|
||||
)->store;
|
||||
|
||||
t::lib::Mocks::mock_preference( "UseRecalls", 1 );
|
||||
t::lib::Mocks::mock_userenv( { branchcode => $branch } );
|
||||
|
||||
C4::Circulation::AddIssue( $patron2, $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 $notice = Koha::Notice::Template->new(
|
||||
{
|
||||
name => 'Hold cancellation',
|
||||
module => 'reserves',
|
||||
code => 'HOLD_CANCELLATION',
|
||||
title => 'Hold cancelled',
|
||||
content => 'Your hold was cancelled.',
|
||||
message_transport_type => 'email',
|
||||
branchcode => q{},
|
||||
}
|
||||
)->store();
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue