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 |
}); |