1 $('#placeBookingModal').on('show.bs.modal', function(e) {
2 var button = $(e.relatedTarget);
3 var biblionumber = button.data('biblionumber');
4 var itemnumber = button.data('itemnumber') || 0;
5 $('#booking_biblio_id').val(biblionumber);
8 $("#booking_patron_id").kohaSelect({
9 dropdownParent: $(".modal-content", "#placeBookingModal"),
11 dropdownAutoWidth: true,
13 minimumInputLength: 3,
15 url: '/api/v1/patrons',
18 data: function(params) {
19 var search_term = (params.term === undefined) ? '' : params.term;
24 "-like": search_term + '%'
29 "-like": search_term + '%'
34 "-like": search_term + '%'
39 '_order_by': 'firstname',
44 processResults: function(data, params) {
46 data.results.forEach(function(patron) {
48 "id": patron.patron_id,
49 "text": escape_str(patron.firstname) + " " + escape_str(patron.surname) + " (" + escape_str(patron.cardnumber) + ")"
53 "results": results, "pagination": { "more": data.pagination.more }
57 placeholder: "Search for a patron"
60 // If passed patron, pre-select
61 var patron_id = button.data('patron') || 0;
64 url: '/api/v1/patrons/' + patron_id,
71 var newOption = new Option(escape_str(patron.firstname) + " " + escape_str(patron.surname) + " (" + escape_str(patron.cardnumber) + ")", patron.patron_id, true, true);
72 $('#booking_patron_id').append(newOption).trigger('change');
78 $("#booking_item_id").select2({
79 dropdownParent: $(".modal-content", "#placeBookingModal"),
81 dropdownAutoWidth: true,
82 minimumResultsForSearch: 20,
83 placeholder: "Select item"
86 // Adopt flatpickr and update mode
87 var periodPicker = $("#period").get(0)._flatpickr;
88 periodPicker.set('mode', 'range');
90 // Fetch list of bookable items
92 url: '/api/v1/biblios/' + biblionumber + '/items?bookable=1' + '&_per_page=-1',
97 // Fetch list of existing bookings
98 var bookings = $.ajax({
99 url: '/api/v1/bookings?biblio_id=' + biblionumber,
104 // Update item select2 and period flatpickr
105 $.when(items, bookings).then(
106 function(items,bookings){
108 // Total bookable items
111 for (item of items[0]) {
113 // Populate item select
114 if (!($('#booking_item_id').find("option[value='" + item.item_id + "']").length)) {
115 if (itemnumber && itemnumber == item.item_id) {
116 // Create a DOM Option and pre-select by default
117 var newOption = new Option(escape_str(item.external_id), item.item_id, true, true);
118 // Append it to the select
119 $('#booking_item_id').append(newOption);
121 // Create a DOM Option and de-select by default
122 var newOption = new Option(escape_str(item.external_id), item.item_id, false, false);
123 // Append it to the select
124 $('#booking_item_id').append(newOption);
129 // Redraw select with new options and enable
130 $('#booking_item_id').trigger('change');
131 $("#booking_item_id").prop("disabled", false);
133 // Set disabled dates in datepicker
134 periodPicker.config.disable.push( function(date) {
136 // set local copy of selectedDates
137 let selectedDates = periodPicker.selectedDates;
139 // set booked counter
142 // reset the unavailable items array
143 let unavailable_items = [];
145 // reset the biblio level bookings array
146 let biblio_bookings = [];
148 // disable dates before selected date
149 if (selectedDates[0] && selectedDates[0] > date) {
153 // iterate existing bookings
154 for (booking of bookings[0]) {
155 var start_date = flatpickr.parseDate(booking.start_date);
156 var end_date = flatpickr.parseDate(booking.end_date);
158 // patron has selected a start date (end date checks)
159 if (selectedDates[0]) {
161 // new booking start date is between existing booking start and end dates
162 if (selectedDates[0] >= start_date && selectedDates[0] <= end_date) {
163 if (booking.item_id) {
164 if (unavailable_items.indexOf(booking.item_id) === -1) {
165 unavailable_items.push(booking.item_id);
168 if (biblio_bookings.indexOf(booking.booking_id) === -1) {
169 biblio_bookings.push(booking.booking_id);
174 // new booking end date would be between existing booking start and end dates
175 else if (date >= start_date && date <= end_date) {
176 if (booking.item_id) {
177 if (unavailable_items.indexOf(booking.item_id) === -1) {
178 unavailable_items.push(booking.item_id);
181 if (biblio_bookings.indexOf(booking.booking_id) === -1) {
182 biblio_bookings.push(booking.booking_id);
187 // new booking would span existing booking
188 else if (selectedDates[0] <= start_date && date >= end_date) {
189 if (booking.item_id) {
190 if (unavailable_items.indexOf(booking.item_id) === -1) {
191 unavailable_items.push(booking.item_id);
194 if (biblio_bookings.indexOf(booking.booking_id) === -1) {
195 biblio_bookings.push(booking.booking_id);
200 // new booking would not conflict
205 // check that there are available items
206 // available = all bookable items - booked items - booked biblios
207 let total_available = items[0].length - unavailable_items.length - biblio_bookings.length;
208 if (total_available === 0) {
213 // patron has not yet selected a start date (start date checks)
214 else if (date <= end_date && date >= start_date) {
216 // same item, disable date
217 if (booking.item_id && booking.item_id == itemnumber) {
221 // count all clashes, both item and biblio level
223 if (booked == bookable) {
230 // Enable flatpickr now we have date function populated
231 periodPicker.redraw();
232 $("#period_fields :input").prop('disabled', false);
234 // Setup listener for item select2
235 $('#booking_item_id').on('select2:select', function(e) {
236 itemnumber = e.params.data.id ? e.params.data.id : null;
238 // redraw pariodPicker taking selected item into account
239 periodPicker.redraw();
242 // Set onClose for flatpickr
243 periodPicker.config.onClose.push(function(selectedDates, dateStr, instance) {
245 if ( selectedDates[0] && selectedDates[1] ) {
246 // set form fields from picker
247 let picker_start = new Date(selectedDates[0]);
248 let picker_end = new Date(selectedDates[1]);
249 picker_end.setHours(picker_end.getHours()+23);
250 picker_end.setMinutes(picker_end.getMinutes()+59);
251 $('#booking_start_date').val(picker_start.toISOString());
252 $('#booking_end_date').val(picker_end.toISOString());
254 // set available items in select2
255 var booked_items = bookings[0].filter(function(booking) {
256 let start_date = flatpickr.parseDate(booking.start_date);
257 let end_date = flatpickr.parseDate(booking.end_date);
258 // This booking ends before the start of the new booking
259 if ( end_date <= selectedDates[0] ) {
262 // This booking starts after then end of the new booking
263 if ( start_date >= selectedDates[1] ) {
266 // This booking overlaps
269 $("#booking_item_id > option").each(function() {
270 let option = $(this);
271 if ( itemnumber && itemnumber == option.val() ) {
272 console.log("itemnumber defined and equal to value");
273 } else if ( booked_items.some(function(booked_item){
274 return option.val() == booked_item.item_id;
276 option.prop('disabled',true);
278 option.prop('disabled',false);
284 function(jqXHR, textStatus, errorThrown){
285 console.log("Fetch failed");
290 $("#placeBookingForm").on('submit', function(e) {
293 var url = '/api/v1/bookings';
295 var start_date = $('#booking_start_date').val();
296 var end_date = $('#booking_end_date').val();
297 var item_id = $('#booking_item_id').val();
299 var posting = $.post(
302 "start_date": start_date,
303 "end_date": end_date,
304 "biblio_id": $('#booking_biblio_id').val(),
305 "item_id": item_id != 0 ? item_id : null,
306 "patron_id": $('#booking_patron_id').find(':selected').val()
310 posting.done(function(data) {
311 // Update bookings page as required
312 if (typeof bookings_table !== 'undefined' && bookings_table !== null) {
313 bookings_table.api().ajax.reload();
317 $('#placeBookingModal').modal('hide');
320 $('#booking_patron_id').val(null).trigger('change');
321 $('#booking_item_id').val(null).trigger('change');
322 $("#period").get(0)._flatpickr.clear();
323 $('#booking_start_date').val('');
324 $('#booking_end_date').val('');
327 posting.fail(function(data) {
328 $('#booking_result').replaceWith('<div id="booking_result" class="alert alert-danger">Failure</div>');