From 709255fdfa0182e9928447c1b64af52ff31c5ca6 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Mon, 15 Aug 2022 12:19:31 +0100 Subject: [PATCH] Bug 30979: Add checkout modal to the OPAC This patch adds a new self checkout modal to the OPAC when OpacTrustedCheckout is enabled and a user is logged in. The new modal allows an end user to scan an item barcode to checkout. We check for item existance and availability and then check the item out after any confirmations have been displayed. Signed-off-by: Silvia Meakins Signed-off-by: Kyle M Hall Signed-off-by: Tomas Cohen Arazi --- .../bootstrap/en/includes/masthead.inc | 7 + .../bootstrap/en/includes/modals/checkout.inc | 40 +++ .../bootstrap/en/includes/opac-bottom.inc | 8 + .../opac-tmpl/bootstrap/js/modals/checkout.js | 249 ++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/checkout.inc create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc index 7abe61f0d6..aeac56d833 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc @@ -81,6 +81,13 @@ [% END # / IF virtualshelves %] + [% IF ( Koha.Preference( 'OpacTrustedCheckout' ) && loggedinusername ) %] + + [% END %] [% IF Koha.Preference( 'opacuserlogin' ) == 1 || Koha.Preference( 'EnableOpacSearchHistory') || Koha.Preference( 'opaclanguagesdisplay' ) %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/checkout.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/checkout.inc new file mode 100644 index 0000000000..1bb20c536c --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/checkout.inc @@ -0,0 +1,40 @@ + + diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc index b0497410c2..4a142d2641 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc @@ -117,6 +117,10 @@ [% END # /IF OpacLangSelectorMode == 'both' || OpacLangSelectorMode == 'footer' %] [% END # / UNLESS is_popup %] +[% IF ( Koha.Preference( 'OpacTrustedCheckout' ) && loggedinusername ) %] + [% INCLUDE 'modals/checkout.inc' %] +[% END %] + [% Asset.js("lib/jquery/jquery-3.6.0.min.js") | $raw %] [% Asset.js("lib/jquery/jquery-migrate-3.3.2.min.js") | $raw %] @@ -284,6 +288,7 @@ $(document).ready(function() { } }); +[% INCLUDE 'js-date-format.inc' %] [% PROCESS jsinclude %] [% IF ( Koha.Preference('OPACUserJS') ) %] [% END %] [% END %] +[% IF ( Koha.Preference( 'OpacTrustedCheckout' ) && loggedinusername ) %] + [% Asset.js("js/modals/checkout.js") | $raw %] +[% END %] [% KohaPlugins.get_plugins_opac_js | $raw %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js b/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js new file mode 100644 index 0000000000..0c666c55e7 --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js @@ -0,0 +1,249 @@ +$(document).ready(function() { + + let checkouts_count = 0; + let current_item; + + function addResult(type, code, data) { + let result = ''; + if (type == 'danger') { + result += '
'; + } else if (type == 'warning') { + result += '
'; + } else if (type == 'info') { + result += '
'; + } else { + result += '
'; + } + + if (code == 'NOT_FOUND') { + result += _("Item '%s' not found").format(data); + } else if (code == 'RENEW_ISSUE') { + result += _("Item will be renewed").format(data); + } else if (code == 'OTHER_CHARGES') { + result += _("Your account currently has outstanding charges of '%s'").format(data); + } else if (code == 'DEBT') { + result += _("Your account is currently in debt by '%s'").format(data); + } else if (code == 'ISSUED_TO_ANOTHER') { + result += _("This item appears to be checked out to another patron, please return it to the desk"); + } else if (code == 'RESERVED' || code == 'RESERVED_WAITING') { + result += _("This item appears to be reserved for another patron, please return it to the desk"); + } else if (code == 'TOO_MANY') { + result += _("You have reached the maximum number of checkouts allowed on your account"); + } else if (code == 'AGE_RESTRICTION') { + result += _("This item is age restricted"); + } else if (code == 'NO_MORE_RENEWALS') { + result += _("Maximum renewals reached for this item"); + } else if (code == 'NOT_FOR_LOAN') { + result += _("This item is not normally for loan, please select another or ask at the desk"); + } else if (code == 'WTHDRAWN') { + result += _("This item is marked withdrawn, please select another or ask at the desk"); + } else if (code == 'EMPTY') { + result += _("Please enter the barcode for the item you wish to checkout"); + } else { + result += _("Message code '%s' with data '%s'").format(code, data); + } + + result += '
'; + $('#availabilityResult').append(result); + }; + + function addCheckout(checkout) { + // Alert that checkout was successful + $('#checkoutResults').replaceWith('
' + _("Item '%s' was checked out").format(current_item.external_id) + '
'); + // Cleanup input and unset readonly + $('#checkout_barcode').val("").prop("readonly", false).focus(); + // Display checkouts table if not already visible + $('#checkoutsTable').show(); + // Add checkout to checkouts table + $('#checkoutsTable > tbody').append('' + current_item.external_id +''+ $date(checkout.due_date) +''); + $('#checkoutsCount').html(++checkouts_count); + // Reset to submission + $('#checkoutConfirm').replaceWith(''); + }; + + // On modal show, clear any prior results and set focus + $('#checkoutModal').on('shown.bs.modal', function(e) { + $('#checkoutResults').replaceWith('
'); + $('#availabilityResult').replaceWith('
'); + $('#checkoutsTable').hide(); + $('#checkout_barcode').val("").focus(); + }); + + // On modal submit + $('#checkoutModal').on('click', '#checkoutSubmit', function(e) { + + // Get item from barcode + let external_id = $('#checkout_barcode').val(); + if ( external_id === '' ) { + addResult('warning', 'EMPTY'); + return; + } + + let item_id; + let items = $.ajax({ + url: '/api/v1/public/items?external_id=' + external_id, + dataType: 'json', + type: 'GET' + }); + + $('#availabilityResult').replaceWith('
'); + + // Get availability of the item + let availability = items.then( + function(data, textStatus, jqXHR) { + if (data.length == 1) { + current_item = data[0]; + item_id = current_item.item_id; + return $.ajax({ + url: '/api/v1/public/checkouts/availability?item_id=' + item_id + '&patron_id=' + logged_in_user_id, + type: 'GET', + contentType: "json" + }); + } else { + addResult('danger', 'NOT_FOUND', external_id); + } + }, + function(data, textStatus, jqXHR) { + addResult('danger', 'NOT_FOUND', external_id); + console.log('Items request failed with: ' + textStatus); + } + ); + + let checkout = availability.then( + function(data, textStatus, jqXHR) { + let result; + // blocked + if (Object.keys(data.blockers).length >= 1) { + for (const key in data.blockers) { + if (data.blockers.hasOwnProperty(key)) { + addResult('danger', `${key}`, `${data.blockers[key]}`); + console.log(`${key}: ${data.blockers[key]}`); + $('#checkout_barcode').val("").prop("readonly", false).focus(); + } + } + } + // requires confirmation + else if (Object.keys(data.confirms).length >= 1) { + for (const key in data.confirms) { + if (data.confirms.hasOwnProperty(key)) { + addResult('warning', `${key}`, `${data.confirms[key]}`); + } + } + $('#checkout_barcode').prop("readonly", true); + $('#checkoutSubmit').replaceWith(''); + } + // straight checkout + else { + let params = { + "checkout_id": undefined, + "patron_id": logged_in_user_id, + "item_id": item_id, + "due_date": undefined, + "library_id": undefined, + "issuer_id": undefined, + "checkin_date": undefined, + "last_renewed_date": undefined, + "renewals_count": undefined, + "unseen_renewals": undefined, + "auto_renew": undefined, + "auto_renew_error": undefined, + "timestamp": undefined, + "checkout_date": undefined, + "onsite_checkout": false, + "note": undefined, + "note_date": undefined, + "note_seen": undefined, + }; + result = $.ajax({ + url: '/api/v1/public/patrons/'+logged_in_user_id+'/checkouts', + type: 'POST', + data: JSON.stringify(params), + contentType: "json" + }); + } + + // warnings to display + if (Object.keys(data.warnings).length >= 1) { + for (const key in data.warnings) { + if (data.warnings.hasOwnProperty(key)) { + addResult('info', `${key}`, `${data.warnings[key]}`); + } + } + } + + // return a rejected promise if we've reached here + return result ? result : $.Deferred().reject('Checkout halted'); + }, + function(data, textStatus, jqXHR) { + console.log('Items request failed with: ' + textStatus); + console.log(data); + } + ); + + checkout.then( + function(data, textStatus, jqXHR) { + addCheckout(data); + // data retrieved from url2 as provided by the first request + }, + function(data, textStatus, jqXHR) { + console.log("checkout.then failed"); + } + ); + + }); + + $('#checkoutModal').on('click', '#checkoutConfirm', function(e) { + let external_id = $('#checkout_barcode').val(); + let item_id = $('#item_id').val(); + let token = $('#confirm_token').val(); + let params = { + "checkout_id": undefined, + "patron_id": logged_in_user_id, + "item_id": item_id, + "due_date": undefined, + "library_id": undefined, + "issuer_id": undefined, + "checkin_date": undefined, + "last_renewed_date": undefined, + "renewals_count": undefined, + "unseen_renewals": undefined, + "auto_renew": undefined, + "auto_renew_error": undefined, + "timestamp": undefined, + "checkout_date": undefined, + "onsite_checkout": false, + "note": undefined, + "note_date": undefined, + "note_seen": undefined, + }; + let checkout = $.ajax({ + url: '/api/v1/public/patrons/' + + logged_in_user_id + + '/checkouts?confirmation=' + + token, + type: 'POST', + data: JSON.stringify(params), + contentType: "json" + }); + + checkout.then( + function(data, textStatus, jqXHR) { + $('#item_id').remove; + $('#confirm_token').remove; + $('#availabilityResult').replaceWith('
'); + addCheckout(data); + // data retrieved from url2 as provided by the first request + }, + function(data, textStatus, jqXHR) { + console.log("checkout.then failed"); + } + ); + }); + + $('#checkoutModal').on('hidden.bs.modal', function (e) { + let pageName = $(location).attr("pathname"); + if ( pageName == '/cgi-bin/koha/opac-user.pl' ) { + location.reload(); + } + }) +}); -- 2.39.5