From 0901738c03f8f1488e655cfa4ae33f19a1ebe237 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, check for item existance and availability and then check the item out after any confirmations have been displayed. --- .../bootstrap/en/includes/masthead.inc | 7 + .../bootstrap/en/includes/modals/checkout.inc | 25 ++ .../bootstrap/en/includes/opac-bottom.inc | 10 + .../opac-tmpl/bootstrap/js/modals/checkout.js | 214 ++++++++++++++++++ 4 files changed, 256 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 fbab4ec2c9..7b704b6e0c 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc @@ -80,6 +80,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..67d3c66e2c --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/checkout.inc @@ -0,0 +1,25 @@ + + 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 4fab59077d..33cf6b8610 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 %] @@ -124,6 +128,9 @@ [% Asset.js("lib/bootstrap/js/bootstrap.bundle.min.js") | $raw %] [% Asset.js("lib/fontfaceobserver.min.js") | $raw %] [% Asset.js("lib/enquire.min.js") | $raw %] + [% Asset.js("js/global.js") | $raw %] [% IF ( OPACAmazonCoverImages || SyndeticsCoverImages ) %] [% 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..522f60b9ad --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/js/modals/checkout.js @@ -0,0 +1,214 @@ +$(document).ready(function() { + + function resetResults(){ + $('#checkoutResults').replaceWith('
'); + $('#availabilityResult').replaceWith('
'); + }; + + 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 current has outstanding charges of '%s'").format(data); + } + else { + result += _("Message code '%s' with data '%s'").format(code,data); + } + + result += '
'; + $('#availabilityResult').append(result); + }; + + // On modal show, clear any prior results and set focus + $('#checkoutModal').on('shown.bs.modal', function(e) { + $('#checkoutResults').replaceWith('
'); + $('#availabilityResult').replaceWith('
'); + $('#external_id').focus(); + }); + + // On modal submit + $('#checkoutModal').on('click', '#checkoutSubmit', function(e) { + + // Get item from barcode + let external_id = $('#checkout_barcode').val(); + 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) { + item_id = data[0].item_id; + console.log('Found one item with id: ' + 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) { + console.log('Items request failed with: ' + textStatus); + console.log(data); + } + ); + + let checkout = availability.then( + function(data, textStatus, jqXHR) { + let result; + console.log(data); + // 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]}`); + } + } + console.log("Request succeeded but blockers were reported"); + } + // 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]}`); + console.log(`${key}: ${data.confirms[key]}`); + } + } + $('#checkoutSubmit').replaceWith(''); + console.log("Request succeeded confirmation is required"); + } + // 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/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]}`); + console.log(`${key}: ${data.warnings[key]}`); + } + } + console.log("Request succeeded but there were warnings"); + } + + // 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) { + console.log("checkout.then succeeded"); + $('#checkoutResults').replaceWith('
' + _("Item '%s' was checked out").format(external_id) + '
'); + $('#checkout_barcode').val("").focus(); + console.log(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 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/checkouts?confirmation=' + token, + type: 'POST', + data: JSON.stringify(params), + contentType: "json" + }); + + checkout.then( + function(data, textStatus, jqXHR) { + console.log("checkout.then succeeded"); + $('#item_id').remove; + $('#confirm_token').remove; + $('#checkoutResults').replaceWith('
' + _("Item '%s' was checked out").format(external_id) + '
'); + $('#checkoutConfirm').replaceWith(''); + $('#checkout_barcode').val("").focus(); + // data retrieved from url2 as provided by the first request + }, + function(data, textStatus, jqXHR) { + console.log("checkout.then failed"); + } + ); + }); +}); -- 2.20.1