Bugzilla – Attachment 176294 Details for
Bug 38493
Place bookings on multiple items
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 38493: Add possibility to add mutliple bookings at once
Bug-38493-Add-possibility-to-add-mutliple-bookings.patch (text/plain), 16.97 KB, created by
Thibaud Guillot (thibaud_g)
on 2025-01-09 15:12:55 UTC
(
hide
)
Description:
Bug 38493: Add possibility to add mutliple bookings at once
Filename:
MIME Type:
Creator:
Thibaud Guillot (thibaud_g)
Created:
2025-01-09 15:12:55 UTC
Size:
16.97 KB
patch
obsolete
>From 1006b34fc949a533c86c7ecfffa3eb593aff0607 Mon Sep 17 00:00:00 2001 >From: Thibaud Guillot <thibaud.guillot@biblibre.com> >Date: Tue, 3 Dec 2024 14:04:51 +0100 >Subject: [PATCH] Bug 38493: Add possibility to add mutliple bookings at once >MIME-Version: 1.0 >Content-Type: text/plain; charset=UTF-8 >Content-Transfer-Encoding: 8bit > >Note: For now it's implemented only on the OPAC side > >If the biblio_id parameter is not present in the url, the modal window will be replaced by another form. >Items can be found by barcode, and once added, validated by clicking on the button. >Pickup locations will automatically appear according to the item's biblios. > >Test plan: >1) Apply this patch and restart_all >2) Active booking for one item or itemtype >3) Go on the OPAC url 'cgi-bin/koha/opac-bookings.pl' >4) You can add some items with their barcodes >5) Dynamically delete items if you want >6) And when you're ready, click on 'Validate items' it will check if you can perform a booking for each items with the same pickup library and on the same period > >Sponsored by: Association de Gestion des Ã…uvres Sociales d'Inria (AGOS) >--- > .../bootstrap/en/includes/modals/bookings.inc | 137 +++++++++++++- > .../bootstrap/en/modules/opac-bookings.tt | 6 +- > .../js/modals/place_multiple_bookings.js | 169 ++++++++++++++++++ > 3 files changed, 308 insertions(+), 4 deletions(-) > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/modals/place_multiple_bookings.js > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc >index 2645a244d4..aa5c850ce0 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/modals/bookings.inc >@@ -66,7 +66,42 @@ > </ol> > </fieldset> > [% END %] >- [% CASE %] >+ [% CASE 'add-multiple' %] >+ <div id="booking_result"></div> >+ <fieldset class="brief"> >+ <input type="hidden" name="biblio_id" id="biblio-id" value="[% biblio.biblionumber %]"> >+ <input type="hidden" name="start_date" id="booking-start_date" value=""> >+ <input type="hidden" name="end_date" id="booking-end_date" value=""> >+ <input type="hidden" id="items-hidden" name="items"> >+ <ol> >+ <li> >+ <div> >+ <label for="itemnumber">Add barcode :</label> >+ <input type="text" id="barcode" name="barcode"> >+ <button type="button" class="btn btn-sm btn-link" > >+ <i class="fa fa-fw fa-plus" aria-hidden="true" onclick="addItem()"></i> >+ </button> >+ </div> >+ <ul id="items-list"></ul> >+ </li> >+ <button type="button" id="checks_bookings" name="opac-add-multiple-check" class="btn btn-sm btn-link" style="display:none" onclick="validateItems()"> >+ <i class="fa fa-fw fa-arrows-rotate" aria-hidden="true"></i> Validate items >+ </button> >+ <li> >+ <label class="required" for="booking_pickup">Pickup at:</label> >+ <select name="booking_pickup" id="pickup_library_id" required="required" disabled="true"></select> >+ <span class="required">Required</span> >+ </li> >+ <li> >+ <div id="period_fields"> >+ <label class="required" for="period">Booking dates: </label> >+ <input type="text" id="period" name="period" class="flatpickr" data-flatpickr-futuredate="true" data-flatpickr-disable-shortcuts="true" required autocomplete="off"> >+ <span class="required">Required</span> >+ </div> >+ <div class="hint">Select the booking start date and end date</div> >+ </li> >+ </ol> >+ </fieldset> > [% END %] > </div> > <div class="modal-footer"> >@@ -86,4 +121,102 @@ > </div> <!-- /.modal-content --> > </form> > </div> <!-- /.modal-dialog --> >-</div> <!-- /.modal --> >\ No newline at end of file >+</div> <!-- /.modal --> >+<script> >+function addItem() { >+ const barcodeInput = document.getElementById('barcode'); >+ const itemsList = document.getElementById('items-list'); >+ const barcode = barcodeInput.value.trim(); >+ const errorDiv = document.getElementById('booking_result'); >+ const errorElement = document.createElement('div'); >+ errorElement.id = 'add_booking_error'; >+ errorElement.classList.add('alert', 'alert-danger'); >+ const observer = new MutationObserver(function() { >+ if (itemsList.children.length === 0) { >+ $("#pickup_library_id").prop("disabled", true); >+ $("#checks_bookings").hide(); >+ >+ } else { >+ $("#pickup_library_id").prop("disabled", false); >+ $("#checks_bookings").show(); >+ } >+ }); >+ observer.observe(itemsList, { >+ childList: true, >+ subtree: true >+ }); >+ >+ if (barcode) { >+ let targetItemInList = document.getElementById('booking_item-' + barcode); >+ if (targetItemInList) { >+ errorElement.textContent = 'Item already in the list.'; >+ errorDiv.appendChild(errorElement); >+ return; >+ } >+ let biblioTitle; >+ let itemRepresentation = $.ajax({ >+ url: "/api/v1/items/?external_id=" + barcode, >+ type: "GET", >+ dataType: "json", >+ success: function (response) { >+ if (!response[0].bookable) { >+ errorElement.textContent = 'Item not bookable'; >+ errorDiv.replaceWith(errorElement); >+ } else { >+ let biblio_id = response[0].biblio_id; >+ let item_id = response[0].item_id; >+ let biblioRepresentation = $.ajax({ >+ url: "/api/v1/biblios/" + biblio_id, >+ type: "GET", >+ dataType: "json", >+ success: function (response) { >+ if (response) { >+ biblioTitle = response.title; >+ const listItem = document.createElement('li'); >+ listItem.setAttribute('id', 'booking_item-' + barcode); >+ listItem.dataset.biblio_id = biblio_id; >+ listItem.dataset.item_id = item_id; >+ listItem.textContent = `${item_id} - ${biblioTitle} (${barcode})`; >+ const deleteLink = document.createElement('a'); >+ deleteLink.classList.add('fa', 'fa-fw', 'fa-times'); >+ deleteLink.setAttribute('aria-hidden', 'true'); >+ deleteLink.onclick = function () { >+ deleteItem(listItem); >+ }; >+ listItem.appendChild(deleteLink); >+ itemsList.appendChild(listItem); >+ barcodeInput.value = ''; >+ updateItemList(); >+ } >+ }, >+ error: function (xhr, status, error) { >+ errorElement.textContent = 'Fetch failed: ' + error; >+ errorDiv.replaceWith(errorElement); >+ }, >+ }); >+ } >+ }, >+ error: function (xhr, status, error) { >+ errorElement.textContent = 'Fetch failed: ' + error; >+ errorDiv.replaceWith(errorElement); >+ }, >+ }); >+ } >+} >+function updateItemList() { >+ const itemsList = document.getElementById('items-list'); >+ const itemnumbers = []; >+ const barcodes = itemsList.getElementsByTagName('li'); >+ for (let barcode of barcodes) { >+ itemnumbers.push(barcode.textContent); >+ } >+ const itemsHidden = document.getElementById('items-hidden'); >+ itemsHidden.value = JSON.stringify(itemnumbers); >+} >+function deleteItem(listItem) { >+ const itemsList = document.getElementById('items-list'); >+ itemsList.removeChild(listItem); >+ $('#pickup_library_id').empty(); >+ updateItemList(); >+} >+</script> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt >index 18b78fbe2b..77be977c8a 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-bookings.tt >@@ -67,15 +67,16 @@ > <div class="tab-content"> > <div id="toolbar" class="toolbar clearfix"> > <div class="list-actions"> >+ [% SET action_method = biblio == '' ? 'add-multiple' : 'add' %] > <button > type="button" > class="btn btn-sm btn-link" > data-bs-toggle="modal" >- data-bs-target="#opac-add-booking-modal" >+ data-bs-target="#opac-[%action_method %]-booking-modal" > > > <i class="fa fa-fw fa-plus" aria-hidden="true"></i> New booking > </button> >- [% PROCESS 'modals/bookings.inc' action = 'add' %] >+ [% PROCESS 'modals/bookings.inc' action = action_method %] > </div> <!-- / .list-actions --> > </div> <!-- / #toolbar --> > [% PROCESS 'bookings-table.inc' %] >@@ -92,6 +93,7 @@ > [% 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("js/modals/place_multiple_bookings.js") | $raw %] > [% Asset.js("lib/bootstrap/js/bootstrap.min.js") | $raw %] > [% INCLUDE 'select2.inc' %] > [% INCLUDE 'calendar.inc' %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_multiple_bookings.js b/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_multiple_bookings.js >new file mode 100644 >index 0000000000..bb21c6f141 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/js/modals/place_multiple_bookings.js >@@ -0,0 +1,169 @@ >+function validateItems (){ >+ let $pickupSelect = $("#pickup_library_id"); >+ var bookingItems = $('#items-list li').map(function() { >+ var barcode = $(this).attr('id').replace('booking_item-', ''); >+ var biblioId = $(this).data('biblio_id'); >+ var itemId = $(this).data('item_id'); >+ return { >+ [barcode]: { >+ biblionumber: biblioId, >+ itemnumber: itemId >+ } >+ }; >+ }).get(); >+ >+ var pickupLocations = []; >+ var promises = bookingItems.map(function(item) { >+ var barcode = Object.keys(item)[0]; >+ var biblionumber = item[barcode].biblionumber; >+ >+ return new Promise(function(resolve, reject) { >+ addPickupLocation(pickupLocations, biblionumber, resolve, reject); >+ }); >+ }); >+ Promise.all(promises).then(function() { >+ $pickupSelect.empty(); >+ pickupLocations.forEach(el => { >+ let option = $( >+ '<option value="' + >+ el.id + >+ '">' + >+ el.name + >+ "</option>" >+ ); >+ $pickupSelect.append(option); >+ }); >+ }).catch(function(error) { >+ console.log("An error occurred while fetching pickup locations: ", error); >+ }); >+ $pickupSelect.prop('disabled', false); >+ >+ // Set flatpickr >+ let periodPicker = $("#period").get(0)._flatpickr; >+ periodPicker.set("mode", "range"); >+ >+ // 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() >+ ); >+ } >+ }); >+ } >+} >+ >+function addPickupLocation(locations, biblionumber, resolve, reject) { >+ let pickup_url = "/api/v1/biblios/" + biblionumber + "/pickup_locations"; >+ $.ajax({ >+ url: pickup_url, >+ type: "GET", >+ dataType: "json", >+ data: { >+ _order_by: "name", >+ _per_page: "-1", >+ patron_id: logged_in_user_id, >+ }, >+ success: function(response) { >+ var library_id = response[0].library_id; >+ var library_name = response[0].name; >+ >+ if (!locations.some(location => location.id === library_id)) { >+ locations.push({ id: library_id, name: library_name }); >+ } >+ resolve(); >+ }, >+ error: function(xhr, status, error) { >+ console.log("Pickup location fetch failed: ", error); >+ reject(error); >+ }, >+ }); >+} >+ >+$("#opac-add-multiple-form-booking").on("submit", function (e) { >+ e.preventDefault(); >+ >+ var url = "/api/v1/bookings"; >+ >+ var items_list = $('#items-list li'); >+ var start_date = $("#booking-start_date").val(); >+ var end_date = $("#booking-end_date").val(); >+ var pickup_library_id = $("#pickup_library_id").val(); >+ >+ var errors = []; >+ var requests = []; >+ var sendBookingRequest = function (item_id, biblio_id) { >+ return $.post( >+ url, >+ JSON.stringify({ >+ start_date: start_date, >+ end_date: end_date, >+ biblio_id: biblio_id, >+ item_id: item_id, >+ patron_id: logged_in_user_id, >+ pickup_library_id: pickup_library_id >+ }) >+ ); >+ }; >+ >+ items_list.each(function (i) { >+ item_id = $(this).data('item_id'); >+ biblio_id = $(this).data('biblio_id'); >+ var request = sendBookingRequest(item_id, biblio_id); >+ >+ requests.push(request); >+ >+ request.fail(function (data) { >+ var error = data.responseJSON.error; >+ var errorMessage = error ? error : "Failure"; >+ errors.push(item_id + " - " + errorMessage); >+ }); >+ }); >+ >+ $.when.apply($, requests).done(function () { >+ if (errors.length > 0) { >+ $("#booking_result").replaceWith( >+ '<div id="booking_result" class="alert alert-danger">' + >+ errors.join("<br>") + >+ "</div>" >+ ); >+ } else { >+ $("#opac-add-multiple-booking-modal").modal("hide"); >+ window.location.reload(); >+ } >+ }).fail(function () { >+ $("#booking_result").replaceWith( >+ '<div id="booking_result" class="alert alert-danger">' + >+ errors.join("<br>") + >+ "</div>" >+ ); >+ }); >+}); >+ >+$("#opac-add-multiple-booking-modal").on("hidden.bs.modal", function (e) { >+ $("#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); >+}); >-- >2.25.1
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 38493
: 176294