From fffc2eb739e557caf593d9ab4545ccc72a050eaa Mon Sep 17 00:00:00 2001 From: Paul Derscheid Date: Fri, 20 Sep 2024 19:08:26 +0000 Subject: [PATCH] Bug 33738: Add bookings feature on the OPAC side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inspired by the work of Paul and Martin, I'm submitting this proposal for the bookings functionality on the OPAC side. Bookings are perform as on the staff side with the api, adding, cancelling and modifying the pickup library works (normally :) ). Test plan: 1) Apply this patch and restart_all 2) Having a bookable item 3) Go to the OPAC side and click on “Place booking” in the menu. 4) Add a booking; if there is an error, it will appear in the modal (preparation of errors linked to circulation rules). 5) Cancel or Modify pickup library Thanks for testing --- .../bootstrap/en/includes/bookings-table.inc | 75 +++ .../bootstrap/en/includes/modals/bookings.inc | 89 ++++ .../en/includes/opac-detail-sidebar.inc | 4 + .../bootstrap/en/includes/select2.inc | 6 + .../bootstrap/en/includes/usermenu.inc | 9 + .../bootstrap/en/modules/opac-bookings.tt | 162 ++++++ .../bootstrap/en/modules/opac-user.tt | 13 +- .../bootstrap/js/modals/place_booking.js | 500 ++++++++++++++++++ .../lib/select2/css/select2-spinner.gif | Bin 0 -> 1849 bytes .../opac-tmpl/lib/select2/css/select2.min.css | 1 + .../opac-tmpl/lib/select2/css/select2.png | Bin 0 -> 613 bytes .../opac-tmpl/lib/select2/css/select2x2.png | Bin 0 -> 845 bytes .../opac-tmpl/lib/select2/js/select2.min.js | 2 + opac/opac-bookings.pl | 143 +++++ opac/opac-detail.pl | 3 + opac/opac-user.pl | 6 + 16 files changed, 1012 insertions(+), 1 deletion(-) create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/bookings-table.inc create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/select2.inc create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/modals/place_booking.js create mode 100644 koha-tmpl/opac-tmpl/lib/select2/css/select2-spinner.gif create mode 100644 koha-tmpl/opac-tmpl/lib/select2/css/select2.min.css create mode 100644 koha-tmpl/opac-tmpl/lib/select2/css/select2.png create mode 100644 koha-tmpl/opac-tmpl/lib/select2/css/select2x2.png create mode 100644 koha-tmpl/opac-tmpl/lib/select2/js/select2.min.js create mode 100755 opac/opac-bookings.pl diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/bookings-table.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/bookings-table.inc new file mode 100644 index 0000000000..1ab8e4fad2 --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/bookings-table.inc @@ -0,0 +1,75 @@ +[% USE Branches %] +[% USE ItemTypes %] +[% USE KohaDates %] +[% PROCESS 'i18n.inc' %] + + + + + + + + + + + + + + + + + + + [% FOREACH BOOKING IN BOOKINGS %] + + + + + + + [% IF BOOKING.item %] + + + [% ELSE %] + + + [% END %] + + + + + [% END # /FOREACH HOLDS %] + +
Bookings ([% BOOKINGS.count | html %] total)
TitlePlaced onPickup locationStart dateEnd dateItem typeBarcodeProvided byModify
+ [% INCLUDE 'biblio-title.inc' biblio=BOOKING.biblio link => 1 %] + [% BOOKING.item.enumchron | html %] + [% BOOKING.biblio.author | html %] + + [% BOOKING.creation_date | $KohaDates %] + + [% BOOKING.pickup_library.branchname | html %] + + [% PROCESS 'modals/bookings.inc' action = 'change-pickup-location' %] + + [% BOOKING.start_date | $KohaDates %] + + [% BOOKING.end_date | $KohaDates %] + + [% BOOKING.item.itemtype.description %] + + [% BOOKING.item.barcode %] + + [% Branches.GetName(BOOKING.item.homebranch) | html %] + + + [% PROCESS 'modals/bookings.inc' action = 'cancel' %] +
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc new file mode 100644 index 0000000000..2645a244d4 --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc @@ -0,0 +1,89 @@ +[% USE Branches %] +[% SET booking_id = BOOKING.booking_id | html %] + + \ No newline at end of file diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc index a0ddc377be..d98176d7ef 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc @@ -9,6 +9,10 @@
  • Place hold
  • [% END %] [% END %] + + [% IF ( BookableItems ) %] +
  • Place booking
  • + [% END %] [% END %] [% IF Koha.Preference('UseRecalls') %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/select2.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/select2.inc new file mode 100644 index 0000000000..db18d56081 --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/select2.inc @@ -0,0 +1,6 @@ +[% USE raw %] +[% USE Asset %] +[% Asset.js("lib/select2/js/select2.min.js") | $raw %] +[% Asset.css("lib/select2/css/select2.min.css") | $raw %] +[% Asset.css("css/select2.css") | $raw %] +[% Asset.js("js/select2.js") | $raw %] \ No newline at end of file diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc index d088f48ab7..9fa70b3d57 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/usermenu.inc @@ -122,6 +122,15 @@ Lists [% END %] + [% IF logged_in_user.bookings.count %] + [% IF ( bookingsview ) %] +
  • + [% ELSE %] +
  • + [% END %] + Bookings ([% logged_in_user.bookings.count | html %])
  • + [% END %] + [% IF Koha.Preference( 'RoutingSerials' ) && logged_in_user && logged_in_user.get_routing_lists.count %] [% IF ( routinglistsview ) %]
  • diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt new file mode 100644 index 0000000000..18b78fbe2b --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt @@ -0,0 +1,162 @@ +[% USE raw %] +[% USE Asset %] +[% USE Koha %] +[% USE KohaDates %] +[% USE Branches %] +[% USE ItemTypes %] +[% USE Price %] +[% USE AuthorisedValues %] +[% USE AdditionalContents %] +[% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] +[% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] +[% SET OpacMySummaryNote = AdditionalContents.get( location => "OpacMySummaryNote", lang => lang, library => branchcode ) %] + +[% SET borrower_club_enrollments = logged_in_user.get_club_enrollments %] +[% SET borrower_enrollable_clubs = logged_in_user.get_enrollable_clubs(1) %] + +[% INCLUDE 'doc-head-open.inc' %] +Your library home › [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog +[% INCLUDE 'doc-head-close.inc' %] +[% Asset.css("css/overdrive.css") | $raw %] +[% BLOCK cssinclude %][% END %] + +[% INCLUDE 'bodytag.inc' bodyid='opac-user' bodyclass='scrollto' %] +[% INCLUDE 'masthead.inc' %] + +
    + [% WRAPPER breadcrumbs %] + [% WRAPPER breadcrumb_item %] + [% INCLUDE 'patron-title.inc' patron = logged_in_user %] + [% END %] + [% WRAPPER breadcrumb_item bc_active= 1 %] + Your bookings + [% END %] + [% END #/ WRAPPER breadcrumbs %] + +
    +
    +
    + +
    +
    +
    +

    Your bookings

    + + [% IF ( messages ) %] +
    + Booking failed: + [% FOREACH message IN messages %] +
    + [% message.description %] +
    + [% END # /FOREACH messages %] + Go back to notice +
    + [% END %] + + [% IF op == 'list' %] +
    + +
    +
    +
    +
    + + [% PROCESS 'modals/bookings.inc' action = 'add' %] +
    +
    + [% PROCESS 'bookings-table.inc' %] +
    + [% END %] +
    +
    +
    +
    +
    + +[% INCLUDE 'opac-bottom.inc' %] +[% BLOCK jsinclude %] + [% Asset.js("js/form-submit.js") | $raw %] + [% Asset.js("lib/vis-timeline/vis-timeline-graph2d.min.js") | $raw %] + [% Asset.js("js/modals/place_booking.js") | $raw %] + [% Asset.js("lib/bootstrap/js/bootstrap.min.js") | $raw %] + [% INCLUDE 'select2.inc' %] + [% INCLUDE 'calendar.inc' %] + [% INCLUDE 'datatables.inc' %] + + + +[% END %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt index a2b2f208fd..605860c2a3 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt @@ -300,6 +300,11 @@ Holds ([% RESERVES.count | html %]) [% END %] [% END %] + [% IF ( BOOKINGS.count ) %] + [% WRAPPER tab_item tabname= "opac-user-bookings" %] + Bookings ([% BOOKINGS.count | html %]) + [% END %] + [% END %] [% IF Koha.Preference('UseRecalls') && RECALLS.count %] [% WRAPPER tab_item tabname= "opac-user-recalls" %] Recalls ([% RECALLS.count | html %]) @@ -835,6 +840,12 @@ [% END # /tab_panel#opac-user-holds %] [% END # / #RESERVES.count %] + [% IF ( BOOKINGS.count ) %] + [% WRAPPER tab_panel tabname="opac-user-bookings" %] + [% PROCESS 'bookings-table.inc' %] + [% END # /tab_panel#opac-user-bookings %] + [% END # / #BOOKINGS.count %] + [% IF Koha.Preference('UseRecalls') && RECALLS.count %] [% WRAPPER tab_panel tabname="opac-user-recalls" %] @@ -1199,7 +1210,7 @@ ); }); - var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table"); + var dTables = $("#checkoutst,#holdst,#overduest,#opac-user-relative-issues-table,#opac-user-bookings-table"); dTables.each(function(){ var thIndex = $(this).find("th.psort").index(); $(this).on("init.dt", function() { diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_booking.js b/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_booking.js new file mode 100644 index 0000000000..5a98fde7df --- /dev/null +++ b/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_booking.js @@ -0,0 +1,500 @@ +let dataFetched = false; +let bookable_items, bookings, checkouts, booking_id, booking_item_id, booking_patron; + +function containsAny(integers1, integers2) { + // Create a hash set to store integers from the second array + let integerSet = {}; + for (let i = 0; i < integers2.length; i++) { + integerSet[integers2[i]] = true; + } + + // Check if any integer from the first array exists in the hash set + for (let i = 0; i < integers1.length; i++) { + if (integerSet[integers1[i]]) { + return true; // Found a match, return true + } + } + + return false; // No match found +} + +$('#opac-add-booking-modal').on('show.bs.modal', function(e) { + // Get context + let biblionumber = $('#biblio-id').val(); + + let start_date = $('#booking-start_date').val(); + let end_date = $('#booking-end_date').val(); + + // Adopt periodPicker + let periodPicker = $("#period").get(0)._flatpickr; + + if ( !dataFetched ) { + + // Fetch list of bookable items + let itemsFetch = $.ajax({ + url: '/api/v1/biblios/' + biblionumber + '/items?bookable=1' + '&_per_page=-1', + dataType: 'json', + type: 'GET' + }); + + // Fetch list of existing bookings + let bookingsFetch = $.ajax({ + url: '/api/v1/bookings?biblio_id=' + biblionumber + '&_per_page=-1', + dataType: 'json', + type: 'GET' + }); + + // Fetch list of current checkouts + let checkoutsFetch = $.ajax({ + url: '/api/v1/biblios/' + biblionumber + '/checkouts?_per_page=-1', + dataType: 'json', + type: 'GET' + }); + + // Update item select2 and period flatpickr + $.when(itemsFetch, bookingsFetch, checkoutsFetch).then( + function(itemsFetch,bookingsFetch, checkoutsFetch){ + + // Set variables + bookable_items = itemsFetch[0]; + bookings = bookingsFetch[0]; + checkouts = checkoutsFetch[0]; + + // Merge current checkouts into bookings + for (checkout of checkouts) { + let booking = { + biblio_id: biblionumber, + booking_id: null, + end_date: checkout.due_date, + item_id: checkout.item_id, + patron_id: checkout.patron_id, + start_date: new Date().toISOString(), + }; + bookings.unshift(booking); + } + + + // Update flatpickr mode + periodPicker.set('mode', 'range'); + + // Total bookable items + let bookable = 0; + + for (item of bookable_items) { + bookable++; + // Populate item select (NOTE: Do we still need this check for pre-existing select option here?) + if (!($('#booking-item-id').find("option[value='" + item.item_id + "']").length)) { + // Create a DOM Option and de-select by default + let newOption = new Option(escape_str(item.external_id), item.item_id, false, false); + if(!booking_item_id) { + booking_item_id = item.external_id; + } + // Append it to the select + $('#booking-item-id').append(newOption); + } + } + + // Pickup location select2 + let pickup_url = + "/api/v1/biblios/" + biblionumber + "/pickup_locations"; + $("#pickup_library_id").select2({ + dropdownParent: $( + ".modal-content", + "#opac-add-booking-modal" + ), + width: "50%", + dropdownAutoWidth: true, + minimumResultsForSearch: 10, + allowClear: false, + placeholder: __("Pickup location"), + }); + function setLocationsPicker(response) { + let $pickupSelect = $("#pickup_library_id"); + let bookableItemnumbers = bookable_items.map(function ( + object + ) { + return object.item_id; + }); + $pickupSelect.empty(); + + $.each(response, function (index, pickup_location) { + if ( + containsAny( + pickup_location.pickup_items, + bookableItemnumbers + ) + ) { + let option = $( + '" + ); + + option.attr( + "data-needs_override", + pickup_location.needs_override + ); + option.attr( + "data-pickup_items", + pickup_location.pickup_items.join(",") + ); + + $pickupSelect.append(option); + } + }); + } + + // Fetch pickup locations and enable picker + $.ajax({ + url: pickup_url, + type: "GET", + dataType: "json", + data: { + _order_by: "name", + _per_page: "-1", + patron_id: logged_in_user_id, + }, + success: function (response) { + if (dataFetched === true) { + setLocationsPicker(response); + } else { + var interval = setInterval(function () { + if (dataFetched === true) { + // Data is fetched, execute the callback and stop the interval + setLocationsPicker(response); + clearInterval(interval); + } + }, 100); + } + }, + error: function (xhr, status, error) { + console.log("Pickup location fetch failed: ", error); + }, + }); + + $("#pickup_library_id").prop("disabled", false); + + // Item select2 + $("#booking-item-id").select2({ + dropdownParent: $(".modal-content", "#opac-add-booking-modal"), + width: '50%', + dropdownAutoWidth: true, + minimumResultsForSearch: 20, + placeholder: __("Select item") + }); + + // Set disable function for periodPicker + let disableExists = periodPicker.config.disable.filter(f => f.name === 'dateDisable'); + if ( disableExists.length === 0 ) { + periodPicker.config.disable.push(function dateDisable(date){ + + // set local copy of selectedDates + let selectedDates = periodPicker.selectedDates; + + // set booked counter + let booked = 0; + + // reset the unavailable items array + let unavailable_items = []; + + // reset the biblio level bookings array + let biblio_bookings = []; + + // disable dates before selected date + if (!selectedDates[1] && (selectedDates[0] && selectedDates[0] > date)) { + return true; + } + + // iterate existing bookings + for (booking of bookings) { + + let start_date = flatpickr.parseDate(booking.start_date); + let end_date = flatpickr.parseDate(booking.end_date); + + // patron has selected a start date (end date checks) + if (selectedDates[0]) { + + // new booking start date is between existing booking start and end dates + if (selectedDates[0] >= start_date && selectedDates[0] <= end_date) { + if (booking.item_id) { + if (unavailable_items.indexOf(booking.item_id) === -1) { + unavailable_items.push(booking.item_id); + } + } else { + if (biblio_bookings.indexOf(booking.booking_id) === -1) { + biblio_bookings.push(booking.booking_id); + } + } + } + + // new booking end date would be between existing booking start and end dates + else if (date >= start_date && date <= end_date) { + if (booking.item_id) { + if (unavailable_items.indexOf(booking.item_id) === -1) { + unavailable_items.push(booking.item_id); + } + } else { + if (biblio_bookings.indexOf(booking.booking_id) === -1) { + biblio_bookings.push(booking.booking_id); + } + } + } + + // new booking would span existing booking + else if (selectedDates[0] <= start_date && date >= end_date) { + if (booking.item_id) { + if (unavailable_items.indexOf(booking.item_id) === -1) { + unavailable_items.push(booking.item_id); + } + } else { + if (biblio_bookings.indexOf(booking.booking_id) === -1) { + biblio_bookings.push(booking.booking_id); + } + } + } + + // new booking would not conflict + else { + continue; + } + + // check that there are available items + // available = all bookable items - booked items - booked biblios + let total_available = bookable_items.length - unavailable_items.length - biblio_bookings.length; + if (total_available === 0) { + return true; + } + } + + // patron has not yet selected a start date (start date checks) + else if (date <= end_date && date >= start_date) { + + // same item, disable date + if (booking.item_id && booking.item_id == booking_item_id) { + return true; + } + + // count all clashes, both item and biblio level + booked++; + if (booked == bookable) { + return true; + } + + // FIXME: The above is not intelligent enough to spot + // cases where an item must be used for a biblio level booking + // due to all other items being booking within the biblio level + // booking period... we end up with a clash + // To reproduce: + // * One bib with two bookable items. + // * Add item level booking + // * Add biblio level booking that extends one day beyond the item level booking + // * Try to book the item without an item level booking from the day before the biblio level + // booking is to be returned. Note this is a clash, the only item available for the biblio + // level booking is the item you just booked out overlapping the end date. + } + } + }); + }; + + // Setup listener for item select2 + $('#booking-item-id').on('select2:select', function(e) { + booking_item_id = e.params.data.id ? e.params.data.id : null; + + // Disable invalid pickup locations + $("#pickup_library_id > option").each(function () { + let option = $(this); + if (booking_item_id == 0) { + option.prop("disabled", false); + } else { + let valid_items = String( + option.data("pickup_items") + ) + .split(",") + .map(Number); + if ( + valid_items.includes(parseInt(booking_item_id)) + ) { + option.prop("disabled", false); + } else { + option.prop("disabled", true); + } + } + }); + $("#pickup_library_id").trigger("change.select2"); + + // redraw pariodPicker taking selected item into account + periodPicker.redraw(); + }); + + // Setup listener for pickup location select2 + $("#pickup_library_id").on("select2:select", function (e) { + let valid_items = + e.params.data.element.dataset.pickup_items.split(","); + valid_items.push("0"); + + // Disable items not available at the pickup location + $("#booking_item_id > option").each(function () { + let option = $(this); + let item_id = option.val(); + if (valid_items.includes(item_id)) { + option.attr("data-pickup", true); + if (option.data("available")) { + option.prop("disabled", false); + } + } else { + option.prop("disabled", true); + option.attr("data-pickup", false); + } + }); + $("#booking_item_id").trigger("change.select2"); + }); + + // Set onChange for flatpickr + let changeExists = periodPicker.config.onChange.filter(f => f.name ==='periodChange'); + if(changeExists.length === 0) { + periodPicker.config.onChange.push(function periodChange(selectedDates, dateStr, instance) { + // Range set, update hidden fields and set available items + if ( selectedDates[0] && selectedDates[1] ) { + // set form fields from picker + let picker_start = dayjs(selectedDates[0]); + let picker_end = dayjs(selectedDates[1]).endOf('day'); + $('#booking-start_date').val(picker_start.toISOString()); + $('#booking-end_date').val(picker_end.toISOString()); + + // set available items in select2 + let booked_items = bookings.filter(function(booking) { + let start_date = flatpickr.parseDate(booking.start_date); + let end_date = flatpickr.parseDate(booking.end_date); + // This booking ends before the start of the new booking + if ( end_date <= selectedDates[0] ) { + return false; + } + // This booking starts after then end of the new booking + if ( start_date >= selectedDates[1] ) { + return false; + } + // This booking overlaps + return true; + }); + $("#booking-item-id > option").each(function() { + let option = $(this); + if ( booking_item_id && booking_item_id == option.val() ) { + option.prop('disabled',false); + } else if ( booked_items.some(function(booked_item){ + return option.val() == booked_item.item_id; + }) ) { + option.prop('disabled',true); + } else { + option.prop('disabled',false); + } + }); + $('#booking-item-id').trigger('change.select2'); + } + // Range not set, reset field options + else { + $('#booking-item-id > option').each(function() { + $(this).prop('disabled', false); + }); + $('#booking-item-id').trigger('change.select2'); + } + }); + }; + + // Enable flatpickr now we have date function populated + periodPicker.redraw(); + $("#period_fields :input").prop('disabled', false); + + // Redraw select with new options and enable + $('#booking-item-id').trigger('change'); + $("#booking-item-id").prop("disabled", false); + + // Set the flag to indicate that data has been fetched + dataFetched = true; + + // Set form values + setFormValues(booking_item_id,start_date,end_date,periodPicker); + }, + function(jqXHR, textStatus, errorThrown){ + console.log("Fetch failed"); + } + ); + } else { + setFormValues(booking_item_id,start_date,end_date,periodPicker); + }; +}); + +function setFormValues(booking_item_id,start_date,end_date,periodPicker){ + + + // Set booking start & end if this is an edit + if ( start_date ) { + // Allow invalid pre-load so setDate can set date range + // periodPicker.set('allowInvalidPreload', true); + // FIXME: Why is this the case.. we're passing two valid Date objects + + let dates = [ new Date(start_date), new Date(end_date) ]; + periodPicker.setDate(dates, true); + } + // Reset periodPicker, biblio_id may have been nulled + else { + periodPicker.redraw(); + }; + + // If passed an itemnumber, pre-select + if (booking_item_id) { + $('#booking-item-id').val(booking_item_id).trigger('change'); + } +} + +$("#opac-add-form-booking").on('submit', function(e) { + e.preventDefault(); + + let url = '/api/v1/bookings'; + + let start_date = $('#booking-start_date').val(); + let end_date = $('#booking-end_date').val(); + let item_id = $('#booking-item-id').val(); + let pickup_library_id = $("#pickup_library_id").val(); + + let posting = $.post( + url, + JSON.stringify({ + "start_date": start_date, + "end_date": end_date, + "biblio_id": $('#biblio-id').val(), + "item_id": item_id, + "patron_id": logged_in_user_id, + pickup_library_id: pickup_library_id, + }) + ); + + posting.done(function(data) { + bookings.push(data); + $('#opac-add-booking-modal').modal('hide'); + window.location.reload(); + }); + + posting.fail(function (data) { + var error = data.responseJSON.error; + var errorMessage = error ? error : "Failure"; + $("#booking_result").replaceWith( + '
    ' + + _(errorMessage) + + "
    " + ); + }); +}); + +$('#opac-add-booking-modal').on('hidden.bs.modal', function (e) { + $('#booking-item-id').val(0).trigger('change'); + $('#period').get(0)._flatpickr.clear(); + $('#booking-start_date').val(''); + $('#booking-end_date').val(''); + $('#booking_id').val(''); + // Reset pickup library select + $("#pickup_library_id").val(null).trigger("change"); + $("#pickup_library_id").empty(); + $("#pickup_library_id").prop("disabled", true); +}); \ No newline at end of file diff --git a/koha-tmpl/opac-tmpl/lib/select2/css/select2-spinner.gif b/koha-tmpl/opac-tmpl/lib/select2/css/select2-spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b33f7e54f4e55b6b8774d86d96895db9af044b4 GIT binary patch literal 1849 zcma*odr(tX9tZI2z31lM+(&YVk%mZ}5P~KlG2s=WSbGzm0!x7^P##Mnh7t-jP!X0Q zk_SQ}Po-L1tlDK;6l?(>v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw literal 0 HcmV?d00001 diff --git a/koha-tmpl/opac-tmpl/lib/select2/css/select2.min.css b/koha-tmpl/opac-tmpl/lib/select2/css/select2.min.css new file mode 100644 index 0000000000..7c18ad59df --- /dev/null +++ b/koha-tmpl/opac-tmpl/lib/select2/css/select2.min.css @@ -0,0 +1 @@ +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;padding:1px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/koha-tmpl/opac-tmpl/lib/select2/css/select2.png b/koha-tmpl/opac-tmpl/lib/select2/css/select2.png new file mode 100644 index 0000000000000000000000000000000000000000..1d804ffb99699b9e030f1010314de0970b5a000d GIT binary patch literal 613 zcmV-r0-F7aP)#WY!I$JQV$)A5aAS1BM||2XVJl=+L1^1S1H% zM-&lx?NZpUrHhn>fk<>POqf2sh40}xxGZfc+t+#Eb(qHy9_3*1(U%t9t)QDnI#YAL(|ACV(>)>6WD-t!8tutHkdb^#3`HzoJG3A2@T`% zA|K@o*b!`R#(7)PWrMFn2))Ca3MR4(zaT`Zr61*kZK5NPnZwQszxh$fyv3?&4c>$q z2m=+yc0dRXRAsPDxF6sD;@rK4JGdR_``1S~o6Xi@2&aR6hcSrEp9HVRzEqVDqBn<1%hR=D4e1f^ra^A|34Cjc=Gny{F(o#MrvPYgZuTJOz(n)-F<| zj()qR;C={)N<0RRvDZ^@6ND+W*}gh-Lip(MDt!(zMSO)!j2j+*hxgzC-e3$@(O2p* zu;+gddm(cZwXTCLx*Ky4THOa*^b^F`woveIeCK^0aR|TJ00000NkvXXu0mjfA#WC6 literal 0 HcmV?d00001 diff --git a/koha-tmpl/opac-tmpl/lib/select2/css/select2x2.png b/koha-tmpl/opac-tmpl/lib/select2/css/select2x2.png new file mode 100644 index 0000000000000000000000000000000000000000..4bdd5c961d452c49dfa0789c2c7ffb82c238fc24 GIT binary patch literal 845 zcmV-T1G4;yP)upQ6WKflyv?C|ADVW!U!t`EpA+x zB)5#EjWk-_X77YJZtQo`E0SF)^1bZr%)B7Cd`*OK*r z5WG-7e-R9G9^69ksDt29&oyHqxPSt|-S>xi3%PTd+GjY+BGF|nWC(7D-sd(kxqd9~ zS@2YF5vB+>dP8+$l^{oO3-lEWiGA*QIU)Wds#9M6RZ9N zcQ4y4)xqQOxD=vwu%7cz1nY#$lT&y8HCmkWgpwQP#3dhnYj9|2aS_R}IUF_^6s#$= zTm%~>A#oM?KIg$kh=<`gJkeoHa2LrulVy$Yx+N_0R3$4I!R*0677f(FKqm`2_o4~W z0h}fQZ`lC^1A+m;fM7uI(R1`S0KtG@KrkQ}5DW+&@cTnDVIow56KciMk7a899t0bC zC1KI{TsMe5NAR%GD_5`B-@ad4k~K3SO%H z_M31|`HV?E6)u$E3c&*<*n20+V@mRCop>R5;DWuZCmjSo7p@R&OYl^@G":">",'"':""","'":"'","/":"/"};return"string"!=typeof e?e:String(e).replace(/[&<>"'\/\\]/g,function(e){return t[e]})},i.appendMany=function(e,t){if("1.7"===o.fn.jquery.substr(0,3)){var n=o();o.map(t,function(e){n=n.add(e)}),t=n}e.append(t)},i.__cache={};var n=0;return i.GetUniqueElementId=function(e){var t=e.getAttribute("data-select2-id");return null==t&&(e.id?(t=e.id,e.setAttribute("data-select2-id",t)):(e.setAttribute("data-select2-id",++n),t=n.toString())),t},i.StoreData=function(e,t,n){var r=i.GetUniqueElementId(e);i.__cache[r]||(i.__cache[r]={}),i.__cache[r][t]=n},i.GetData=function(e,t){var n=i.GetUniqueElementId(e);return t?i.__cache[n]&&null!=i.__cache[n][t]?i.__cache[n][t]:o(e).data(t):i.__cache[n]},i.RemoveData=function(e){var t=i.GetUniqueElementId(e);null!=i.__cache[t]&&delete i.__cache[t],e.removeAttribute("data-select2-id")},i}),e.define("select2/results",["jquery","./utils"],function(h,f){function r(e,t,n){this.$element=e,this.data=n,this.options=t,r.__super__.constructor.call(this)}return f.Extend(r,f.Observable),r.prototype.render=function(){var e=h('
      ');return this.options.get("multiple")&&e.attr("aria-multiselectable","true"),this.$results=e},r.prototype.clear=function(){this.$results.empty()},r.prototype.displayMessage=function(e){var t=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var n=h(''),r=this.options.get("translations").get(e.message);n.append(t(r(e.args))),n[0].className+=" select2-results__message",this.$results.append(n)},r.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},r.prototype.append=function(e){this.hideLoading();var t=[];if(null!=e.results&&0!==e.results.length){e.results=this.sort(e.results);for(var n=0;n",{class:"select2-results__options select2-results__options--nested"});p.append(l),s.append(a),s.append(p)}else this.template(e,t);return f.StoreData(t,"data",e),t},r.prototype.bind=function(t,e){var l=this,n=t.id+"-results";this.$results.attr("id",n),t.on("results:all",function(e){l.clear(),l.append(e.data),t.isOpen()&&(l.setClasses(),l.highlightFirstItem())}),t.on("results:append",function(e){l.append(e.data),t.isOpen()&&l.setClasses()}),t.on("query",function(e){l.hideMessages(),l.showLoading(e)}),t.on("select",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("unselect",function(){t.isOpen()&&(l.setClasses(),l.options.get("scrollAfterSelect")&&l.highlightFirstItem())}),t.on("open",function(){l.$results.attr("aria-expanded","true"),l.$results.attr("aria-hidden","false"),l.setClasses(),l.ensureHighlightVisible()}),t.on("close",function(){l.$results.attr("aria-expanded","false"),l.$results.attr("aria-hidden","true"),l.$results.removeAttr("aria-activedescendant")}),t.on("results:toggle",function(){var e=l.getHighlightedResults();0!==e.length&&e.trigger("mouseup")}),t.on("results:select",function(){var e=l.getHighlightedResults();if(0!==e.length){var t=f.GetData(e[0],"data");"true"==e.attr("aria-selected")?l.trigger("close",{}):l.trigger("select",{data:t})}}),t.on("results:previous",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e);if(!(n<=0)){var r=n-1;0===e.length&&(r=0);var i=t.eq(r);i.trigger("mouseenter");var o=l.$results.offset().top,s=i.offset().top,a=l.$results.scrollTop()+(s-o);0===r?l.$results.scrollTop(0):s-o<0&&l.$results.scrollTop(a)}}),t.on("results:next",function(){var e=l.getHighlightedResults(),t=l.$results.find("[aria-selected]"),n=t.index(e)+1;if(!(n>=t.length)){var r=t.eq(n);r.trigger("mouseenter");var i=l.$results.offset().top+l.$results.outerHeight(!1),o=r.offset().top+r.outerHeight(!1),s=l.$results.scrollTop()+o-i;0===n?l.$results.scrollTop(0):ithis.$results.outerHeight()||o<0)&&this.$results.scrollTop(i)}},r.prototype.template=function(e,t){var n=this.options.get("templateResult"),r=this.options.get("escapeMarkup"),i=n(e,t);null==i?t.style.display="none":"string"==typeof i?t.innerHTML=r(i):h(t).append(i)},r}),e.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),e.define("select2/selection/base",["jquery","../utils","../keys"],function(n,r,i){function o(e,t){this.$element=e,this.options=t,o.__super__.constructor.call(this)}return r.Extend(o,r.Observable),o.prototype.render=function(){var e=n('');return this._tabindex=0,null!=r.GetData(this.$element[0],"old-tabindex")?this._tabindex=r.GetData(this.$element[0],"old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),e.attr("title",this.$element.attr("title")),e.attr("tabindex",this._tabindex),e.attr("aria-disabled","false"),this.$selection=e},o.prototype.bind=function(e,t){var n=this,r=e.id+"-results";this.container=e,this.$selection.on("focus",function(e){n.trigger("focus",e)}),this.$selection.on("blur",function(e){n._handleBlur(e)}),this.$selection.on("keydown",function(e){n.trigger("keypress",e),e.which===i.SPACE&&e.preventDefault()}),e.on("results:focus",function(e){n.$selection.attr("aria-activedescendant",e.data._resultId)}),e.on("selection:update",function(e){n.update(e.data)}),e.on("open",function(){n.$selection.attr("aria-expanded","true"),n.$selection.attr("aria-owns",r),n._attachCloseHandler(e)}),e.on("close",function(){n.$selection.attr("aria-expanded","false"),n.$selection.removeAttr("aria-activedescendant"),n.$selection.removeAttr("aria-owns"),n.$selection.trigger("focus"),n._detachCloseHandler(e)}),e.on("enable",function(){n.$selection.attr("tabindex",n._tabindex),n.$selection.attr("aria-disabled","false")}),e.on("disable",function(){n.$selection.attr("tabindex","-1"),n.$selection.attr("aria-disabled","true")})},o.prototype._handleBlur=function(e){var t=this;window.setTimeout(function(){document.activeElement==t.$selection[0]||n.contains(t.$selection[0],document.activeElement)||t.trigger("blur",e)},1)},o.prototype._attachCloseHandler=function(e){n(document.body).on("mousedown.select2."+e.id,function(e){var t=n(e.target).closest(".select2");n(".select2.select2-container--open").each(function(){this!=t[0]&&r.GetData(this,"element").select2("close")})})},o.prototype._detachCloseHandler=function(e){n(document.body).off("mousedown.select2."+e.id)},o.prototype.position=function(e,t){t.find(".selection").append(e)},o.prototype.destroy=function(){this._detachCloseHandler(this.container)},o.prototype.update=function(e){throw new Error("The `update` method must be defined in child classes.")},o.prototype.isEnabled=function(){return!this.isDisabled()},o.prototype.isDisabled=function(){return this.options.get("disabled")},o}),e.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(e,t,n,r){function i(){i.__super__.constructor.apply(this,arguments)}return n.Extend(i,t),i.prototype.render=function(){var e=i.__super__.render.call(this);return e.addClass("select2-selection--single"),e.html(''),e},i.prototype.bind=function(t,e){var n=this;i.__super__.bind.apply(this,arguments);var r=t.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",r).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",r),this.$selection.on("mousedown",function(e){1===e.which&&n.trigger("toggle",{originalEvent:e})}),this.$selection.on("focus",function(e){}),this.$selection.on("blur",function(e){}),t.on("focus",function(e){t.isOpen()||n.$selection.trigger("focus")})},i.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},i.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},i.prototype.selectionContainer=function(){return e("")},i.prototype.update=function(e){if(0!==e.length){var t=e[0],n=this.$selection.find(".select2-selection__rendered"),r=this.display(t,n);n.empty().append(r);var i=t.title||t.text;i?n.attr("title",i):n.removeAttr("title")}else this.clear()},i}),e.define("select2/selection/multiple",["jquery","./base","../utils"],function(i,e,l){function n(e,t){n.__super__.constructor.apply(this,arguments)}return l.Extend(n,e),n.prototype.render=function(){var e=n.__super__.render.call(this);return e.addClass("select2-selection--multiple"),e.html('
        '),e},n.prototype.bind=function(e,t){var r=this;n.__super__.bind.apply(this,arguments),this.$selection.on("click",function(e){r.trigger("toggle",{originalEvent:e})}),this.$selection.on("click",".select2-selection__choice__remove",function(e){if(!r.isDisabled()){var t=i(this).parent(),n=l.GetData(t[0],"data");r.trigger("unselect",{originalEvent:e,data:n})}})},n.prototype.clear=function(){var e=this.$selection.find(".select2-selection__rendered");e.empty(),e.removeAttr("title")},n.prototype.display=function(e,t){var n=this.options.get("templateSelection");return this.options.get("escapeMarkup")(n(e,t))},n.prototype.selectionContainer=function(){return i('
      • ×
      • ')},n.prototype.update=function(e){if(this.clear(),0!==e.length){for(var t=[],n=0;n×');a.StoreData(r[0],"data",t),this.$selection.find(".select2-selection__rendered").prepend(r)}},e}),e.define("select2/selection/search",["jquery","../utils","../keys"],function(r,a,l){function e(e,t,n){e.call(this,t,n)}return e.prototype.render=function(e){var t=r('');this.$searchContainer=t,this.$search=t.find("input");var n=e.call(this);return this._transferTabIndex(),n},e.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),t.on("open",function(){r.$search.attr("aria-controls",i),r.$search.trigger("focus")}),t.on("close",function(){r.$search.val(""),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.trigger("focus")}),t.on("enable",function(){r.$search.prop("disabled",!1),r._transferTabIndex()}),t.on("disable",function(){r.$search.prop("disabled",!0)}),t.on("focus",function(e){r.$search.trigger("focus")}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")}),this.$selection.on("focusin",".select2-search--inline",function(e){r.trigger("focus",e)}),this.$selection.on("focusout",".select2-search--inline",function(e){r._handleBlur(e)}),this.$selection.on("keydown",".select2-search--inline",function(e){if(e.stopPropagation(),r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented(),e.which===l.BACKSPACE&&""===r.$search.val()){var t=r.$searchContainer.prev(".select2-selection__choice");if(0this.maximumInputLength?this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:t.term,params:t}}):e.call(this,t,n)},e}),e.define("select2/data/maximumSelectionLength",[],function(){function e(e,t,n){this.maximumSelectionLength=n.get("maximumSelectionLength"),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("select",function(){r._checkIfMaximumSelected()})},e.prototype.query=function(e,t,n){var r=this;this._checkIfMaximumSelected(function(){e.call(r,t,n)})},e.prototype._checkIfMaximumSelected=function(e,n){var r=this;this.current(function(e){var t=null!=e?e.length:0;0=r.maximumSelectionLength?r.trigger("results:message",{message:"maximumSelected",args:{maximum:r.maximumSelectionLength}}):n&&n()})},e}),e.define("select2/dropdown",["jquery","./utils"],function(t,e){function n(e,t){this.$element=e,this.options=t,n.__super__.constructor.call(this)}return e.Extend(n,e.Observable),n.prototype.render=function(){var e=t('');return e.attr("dir",this.options.get("dir")),this.$dropdown=e},n.prototype.bind=function(){},n.prototype.position=function(e,t){},n.prototype.destroy=function(){this.$dropdown.remove()},n}),e.define("select2/dropdown/search",["jquery","../utils"],function(o,e){function t(){}return t.prototype.render=function(e){var t=e.call(this),n=o('');return this.$searchContainer=n,this.$search=n.find("input"),t.prepend(n),t},t.prototype.bind=function(e,t,n){var r=this,i=t.id+"-results";e.call(this,t,n),this.$search.on("keydown",function(e){r.trigger("keypress",e),r._keyUpPrevented=e.isDefaultPrevented()}),this.$search.on("input",function(e){o(this).off("keyup")}),this.$search.on("keyup input",function(e){r.handleSearch(e)}),t.on("open",function(){r.$search.attr("tabindex",0),r.$search.attr("aria-controls",i),r.$search.trigger("focus"),window.setTimeout(function(){r.$search.trigger("focus")},0)}),t.on("close",function(){r.$search.attr("tabindex",-1),r.$search.removeAttr("aria-controls"),r.$search.removeAttr("aria-activedescendant"),r.$search.val(""),r.$search.trigger("blur")}),t.on("focus",function(){t.isOpen()||r.$search.trigger("focus")}),t.on("results:all",function(e){null!=e.query.term&&""!==e.query.term||(r.showSearch(e)?r.$searchContainer.removeClass("select2-search--hide"):r.$searchContainer.addClass("select2-search--hide"))}),t.on("results:focus",function(e){e.data._resultId?r.$search.attr("aria-activedescendant",e.data._resultId):r.$search.removeAttr("aria-activedescendant")})},t.prototype.handleSearch=function(e){if(!this._keyUpPrevented){var t=this.$search.val();this.trigger("query",{term:t})}this._keyUpPrevented=!1},t.prototype.showSearch=function(e,t){return!0},t}),e.define("select2/dropdown/hidePlaceholder",[],function(){function e(e,t,n,r){this.placeholder=this.normalizePlaceholder(n.get("placeholder")),e.call(this,t,n,r)}return e.prototype.append=function(e,t){t.results=this.removePlaceholder(t.results),e.call(this,t)},e.prototype.normalizePlaceholder=function(e,t){return"string"==typeof t&&(t={id:"",text:t}),t},e.prototype.removePlaceholder=function(e,t){for(var n=t.slice(0),r=t.length-1;0<=r;r--){var i=t[r];this.placeholder.id===i.id&&n.splice(r,1)}return n},e}),e.define("select2/dropdown/infiniteScroll",["jquery"],function(n){function e(e,t,n,r){this.lastParams={},e.call(this,t,n,r),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return e.prototype.append=function(e,t){this.$loadingMore.remove(),this.loading=!1,e.call(this,t),this.showLoadingMore(t)&&(this.$results.append(this.$loadingMore),this.loadMoreIfNeeded())},e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("query",function(e){r.lastParams=e,r.loading=!0}),t.on("query:append",function(e){r.lastParams=e,r.loading=!0}),this.$results.on("scroll",this.loadMoreIfNeeded.bind(this))},e.prototype.loadMoreIfNeeded=function(){var e=n.contains(document.documentElement,this.$loadingMore[0]);if(!this.loading&&e){var t=this.$results.offset().top+this.$results.outerHeight(!1);this.$loadingMore.offset().top+this.$loadingMore.outerHeight(!1)<=t+50&&this.loadMore()}},e.prototype.loadMore=function(){this.loading=!0;var e=n.extend({},{page:1},this.lastParams);e.page++,this.trigger("query:append",e)},e.prototype.showLoadingMore=function(e,t){return t.pagination&&t.pagination.more},e.prototype.createLoadingMore=function(){var e=n('
      • '),t=this.options.get("translations").get("loadingMore");return e.html(t(this.lastParams)),e},e}),e.define("select2/dropdown/attachBody",["jquery","../utils"],function(f,a){function e(e,t,n){this.$dropdownParent=f(n.get("dropdownParent")||document.body),e.call(this,t,n)}return e.prototype.bind=function(e,t,n){var r=this;e.call(this,t,n),t.on("open",function(){r._showDropdown(),r._attachPositioningHandler(t),r._bindContainerResultHandlers(t)}),t.on("close",function(){r._hideDropdown(),r._detachPositioningHandler(t)}),this.$dropdownContainer.on("mousedown",function(e){e.stopPropagation()})},e.prototype.destroy=function(e){e.call(this),this.$dropdownContainer.remove()},e.prototype.position=function(e,t,n){t.attr("class",n.attr("class")),t.removeClass("select2"),t.addClass("select2-container--open"),t.css({position:"absolute",top:-999999}),this.$container=n},e.prototype.render=function(e){var t=f(""),n=e.call(this);return t.append(n),this.$dropdownContainer=t},e.prototype._hideDropdown=function(e){this.$dropdownContainer.detach()},e.prototype._bindContainerResultHandlers=function(e,t){if(!this._containerResultsHandlersBound){var n=this;t.on("results:all",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:append",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("results:message",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("select",function(){n._positionDropdown(),n._resizeDropdown()}),t.on("unselect",function(){n._positionDropdown(),n._resizeDropdown()}),this._containerResultsHandlersBound=!0}},e.prototype._attachPositioningHandler=function(e,t){var n=this,r="scroll.select2."+t.id,i="resize.select2."+t.id,o="orientationchange.select2."+t.id,s=this.$container.parents().filter(a.hasScroll);s.each(function(){a.StoreData(this,"select2-scroll-position",{x:f(this).scrollLeft(),y:f(this).scrollTop()})}),s.on(r,function(e){var t=a.GetData(this,"select2-scroll-position");f(this).scrollTop(t.y)}),f(window).on(r+" "+i+" "+o,function(e){n._positionDropdown(),n._resizeDropdown()})},e.prototype._detachPositioningHandler=function(e,t){var n="scroll.select2."+t.id,r="resize.select2."+t.id,i="orientationchange.select2."+t.id;this.$container.parents().filter(a.hasScroll).off(n),f(window).off(n+" "+r+" "+i)},e.prototype._positionDropdown=function(){var e=f(window),t=this.$dropdown.hasClass("select2-dropdown--above"),n=this.$dropdown.hasClass("select2-dropdown--below"),r=null,i=this.$container.offset();i.bottom=i.top+this.$container.outerHeight(!1);var o={height:this.$container.outerHeight(!1)};o.top=i.top,o.bottom=i.top+o.height;var s=this.$dropdown.outerHeight(!1),a=e.scrollTop(),l=e.scrollTop()+e.height(),c=ai.bottom+s,d={left:i.left,top:o.bottom},p=this.$dropdownParent;"static"===p.css("position")&&(p=p.offsetParent());var h={top:0,left:0};(f.contains(document.body,p[0])||p[0].isConnected)&&(h=p.offset()),d.top-=h.top,d.left-=h.left,t||n||(r="below"),u||!c||t?!c&&u&&t&&(r="below"):r="above",("above"==r||t&&"below"!==r)&&(d.top=o.top-h.top-s),null!=r&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+r),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+r)),this.$dropdownContainer.css(d)},e.prototype._resizeDropdown=function(){var e={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(e.minWidth=e.width,e.position="relative",e.width="auto"),this.$dropdown.css(e)},e.prototype._showDropdown=function(e){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},e}),e.define("select2/dropdown/minimumResultsForSearch",[],function(){function e(e,t,n,r){this.minimumResultsForSearch=n.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),e.call(this,t,n,r)}return e.prototype.showSearch=function(e,t){return!(function e(t){for(var n=0,r=0;r');return e.attr("dir",this.options.get("dir")),this.$container=e,this.$container.addClass("select2-container--"+this.options.get("theme")),u.StoreData(e[0],"element",this.$element),e},d}),e.define("jquery-mousewheel",["jquery"],function(e){return e}),e.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults","./select2/utils"],function(i,e,o,t,s){if(null==i.fn.select2){var a=["open","close","destroy"];i.fn.select2=function(t){if("object"==typeof(t=t||{}))return this.each(function(){var e=i.extend(!0,{},t);new o(i(this),e)}),this;if("string"!=typeof t)throw new Error("Invalid arguments for Select2: "+t);var n,r=Array.prototype.slice.call(arguments,1);return this.each(function(){var e=s.GetData(this,"select2");null==e&&window.console&&console.error&&console.error("The select2('"+t+"') method was called on an element that is not using Select2."),n=e[t].apply(e,r)}),-1. + +use Modern::Perl; + +use C4::Members (); +use C4::Auth qw( get_template_and_user ); +use C4::Output qw( output_html_with_http_headers ); + +use Koha::Biblios (); +use Koha::Booking (); +use Koha::Bookings (); +use Koha::DateUtils qw(dt_from_string); +use Koha::Patrons (); + +use CGI qw( -utf8 ); +use Carp qw( croak ); +use Digest::SHA qw( sha1_base64 ); +use Time::HiRes qw( time ); + +my $query = CGI->new; +my @messages; + +my ( $template, $borrowernumber, $cookie ) = get_template_and_user( + { + template_name => 'opac-bookings.tt', + query => $query, + type => 'opac', + } +); + +my $patron = Koha::Patrons->find($borrowernumber); + +my $op = $query->param('op') // 'list'; +if ( $op eq 'list' ) { + my $bookings = Koha::Bookings->search( { patron_id => $patron->borrowernumber } )->filter_by_active; + my $hash = sha1_base64( join q{}, time, rand ); + + my $biblio; + my $biblio_id = $query->param('biblio_id'); + if ($biblio_id) { + $biblio = Koha::Biblios->find($biblio_id); + } + + $template->param( + op => 'list', BOOKINGS => $bookings, + BOOKING => { booking_id => $hash }, + biblio => $biblio, + ); +} + +if ( $op eq 'cud-add' ) { + eval { + Koha::Booking->new( + { + patron_id => $patron->borrowernumber, + biblio_id => $query->param('biblio_id'), + item_id => $query->param('item_id'), + start_date => dt_from_string( $query->param('start_date') ), + end_date => dt_from_string( $query->param('end_date') ), + } + )->store; + }; + + if ($@) { + if ( ref($@) eq 'Koha::Exceptions::Booking::Clash') { + push @messages, + { description => "Item already booked for this period", biblio_id => $query->param('biblio_id') }; + } + if ( ref($@) eq 'Koha::Exceptions::Booking::Rule') { + push @messages, { description => $@->{message}, biblio_id => $query->param('biblio_id') }; + } + } else { + print $query->redirect('/cgi-bin/koha/opac-user.pl?tab=opac-user-bookings') or croak; + } +} + +if ( $op eq 'cud-cancel' ) { + my $booking_id = $query->param('booking_id'); + my $booking = Koha::Bookings->find($booking_id); + if ( !$booking ) { + print $query->redirect('/cgi-bin/koha/errors/404.pl') or croak; + exit; + } + + if ( $booking->patron_id ne $patron->borrowernumber ) { + print $query->redirect('/cgi-bin/koha/errors/403.pl') or croak; + exit; + } + + my $is_deleted = $booking->delete; + if ( !$is_deleted ) { + print $query->redirect('/cgi-bin/koha/errors/500.pl') or croak; + exit; + } + + print $query->redirect('/cgi-bin/koha/opac-user.pl') or croak; +} + +if ( $op eq 'cud-change-pickup-location' ) { + my $booking_id = $query->param('booking_id'); + my $new_pickup_location = $query->param('new_pickup_location'); + my $booking = Koha::Bookings->find($booking_id); + + if ( !$booking ) { + print $query->redirect('/cgi-bin/koha/errors/404.pl') or croak; + exit; + } + + if ( $booking->patron_id ne $patron->borrowernumber ) { + print $query->redirect('/cgi-bin/koha/errors/403.pl') or croak; + exit; + } + + my $is_updated = $booking->update( { pickup_library_id => $new_pickup_location } ); + if ( !$is_updated ) { + print $query->redirect('/cgi-bin/koha/errors/500.pl') or croak; + exit; + } + + print $query->redirect('/cgi-bin/koha/opac-user.pl?tab=opac-user-bookings') or croak; +} + +$template->param( + bookingsview => 1, + messages => \@messages +); + +output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 }; diff --git a/opac/opac-detail.pl b/opac/opac-detail.pl index 348e509f6a..a8e9e96311 100755 --- a/opac/opac-detail.pl +++ b/opac/opac-detail.pl @@ -671,6 +671,8 @@ my $holdable_items = $biblio->items->filter_by_for_hold->count; # If we don't have a patron, then holdable items determines holdability my $can_holds_be_placed = $patron ? 0 : $holdable_items; +my $can_bookings_be_placed = $patron ? $biblio->bookable_items->count : 0; + my ( $itemloop_has_images, $otheritemloop_has_images ); my $item_level_holds; my $item_checkouts; @@ -781,6 +783,7 @@ $template->param( item_checkouts => $item_checkouts, item_level_holds => $item_level_holds, ReservableItems => $can_holds_be_placed, + BookableItems => $can_bookings_be_placed, itemloop_has_images => $itemloop_has_images, otheritemloop_has_images => $otheritemloop_has_images, ); diff --git a/opac/opac-user.pl b/opac/opac-user.pl index 3f1ca4ad59..8ebb91bf64 100755 --- a/opac/opac-user.pl +++ b/opac/opac-user.pl @@ -340,6 +340,12 @@ $template->param( showpriority => $show_priority, ); +my $bookings = $patron->bookings->filter_by_active; + +$template->param( + BOOKINGS => $bookings, +); + if ( C4::Context->preference('UseRecalls') ) { my $recalls = Koha::Recalls->search( { patron_id => $borrowernumber, completed => 0 } ); $template->param( RECALLS => $recalls ); -- 2.25.1