|
Line 0
Link Here
|
| 0 |
- |
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); |
| 6 |
|
| 7 |
// Patron select2 |
| 8 |
$("#booking_patron_id").kohaSelect({ |
| 9 |
dropdownParent: $(".modal-content", "#placeBookingModal"), |
| 10 |
width: '30%', |
| 11 |
dropdownAutoWidth: true, |
| 12 |
allowClear: true, |
| 13 |
minimumInputLength: 3, |
| 14 |
ajax: { |
| 15 |
url: '/api/v1/patrons', |
| 16 |
delay: 250, |
| 17 |
dataType: 'json', |
| 18 |
data: function(params) { |
| 19 |
var search_term = (params.term === undefined) ? '' : params.term; |
| 20 |
var query = { |
| 21 |
'q': JSON.stringify({ |
| 22 |
"-or": [{ |
| 23 |
"firstname": { |
| 24 |
"-like": search_term + '%' |
| 25 |
} |
| 26 |
}, |
| 27 |
{ |
| 28 |
"surname": { |
| 29 |
"-like": search_term + '%' |
| 30 |
} |
| 31 |
}, |
| 32 |
{ |
| 33 |
"cardnumber": { |
| 34 |
"-like": search_term + '%' |
| 35 |
} |
| 36 |
} |
| 37 |
] |
| 38 |
}), |
| 39 |
'_order_by': 'firstname', |
| 40 |
'_page': params.page, |
| 41 |
}; |
| 42 |
return query; |
| 43 |
}, |
| 44 |
processResults: function(data, params) { |
| 45 |
var results = []; |
| 46 |
data.results.forEach(function(patron) { |
| 47 |
results.push({ |
| 48 |
"id": patron.patron_id, |
| 49 |
"text": escape_str(patron.firstname) + " " + escape_str(patron.surname) + " (" + escape_str(patron.cardnumber) + ")" |
| 50 |
}); |
| 51 |
}); |
| 52 |
return { |
| 53 |
"results": results, "pagination": { "more": data.pagination.more } |
| 54 |
}; |
| 55 |
}, |
| 56 |
}, |
| 57 |
placeholder: "Search for a patron" |
| 58 |
}); |
| 59 |
|
| 60 |
// If passed patron, pre-select |
| 61 |
var patron_id = button.data('patron') || 0; |
| 62 |
if (patron_id) { |
| 63 |
var patron = $.ajax({ |
| 64 |
url: '/api/v1/patrons/' + patron_id, |
| 65 |
dataType: 'json', |
| 66 |
type: 'GET' |
| 67 |
}); |
| 68 |
|
| 69 |
$.when(patron).then( |
| 70 |
function(patron){ |
| 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'); |
| 73 |
} |
| 74 |
); |
| 75 |
} |
| 76 |
|
| 77 |
// Item select2 |
| 78 |
$("#booking_item_id").select2({ |
| 79 |
dropdownParent: $(".modal-content", "#placeBookingModal"), |
| 80 |
width: '30%', |
| 81 |
dropdownAutoWidth: true, |
| 82 |
minimumResultsForSearch: 20, |
| 83 |
placeholder: "Select item" |
| 84 |
}); |
| 85 |
|
| 86 |
// Adopt flatpickr and update mode |
| 87 |
var periodPicker = $("#period").get(0)._flatpickr; |
| 88 |
periodPicker.set('mode', 'range'); |
| 89 |
|
| 90 |
// Fetch list of bookable items |
| 91 |
var items = $.ajax({ |
| 92 |
url: '/api/v1/biblios/' + biblionumber + '/items?bookable=1' + '&_per_page=-1', |
| 93 |
dataType: 'json', |
| 94 |
type: 'GET' |
| 95 |
}); |
| 96 |
|
| 97 |
// Fetch list of existing bookings |
| 98 |
var bookings = $.ajax({ |
| 99 |
url: '/api/v1/bookings?biblio_id=' + biblionumber, |
| 100 |
dataType: 'json', |
| 101 |
type: 'GET' |
| 102 |
}); |
| 103 |
|
| 104 |
// Update item select2 and period flatpickr |
| 105 |
$.when(items, bookings).then( |
| 106 |
function(items,bookings){ |
| 107 |
|
| 108 |
// Total bookable items |
| 109 |
var bookable = 0; |
| 110 |
|
| 111 |
for (item of items[0]) { |
| 112 |
bookable++; |
| 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); |
| 120 |
} else { |
| 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); |
| 125 |
} |
| 126 |
} |
| 127 |
} |
| 128 |
|
| 129 |
// Redraw select with new options and enable |
| 130 |
$('#booking_item_id').trigger('change'); |
| 131 |
$("#booking_item_id").prop("disabled", false); |
| 132 |
|
| 133 |
// Set disabled dates in datepicker |
| 134 |
periodPicker.config.disable.push( function(date) { |
| 135 |
|
| 136 |
// set local copy of selectedDates |
| 137 |
let selectedDates = periodPicker.selectedDates; |
| 138 |
|
| 139 |
// set booked counter |
| 140 |
let booked = 0; |
| 141 |
|
| 142 |
// reset the unavailable items array |
| 143 |
let unavailable_items = []; |
| 144 |
|
| 145 |
// reset the biblio level bookings array |
| 146 |
let biblio_bookings = []; |
| 147 |
|
| 148 |
// disable dates before selected date |
| 149 |
if (selectedDates[0] && selectedDates[0] > date) { |
| 150 |
return true; |
| 151 |
} |
| 152 |
|
| 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); |
| 157 |
|
| 158 |
// patron has selected a start date (end date checks) |
| 159 |
if (selectedDates[0]) { |
| 160 |
|
| 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); |
| 166 |
} |
| 167 |
} else { |
| 168 |
if (biblio_bookings.indexOf(booking.booking_id) === -1) { |
| 169 |
biblio_bookings.push(booking.booking_id); |
| 170 |
} |
| 171 |
} |
| 172 |
} |
| 173 |
|
| 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); |
| 179 |
} |
| 180 |
} else { |
| 181 |
if (biblio_bookings.indexOf(booking.booking_id) === -1) { |
| 182 |
biblio_bookings.push(booking.booking_id); |
| 183 |
} |
| 184 |
} |
| 185 |
} |
| 186 |
|
| 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); |
| 192 |
} |
| 193 |
} else { |
| 194 |
if (biblio_bookings.indexOf(booking.booking_id) === -1) { |
| 195 |
biblio_bookings.push(booking.booking_id); |
| 196 |
} |
| 197 |
} |
| 198 |
} |
| 199 |
|
| 200 |
// new booking would not conflict |
| 201 |
else { |
| 202 |
continue; |
| 203 |
} |
| 204 |
|
| 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) { |
| 209 |
return true; |
| 210 |
} |
| 211 |
} |
| 212 |
|
| 213 |
// patron has not yet selected a start date (start date checks) |
| 214 |
else if (date <= end_date && date >= start_date) { |
| 215 |
|
| 216 |
// same item, disable date |
| 217 |
if (booking.item_id && booking.item_id === itemnumber) { |
| 218 |
return true; |
| 219 |
} |
| 220 |
|
| 221 |
// count all clashes, both item and biblio level |
| 222 |
booked++; |
| 223 |
if (booked == bookable) { |
| 224 |
return true; |
| 225 |
} |
| 226 |
} |
| 227 |
} |
| 228 |
}); |
| 229 |
|
| 230 |
// Enable flatpickr now we have date function populated |
| 231 |
periodPicker.redraw(); |
| 232 |
$("#period_fields :input").prop('disabled', false); |
| 233 |
|
| 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; |
| 237 |
|
| 238 |
// redraw pariodPicker taking selected item into account |
| 239 |
periodPicker.redraw(); |
| 240 |
}); |
| 241 |
|
| 242 |
// Set onClose for flatpickr |
| 243 |
periodPicker.config.onClose.push(function(selectedDates, dateStr, instance) { |
| 244 |
|
| 245 |
let startDate = new Date(selectedDates[0]); |
| 246 |
|
| 247 |
// set end datetime hours and minutes to the end of the day |
| 248 |
let endDate = new Date(selectedDates[1]); |
| 249 |
endDate.setHours(endDate.getHours()+23); |
| 250 |
endDate.setMinutes(endDate.getMinutes()+59); |
| 251 |
|
| 252 |
$('#booking_start_date').val(startDate.toISOString()); |
| 253 |
$('#booking_end_date').val(endDate.toISOString()); |
| 254 |
|
| 255 |
// set available items in select2 |
| 256 |
if ( selectedDates[0] && selectedDates[1] ) { |
| 257 |
var booked_items = bookings[0].filter(function(booking) { |
| 258 |
let start_date = flatpickr.parseDate(booking.start_date); |
| 259 |
let end_date = flatpickr.parseDate(booking.end_date); |
| 260 |
// This booking ends before the start of the new booking |
| 261 |
if ( end_date <= selectedDates[0] ) { |
| 262 |
return false; |
| 263 |
} |
| 264 |
// This booking starts after then end of the new booking |
| 265 |
if ( start_date >= selectedDates[1] ) { |
| 266 |
return false; |
| 267 |
} |
| 268 |
// This booking overlaps |
| 269 |
return true; |
| 270 |
}); |
| 271 |
$("#booking_item_id > option").each(function() { |
| 272 |
let option = $(this); |
| 273 |
if ( itemnumber && itemnumber == option.val() ) { |
| 274 |
console.log("itemnumber defined and equal to value"); |
| 275 |
} else if ( booked_items.some(function(booked_item){ |
| 276 |
return option.val() == booked_item.item_id; |
| 277 |
}) ) { |
| 278 |
option.prop('disabled',true); |
| 279 |
} else { |
| 280 |
option.prop('disabled',false); |
| 281 |
} |
| 282 |
}); |
| 283 |
} |
| 284 |
}); |
| 285 |
}, |
| 286 |
function(jqXHR, textStatus, errorThrown){ |
| 287 |
console.log("Fetch failed"); |
| 288 |
} |
| 289 |
); |
| 290 |
}); |
| 291 |
|
| 292 |
$("#placeBookingForm").on('submit', function(e) { |
| 293 |
e.preventDefault(); |
| 294 |
|
| 295 |
var url = '/api/v1/bookings'; |
| 296 |
|
| 297 |
var start_date = $('#booking_start_date').val(); |
| 298 |
var end_date = $('#booking_end_date').val(); |
| 299 |
var item_id = $('#booking_item_id').val(); |
| 300 |
|
| 301 |
var posting = $.post( |
| 302 |
url, |
| 303 |
JSON.stringify({ |
| 304 |
"start_date": start_date, |
| 305 |
"end_date": end_date, |
| 306 |
"biblio_id": $('#booking_biblio_id').val(), |
| 307 |
"item_id": item_id != 0 ? item_id : null, |
| 308 |
"patron_id": $('#booking_patron_id').find(':selected').val() |
| 309 |
}) |
| 310 |
); |
| 311 |
|
| 312 |
posting.done(function(data) { |
| 313 |
$('#placeBookingModal').modal('hide'); |
| 314 |
}); |
| 315 |
|
| 316 |
posting.fail(function(data) { |
| 317 |
$('#booking_result').replaceWith('<div id="booking_result" class="alert alert-danger">Failure</div>'); |
| 318 |
}); |
| 319 |
}); |