From 6594141fbec41e9b0da58c9fa03bf28007c3b928 Mon Sep 17 00:00:00 2001 From: Pedro Amorim <pedro.amorim@ptfs-europe.com> Date: Fri, 5 Jan 2024 15:24:07 +0000 Subject: [PATCH] Bug 35604: ConfirmAuto workflow stage Signed-off-by: David Nind <david@davidnind.com> --- Koha/ILL/Request/Workflow/ConfirmAuto.pm | 116 +++++++++++++ ill/ill-requests.pl | 9 + .../prog/en/modules/ill/ill-requests.tt | 48 ++++++ .../intranet-tmpl/prog/js/ill-autobackend.js | 163 ++++++++++++++++++ .../bootstrap/en/modules/opac-illrequests.tt | 42 ++++- .../opac-tmpl/bootstrap/js/ill-autobackend.js | 161 +++++++++++++++++ opac/opac-illrequests.pl | 13 ++ 7 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 Koha/ILL/Request/Workflow/ConfirmAuto.pm create mode 100644 koha-tmpl/intranet-tmpl/prog/js/ill-autobackend.js create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/ill-autobackend.js diff --git a/Koha/ILL/Request/Workflow/ConfirmAuto.pm b/Koha/ILL/Request/Workflow/ConfirmAuto.pm new file mode 100644 index 0000000000..fe25702011 --- /dev/null +++ b/Koha/ILL/Request/Workflow/ConfirmAuto.pm @@ -0,0 +1,116 @@ +package Koha::ILL::Request::Workflow::ConfirmAuto; + +# Copyright 2023 PTFS Europe Ltd +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see <http://www.gnu.org/licenses>. + +use Modern::Perl; + +use base qw(Koha::ILL::Request::Workflow); + +use JSON qw( encode_json ); +use Koha::ILL::Backends; + +=head1 NAME + +Koha::ILL::Request::Workflow::ConfirmAuto - Koha ILL ConfirmAuto Workflow + +=head1 SYNOPSIS + +Object-oriented class that provides the AutoILLBackendPriority confirmation screen + +=head1 DESCRIPTION + +This class provides the ability to verify if it should render the AutoILLBackendPriority +confirmation screen and handle the template params accordingly + +=head1 API + +=head2 Class Methods + +=head3 show_confirm_auto + + my $show_confirm_auto = + Koha::ILL::Request::Workflow::ConfirmAuto->show_confirm_auto($params); + +Given $request, returns true if confirm auto should be shown + +=cut + +sub show_confirm_auto { + my ( $self, $request ) = @_; + + return + + # AutoILLBackendPriority is enabled + C4::Context->preference("AutoILLBackendPriority") + + # Confirm auto has not yet been submitted + && !$self->{metadata}->{confirm_auto_submitted} + + # The form has been submitted and the backend is able to create the request + && $request->_backend_capability( 'can_create_request', $self->{metadata} ); + +} + +=head3 confirm_auto_template_params + +Given $params, returns the template parameters for rendering the confirm auto screen + +=cut + +sub confirm_auto_template_params { + my ( $self, $params ) = @_; + + $params->{method} = 'confirmautoill' if $self->{ui_context} eq 'staff'; + delete $params->{stage} if $self->{ui_context} eq 'staff'; + + my @backends = $self->get_priority_backends(); + return ( + whole => $params, + metadata => $self->prep_metadata($params), + core_fields => Koha::ILL::Backend::Standard->_get_core_fields, + auto_backends_json => scalar encode_json( \@backends ), + $self->{ui_context} eq 'opac' + ? ( + illrequestsview => 1, + message => $params->{message}, + op => 'confirmautoill', + ) + : () + ); +} + +=head3 get_priority_backends + +Returns backends ordered by AutoILLBackendPriority + +=cut + +sub get_priority_backends { + my ( $self, $params ) = @_; + + my @backends; + my @priority_enabled_backends = split ",", C4::Context->preference('AutoILLBackendPriority'); + foreach my $backend (@priority_enabled_backends) { + my $loaded_backend = Koha::ILL::Request->new->load_backend($backend); + my $availability_check_info = $loaded_backend->_backend->availability_check_info( $self->{metadata} ); + push @backends, $availability_check_info if $availability_check_info; + } + return @backends; +} + +1; diff --git a/ill/ill-requests.pl b/ill/ill-requests.pl index 71d716ac56..a75ebc4ce4 100755 --- a/ill/ill-requests.pl +++ b/ill/ill-requests.pl @@ -31,6 +31,7 @@ use Koha::ILL::Request; use Koha::ILL::Batches; use Koha::ILL::Request::Workflow::Availability; use Koha::ILL::Request::Workflow::TypeDisclaimer; +use Koha::ILL::Request::Workflow::ConfirmAuto; use Koha::Libraries; use Koha::Plugins; @@ -137,6 +138,8 @@ if ( $backends_available ) { Koha::ILL::Request::Workflow::Availability->new( $params, 'staff' ); my $type_disclaimer = Koha::ILL::Request::Workflow::TypeDisclaimer->new( $params, 'staff' ); + my $confirm_auto = + Koha::ILL::Request::Workflow::ConfirmAuto->new( $params, 'staff' ); # ILLCheckAvailability operation if ($availability->show_availability($request)) { @@ -150,6 +153,12 @@ if ( $backends_available ) { $template->param( $type_disclaimer->type_disclaimer_template_params($params) ); + # ConfirmAuto operation + } elsif ( $confirm_auto->show_confirm_auto($request)) { + $op = 'confirmautoill'; + $template->param( + $confirm_auto->confirm_auto_template_params($params) + ); # Ready to create ILL request } else { my $backend_result = $request->backend_create($params); diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt index 732f706d0b..f4754793a3 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt @@ -44,6 +44,13 @@ [% WRAPPER breadcrumb_item bc_active= 1 %] <span>Request type disclaimer</span> [% END %] + [% ELSIF query_type == 'confirmautoill' %] + [% WRAPPER breadcrumb_item %] + <a href="/cgi-bin/koha/ill/ill-requests.pl">ILL requests</a> + [% END %] + [% WRAPPER breadcrumb_item bc_active= 1 %] + <span>Confirm automatic request</span> + [% END %] [% ELSE %] [% WRAPPER breadcrumb_item bc_active= 1 %] <span>ILL requests</span> @@ -929,6 +936,39 @@ </fieldset> </form> </div> + [% ELSIF op == 'confirmautoill' %] + <!-- confirmautoill --> + <h1>Confirm automatic request</h1> + <div id="results" class="page-section"> + <form method="post" id="confirmautoill-form"> + <fieldset class="rows"> + <h3>Confirm backend:</h3> + <p id="autoillbackends"></p> + <p id="autoillbackend-message" class="text-info"></p> + </fieldset> + <fieldset class="action"> + [% FOREACH key IN whole.keys %] + [% value = whole.$key %] + [% IF key != 'method' && key != 'custom_key' && key != 'custom_value' %] + <input type="hidden" name="[% key | html %]" value="[% value | html %]"> + [% END %] + [% END %] + [% custom_keys = whole.custom_key.split('\0') %] + [% custom_values = whole.custom_value.split('\0') %] + [% i = 0 %] + [% FOREACH custom_key IN custom_keys %] + <input type="hidden" name="custom_key" value="[% custom_key | html %]"> + <input type="hidden" name="custom_value" value="[% custom_values.$i | html %]"> + [% i = i + 1 %] + [% END %] + <input type="hidden" name="method" value="create" /> + <input type="hidden" name="stage" value="form"> + <input type="hidden" name="confirm_auto_submitted" value="1"> + <input type="submit" value="Confirm" /> + <a class="cancel" href="ill-requests.pl">Cancel</a> + </fieldset> + </form> + </div> [% ELSIF op == 'batch_list' || op == 'batch_create' %] [% INCLUDE 'ill-batch.inc' %] [% ELSE %] @@ -973,6 +1013,11 @@ [% ELSE %] var services = []; [% END %] + [% IF auto_backends_json.length > 0 %] + var auto_backends = [% auto_backends_json | $raw %]; + [% ELSE %] + var auto_backends = []; + [% END %] [% IF metadata.length > 0 %] var metadata = "[% metadata | $raw %]"; [% END %] @@ -1008,6 +1053,9 @@ [% IF (op == 'availability' || op == 'generic_confirm') && Koha.Preference('ILLCheckAvailability') %] [% Asset.js("js/ill-availability.js") | $raw %] [% END %] + [% IF (op == 'confirmautoill' && Koha.Preference('AutoILLBackendPriority')) %] + [% Asset.js("js/ill-autobackend.js") | $raw %] + [% END %] [% IF op == 'availability' && Koha.Preference('ILLCheckAvailability') %] <script> $(document).ready(function() { diff --git a/koha-tmpl/intranet-tmpl/prog/js/ill-autobackend.js b/koha-tmpl/intranet-tmpl/prog/js/ill-autobackend.js new file mode 100644 index 0000000000..ac3dfe5892 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/ill-autobackend.js @@ -0,0 +1,163 @@ +$(document).ready(function () { + let auto_ill_el = "#confirmautoill-form #autoillbackends"; + let auto_ill_message_el = "#confirmautoill-form #autoillbackend-message"; + + confirmAutoInit(); + getBackendsAvailability(auto_backends, metadata); + + /** + * Retrieves the backend availability for a given auto backend and metadata. + * + * @param {Object} auto_backend - The auto backend object. + * @param {string} metadata - The metadata string. + * @return {Promise} A Promise that resolves to the JSON response. + */ + async function getBackendAvailability(auto_backend, metadata) { + return $.ajax({ + url: auto_backend.endpoint + metadata, + type: "GET", + dataType: "json", + beforeSend: function () { + _addBackendPlaceholderEl(auto_backend.name); + _addBackendOption(auto_backend.name); + _addVerifyingMessage(auto_backend.name); + auto_backend.available = 0; + }, + success: function (data) { + _addSuccessMessage(auto_backend.name); + auto_backend.available = 1; + }, + error: function (request, textstatus) { + if (textstatus === "timeout") { + _addErrorMessage( + auto_backend.name, + __("Verification timed out.") + ); + } else { + let message = "Error"; + if (request.hasOwnProperty("responseJSON")) { + if (request.responseJSON.error) { + message = request.responseJSON.error; + } else if (request.responseJSON.errors) { + message = request.responseJSON.errors + .map(error => error.message) + .join(", "); + } + } + _addErrorMessage(auto_backend.name, message); + } + }, + timeout: 10000, + }); + } + + /** + * Asynchronously checks the availability of multiple auto backends. + * + * @param {Array} auto_backends - An array of auto backends to check availability for. + * @param {Object} metadata - Additional metadata for the availability check. + * @return {void} + */ + function getBackendsAvailability(auto_backends, metadata) { + let promises = []; + for (const auto_backend of auto_backends) { + try { + const prom = getBackendAvailability(auto_backend, metadata); + promises.push(prom); + } catch (e) { + console.log(e); + } + } + Promise.allSettled(promises).then(() => { + let auto_backend = auto_backends.find(backend => backend.available); + if (typeof auto_backend === "undefined") { + _setAutoBackend("Standard"); + } else { + _setAutoBackend(auto_backend.name); + } + $('#confirmautoill-form .action input[type="submit"]').prop( + "disabled", + false + ); + }); + _addBackendPlaceholderEl("Standard"); + _addBackendOption("Standard"); + } + + function _addSuccessMessage(auto_backend_name) { + _removeVerifyingMessage(auto_backend_name); + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + '<span class="text-success"><i class="fa-solid fa-check"></i> ' + + __("Available.").format(auto_backend_name) + + "</span>" + ); + } + + function _addErrorMessage(auto_backend_name, message) { + _removeVerifyingMessage(auto_backend_name); + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + '<span class="text-danger"> <i class="fa-solid fa-xmark"></i> ' + + __("Not readily available:").format(auto_backend_name) + + " " + + message + + "</span>" + ); + } + + function _addBackendOption(auto_backend_name) { + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + ' <input type="radio" id="' + + auto_backend_name + + '" name="backend" value="' + + auto_backend_name + + '">' + + ' <label for="' + + auto_backend_name + + '" class="radio">' + + auto_backend_name + + "</label> " + ); + } + + function _addVerifyingMessage(auto_backend_name) { + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + '<span id="verifying-availabilty" class="text-info"><i id="issues-table-load-delay-spinner" class="fa fa-spinner fa-pulse fa-fw"></i> ' + + __("Verifying availability...").format(auto_backend_name) + + "</span>" + ); + } + function _removeVerifyingMessage(auto_backend_name) { + $( + auto_ill_el + + " #backend-" + + auto_backend_name + + " #verifying-availabilty" + ).remove(); + } + + function _setAutoBackend(auto_backend_name) { + $( + '#confirmautoill-form #autoillbackends input[id="' + + auto_backend_name + + '"]' + ).prop("checked", true); + $(auto_ill_message_el).html( + __( + "The recommended backend for your request is <strong>%s</strong>." + ).format(auto_backend_name) + ); + } + + function _addBackendPlaceholderEl(auto_backend_name) { + $(auto_ill_el).append('<div id="backend-' + auto_backend_name + '">'); + } + + function confirmAutoInit() { + $('#confirmautoill-form .action input[name="backend"]').remove(); + $('#confirmautoill-form .action input[type="submit"]').prop( + "disabled", + true + ); + $(auto_ill_message_el).html(__("Fetching backends availability...")); + } +}); diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt index 7c6e5da1fa..38abce1e35 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt @@ -45,6 +45,10 @@ [% WRAPPER breadcrumb_item bc_active= 1 %] <span>Ill request disclaimer</span> [% END %] + [% ELSIF op == 'confirmautoill' %] + [% WRAPPER breadcrumb_item bc_active= 1 %] + <span>Confirm automatic request</span> + [% END %] [% END %] [% ELSE %] [% WRAPPER breadcrumb_item bc_active= 1 %] @@ -121,7 +125,7 @@ <div id="illrequests-create-button" class="dropdown btn-group"> [% IF Koha.Preference('AutoILLBackendPriority') %] <a id="ill-new" class="btn btn-primary" href="/cgi-bin/koha/opac-illrequests.pl?method=create&backend=Standard"> - <i class="fa fa-plus" aria-hidden="true"></i> Create a new request + <i class="fa fa-plus" aria-hidden="true"></i> Create a new auto request </a> [% ELSIF backends.size > 1 %] <button class="btn btn-primary dropdown-toggle" type="button" id="ill-backend-dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> @@ -329,6 +333,34 @@ </fieldset> </form> </div> + [% ELSIF op == 'confirmautoill' %] + <h1>Confirming automatic request</h1> + <div id="results" class="page-section"> + <form method="post" id="confirmautoill-form"> + [% INCLUDE 'csrf-token.inc' %] + <fieldset class="rows"> + <p id="autoillbackends"></p> + <p id="autoillbackend-message" class="text-info"></p> + </fieldset> + <fieldset class="action"> + [% FOREACH key IN whole.keys %] + [% value = whole.$key %] + [% IF key != 'custom_key' && key != 'custom_value' && key != 'csrf_token' %] + <input type="hidden" name="[% key | html %]" value="[% value | html %]"> + [% END %] + [% END %] + [% custom_keys = whole.custom_key.split('\0') %] + [% custom_values = whole.custom_value.split('\0') %] + [% i = 0 %] + [% FOREACH custom_key IN custom_keys %] + <input type="hidden" name="custom_key" value="[% custom_key | html %]"> + <input type="hidden" name="custom_value" value="[% custom_values.$i | html %]"> + [% i = i + 1 %] + [% END %] + <input type="hidden" name="confirm_auto_submitted" value="1"> + </fieldset> + </form> + </div> [% END # / IF op == 'cud-create' %] </div> <!-- / #illrequests --> [% END # /IF !backends_available %] @@ -355,6 +387,11 @@ [% ELSE %] var services = []; [% END %] + [% IF auto_backends_json.length > 0 %] + var auto_backends = [% auto_backends_json | $raw %]; + [% ELSE %] + var auto_backends = []; + [% END %] [% IF metadata.length > 0 %] var metadata = "[% metadata | $raw %]"; [% END %] @@ -367,6 +404,9 @@ }); </script> [% END %] + [% IF op == 'confirmautoill' %] + [% Asset.js("js/ill-autobackend.js") | $raw %] + [% END %] [% TRY %] [% PROCESS backend_jsinclude %] [% CATCH %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/ill-autobackend.js b/koha-tmpl/opac-tmpl/bootstrap/js/ill-autobackend.js new file mode 100644 index 0000000000..cc25885473 --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/js/ill-autobackend.js @@ -0,0 +1,161 @@ +$(document).ready(function () { + let auto_ill_el = "#confirmautoill-form #autoillbackends"; + let auto_ill_message_el = "#confirmautoill-form #autoillbackend-message"; + + confirmAutoInit(); + getBackendsAvailability(auto_backends, metadata); + + /** + * Retrieves the backend availability for a given auto backend and metadata. + * + * @param {Object} auto_backend - The auto backend object. + * @param {string} metadata - The metadata string. + * @return {Promise} A Promise that resolves to the JSON response. + */ + async function getBackendAvailability(auto_backend, metadata) { + return $.ajax({ + url: auto_backend.endpoint + metadata, + type: "GET", + dataType: "json", + beforeSend: function () { + _addBackendPlaceholderEl(auto_backend.name); + _addBackendOption(auto_backend.name); + _addVerifyingMessage(auto_backend.name); + auto_backend.available = 0; + }, + success: function (data) { + _addSuccessMessage(auto_backend.name); + auto_backend.available = 1; + }, + error: function (request, textstatus) { + if (textstatus === "timeout") { + _addErrorMessage( + auto_backend.name, + __("Verification timed out.") + ); + } else { + let message = "Error"; + if (request.hasOwnProperty("responseJSON")) { + if (request.responseJSON.error) { + message = request.responseJSON.error; + } else if (request.responseJSON.errors) { + message = request.responseJSON.errors + .map(error => error.message) + .join(", "); + } + } + _addErrorMessage(auto_backend.name, message); + } + }, + timeout: 10000, + }); + } + + /** + * Asynchronously checks the availability of multiple auto backends. + * + * @param {Array} auto_backends - An array of auto backends to check availability for. + * @param {Object} metadata - Additional metadata for the availability check. + * @return {void} + */ + function getBackendsAvailability(auto_backends, metadata) { + let promises = []; + for (const auto_backend of auto_backends) { + try { + const prom = getBackendAvailability(auto_backend, metadata); + promises.push(prom); + } catch (e) { + console.log(e); + } + } + Promise.allSettled(promises).then(() => { + let auto_backend = auto_backends.find(backend => backend.available); + if (typeof auto_backend === "undefined") { + _setAutoBackend("Standard"); + } else { + _setAutoBackend(auto_backend.name); + } + $('#confirmautoill-form .action input[type="submit"]').prop( + "disabled", + false + ); + }); + _addBackendPlaceholderEl("Standard"); + _addBackendOption("Standard"); + } + + function _addSuccessMessage(auto_backend_name) { + _removeVerifyingMessage(auto_backend_name); + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + '<span class="text-success"><i class="fa-solid fa-check"></i> ' + + __("Available.").format(auto_backend_name) + + "</span>" + ); + } + + function _addErrorMessage(auto_backend_name, message) { + _removeVerifyingMessage(auto_backend_name); + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + '<span class="text-danger"> <i class="fa-solid fa-xmark"></i> ' + + __("Not readily available:").format(auto_backend_name) + + " " + + message + + "</span>" + ); + } + + function _addBackendOption(auto_backend_name) { + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + ' <input type="radio" id="' + + auto_backend_name + + '" name="backend" value="' + + auto_backend_name + + '">' + + ' <label for="' + + auto_backend_name + + '" class="radio">' + + auto_backend_name + + "</label> " + ); + } + + function _addVerifyingMessage(auto_backend_name) { + $(auto_ill_el + " > #backend-" + auto_backend_name).append( + '<span id="verifying-availabilty" class="text-info"><i id="issues-table-load-delay-spinner" class="fa fa-spinner fa-pulse fa-fw"></i> ' + + __("Verifying availability...").format(auto_backend_name) + + "</span>" + ); + } + function _removeVerifyingMessage(auto_backend_name) { + $( + auto_ill_el + + " #backend-" + + auto_backend_name + + " #verifying-availabilty" + ).remove(); + } + + function _setAutoBackend(auto_backend_name) { + $( + '#confirmautoill-form #autoillbackends input[id="' + + auto_backend_name + + '"]' + ).prop("checked", true); + $("#confirmautoill-form").submit(); + } + + function _addBackendPlaceholderEl(auto_backend_name) { + $(auto_ill_el) + .append('<div id="backend-' + auto_backend_name + '">') + .hide(); + } + + function confirmAutoInit() { + $('#confirmautoill-form .action input[name="backend"]').remove(); + $(auto_ill_message_el).html( + '<span id="verifying-availabilty" class="text-info"><i id="issues-table-load-delay-spinner" class="fa fa-spinner fa-pulse fa-fw"></i> ' + + __("Placing your request...") + + "</span>" + ); + } +}); diff --git a/opac/opac-illrequests.pl b/opac/opac-illrequests.pl index 92f3e5e4f6..a7582e0c8a 100755 --- a/opac/opac-illrequests.pl +++ b/opac/opac-illrequests.pl @@ -33,6 +33,7 @@ use Koha::ILL::Request; use Koha::Libraries; use Koha::Patrons; use Koha::ILL::Request::Workflow::Availability; +use Koha::ILL::Request::Workflow::ConfirmAuto; use Koha::ILL::Request::Workflow::TypeDisclaimer; my $query = CGI->new; @@ -124,6 +125,8 @@ if ( $op eq 'list' ) { Koha::ILL::Request::Workflow::Availability->new( $params, 'opac' ); my $type_disclaimer = Koha::ILL::Request::Workflow::TypeDisclaimer->new( $params, 'opac' ); + my $confirm_auto = + Koha::ILL::Request::Workflow::ConfirmAuto->new( $params, 'opac' ); # ILLCheckAvailability operation if ($availability->show_availability($request)) { @@ -145,6 +148,16 @@ if ( $op eq 'list' ) { $template->output, undef, { force_no_caching => 1 }; exit; + # ConfirmAuto operation + } elsif ( $confirm_auto->show_confirm_auto($request)) { + $op = 'confirmautoill'; + $template->param( + $confirm_auto->confirm_auto_template_params($params) + ); + output_html_with_http_headers $query, $cookie, + $template->output, undef, + { force_no_caching => 1 }; + exit; } my $patron = Koha::Patrons->find( { borrowernumber => $loggedinuser } ); -- 2.39.2