Lines 1-60
Link Here
|
1 |
let dataFetched = false; |
1 |
let dataFetched = false; |
2 |
let bookable_items, bookings, checkouts, booking_id, booking_item_id, booking_patron; |
2 |
let bookable_items, |
3 |
|
3 |
bookings, |
4 |
$('#placeBookingModal').on('show.bs.modal', function(e) { |
4 |
checkouts, |
5 |
|
5 |
booking_id, |
|
|
6 |
booking_item_id, |
7 |
booking_patron; |
8 |
|
9 |
$("#placeBookingModal").on("show.bs.modal", function (e) { |
6 |
// Get context |
10 |
// Get context |
7 |
let button = $(e.relatedTarget); |
11 |
let button = $(e.relatedTarget); |
8 |
let biblionumber = button.data('biblionumber'); |
12 |
let biblionumber = button.data("biblionumber"); |
9 |
$('#booking_biblio_id').val(biblionumber); |
13 |
$("#booking_biblio_id").val(biblionumber); |
10 |
|
14 |
|
11 |
let patron_id = button.data('patron') || 0; |
15 |
let patron_id = button.data("patron") || 0; |
12 |
booking_item_id = button.data('itemnumber'); |
16 |
booking_item_id = button.data("itemnumber"); |
13 |
let start_date = button.data('start_date'); |
17 |
let start_date = button.data("start_date"); |
14 |
let end_date = button.data('end_date'); |
18 |
let end_date = button.data("end_date"); |
15 |
|
19 |
|
16 |
// Get booking id if this is an edit |
20 |
// Get booking id if this is an edit |
17 |
booking_id = button.data('booking'); |
21 |
booking_id = button.data("booking"); |
18 |
if (booking_id) { |
22 |
if (booking_id) { |
19 |
$('#placeBookingLabel').html(__("Edit booking")); |
23 |
$("#placeBookingLabel").html(__("Edit booking")); |
20 |
$('#booking_id').val(booking_id); |
24 |
$("#booking_id").val(booking_id); |
21 |
} else { |
25 |
} else { |
22 |
$('#placeBookingLabel').html(__("Place booking")); |
26 |
$("#placeBookingLabel").html(__("Place booking")); |
23 |
// Ensure we don't accidentally update a booking |
27 |
// Ensure we don't accidentally update a booking |
24 |
$('#booking_id').val(''); |
28 |
$("#booking_id").val(""); |
25 |
} |
29 |
} |
26 |
|
30 |
|
27 |
// Patron select2 |
31 |
// Patron select2 |
28 |
$("#booking_patron_id").kohaSelect({ |
32 |
$("#booking_patron_id").kohaSelect({ |
29 |
dropdownParent: $(".modal-content", "#placeBookingModal"), |
33 |
dropdownParent: $(".modal-content", "#placeBookingModal"), |
30 |
width: '50%', |
34 |
width: "50%", |
31 |
dropdownAutoWidth: true, |
35 |
dropdownAutoWidth: true, |
32 |
allowClear: true, |
36 |
allowClear: true, |
33 |
minimumInputLength: 3, |
37 |
minimumInputLength: 3, |
34 |
ajax: { |
38 |
ajax: { |
35 |
url: '/api/v1/patrons', |
39 |
url: "/api/v1/patrons", |
36 |
delay: 250, |
40 |
delay: 250, |
37 |
dataType: 'json', |
41 |
dataType: "json", |
38 |
headers: { |
42 |
headers: { |
39 |
"x-koha-embed": "library" |
43 |
"x-koha-embed": "library", |
40 |
}, |
44 |
}, |
41 |
data: function(params) { |
45 |
data: function (params) { |
42 |
let q = buildPatronSearchQuery(params.term); |
46 |
let q = buildPatronSearchQuery(params.term); |
43 |
let query = { |
47 |
let query = { |
44 |
'q': JSON.stringify(q), |
48 |
q: JSON.stringify(q), |
45 |
'_page': params.page, |
49 |
_page: params.page, |
46 |
'_order_by': '+me.surname,+me.firstname', |
50 |
_order_by: "+me.surname,+me.firstname", |
47 |
}; |
51 |
}; |
48 |
return query; |
52 |
return query; |
49 |
}, |
53 |
}, |
50 |
processResults: function(data, params) { |
54 |
processResults: function (data, params) { |
51 |
let results = []; |
55 |
let results = []; |
52 |
data.results.forEach(function(patron) { |
56 |
data.results.forEach(function (patron) { |
53 |
patron.id = patron.patron_id; |
57 |
patron.id = patron.patron_id; |
54 |
results.push(patron); |
58 |
results.push(patron); |
55 |
}); |
59 |
}); |
56 |
return { |
60 |
return { |
57 |
"results": results, "pagination": { "more": data.pagination.more } |
61 |
results: results, |
|
|
62 |
pagination: { more: data.pagination.more }, |
58 |
}; |
63 |
}; |
59 |
}, |
64 |
}, |
60 |
}, |
65 |
}, |
Lines 85-96
$('#placeBookingModal').on('show.bs.modal', function(e) {
Link Here
|
85 |
__("years") + |
90 |
__("years") + |
86 |
"</span>" |
91 |
"</span>" |
87 |
: "") + |
92 |
: "") + |
88 |
(patron.library ? |
93 |
(patron.library |
89 |
" <span class=\"ac-library\">" + |
94 |
? ' <span class="ac-library">' + |
90 |
escape_str(patron.library.name) + |
95 |
escape_str(patron.library.name) + |
91 |
"</span>" |
96 |
"</span>" |
92 |
: "") + |
97 |
: "") + |
93 |
"</small>" |
98 |
"</small>", |
94 |
) |
99 |
) |
95 |
.addClass(loggedInClass); |
100 |
.addClass(loggedInClass); |
96 |
return $patron; |
101 |
return $patron; |
Lines 99-145
$('#placeBookingModal').on('show.bs.modal', function(e) {
Link Here
|
99 |
if (!patron.surname) { |
104 |
if (!patron.surname) { |
100 |
return patron.text; |
105 |
return patron.text; |
101 |
} |
106 |
} |
102 |
return ( |
107 |
return patron.surname + ", " + patron.firstname; |
103 |
patron.surname + ", " + patron.firstname |
|
|
104 |
); |
105 |
}, |
108 |
}, |
106 |
placeholder: __("Search for a patron") |
109 |
placeholder: __("Search for a patron"), |
107 |
}); |
110 |
}); |
108 |
|
111 |
|
109 |
$('#booking_patron_id').on('select2:select', function (e) { |
112 |
$("#booking_patron_id").on("select2:select", function (e) { |
110 |
booking_patron = e.params.data; |
113 |
booking_patron = e.params.data; |
111 |
}); |
114 |
}); |
112 |
|
115 |
|
113 |
// Adopt periodPicker |
116 |
// Adopt periodPicker |
114 |
let periodPicker = $("#period").get(0)._flatpickr; |
117 |
let periodPicker = $("#period").get(0)._flatpickr; |
115 |
|
118 |
|
116 |
if ( !dataFetched ) { |
119 |
if (!dataFetched) { |
117 |
|
|
|
118 |
// Fetch list of bookable items |
120 |
// Fetch list of bookable items |
119 |
let itemsFetch = $.ajax({ |
121 |
let itemsFetch = $.ajax({ |
120 |
url: '/api/v1/biblios/' + biblionumber + '/items?bookable=1' + '&_per_page=-1', |
122 |
url: |
121 |
dataType: 'json', |
123 |
"/api/v1/biblios/" + |
122 |
type: 'GET' |
124 |
biblionumber + |
|
|
125 |
"/items?bookable=1" + |
126 |
"&_per_page=-1", |
127 |
dataType: "json", |
128 |
type: "GET", |
123 |
}); |
129 |
}); |
124 |
|
130 |
|
125 |
// Fetch list of existing bookings |
131 |
// Fetch list of existing bookings |
126 |
let bookingsFetch = $.ajax({ |
132 |
let bookingsFetch = $.ajax({ |
127 |
url: '/api/v1/bookings?biblio_id=' + biblionumber + '&_per_page=-1', |
133 |
url: "/api/v1/bookings?biblio_id=" + biblionumber + "&_per_page=-1", |
128 |
dataType: 'json', |
134 |
dataType: "json", |
129 |
type: 'GET' |
135 |
type: "GET", |
130 |
}); |
136 |
}); |
131 |
|
137 |
|
132 |
// Fetch list of current checkouts |
138 |
// Fetch list of current checkouts |
133 |
let checkoutsFetch = $.ajax({ |
139 |
let checkoutsFetch = $.ajax({ |
134 |
url: '/api/v1/biblios/' + biblionumber + '/checkouts?_per_page=-1', |
140 |
url: "/api/v1/biblios/" + biblionumber + "/checkouts?_per_page=-1", |
135 |
dataType: 'json', |
141 |
dataType: "json", |
136 |
type: 'GET' |
142 |
type: "GET", |
137 |
}); |
143 |
}); |
138 |
|
144 |
|
139 |
// Update item select2 and period flatpickr |
145 |
// Update item select2 and period flatpickr |
140 |
$.when(itemsFetch, bookingsFetch, checkoutsFetch).then( |
146 |
$.when(itemsFetch, bookingsFetch, checkoutsFetch).then( |
141 |
function(itemsFetch,bookingsFetch, checkoutsFetch){ |
147 |
function (itemsFetch, bookingsFetch, checkoutsFetch) { |
142 |
|
|
|
143 |
// Set variables |
148 |
// Set variables |
144 |
bookable_items = itemsFetch[0]; |
149 |
bookable_items = itemsFetch[0]; |
145 |
bookings = bookingsFetch[0]; |
150 |
bookings = bookingsFetch[0]; |
Lines 161-174
$('#placeBookingModal').on('show.bs.modal', function(e) {
Link Here
|
161 |
// Item select2 |
166 |
// Item select2 |
162 |
$("#booking_item_id").select2({ |
167 |
$("#booking_item_id").select2({ |
163 |
dropdownParent: $(".modal-content", "#placeBookingModal"), |
168 |
dropdownParent: $(".modal-content", "#placeBookingModal"), |
164 |
width: '50%', |
169 |
width: "50%", |
165 |
dropdownAutoWidth: true, |
170 |
dropdownAutoWidth: true, |
166 |
minimumResultsForSearch: 20, |
171 |
minimumResultsForSearch: 20, |
167 |
placeholder: __("Select item") |
172 |
placeholder: __("Select item"), |
168 |
}); |
173 |
}); |
169 |
|
174 |
|
170 |
// Update flatpickr mode |
175 |
// Update flatpickr mode |
171 |
periodPicker.set('mode', 'range'); |
176 |
periodPicker.set("mode", "range"); |
172 |
|
177 |
|
173 |
// Total bookable items |
178 |
// Total bookable items |
174 |
let bookable = 0; |
179 |
let bookable = 0; |
Lines 176-474
$('#placeBookingModal').on('show.bs.modal', function(e) {
Link Here
|
176 |
for (item of bookable_items) { |
181 |
for (item of bookable_items) { |
177 |
bookable++; |
182 |
bookable++; |
178 |
// Populate item select (NOTE: Do we still need this check for pre-existing select option here?) |
183 |
// Populate item select (NOTE: Do we still need this check for pre-existing select option here?) |
179 |
if (!($('#booking_item_id').find("option[value='" + item.item_id + "']").length)) { |
184 |
if ( |
|
|
185 |
!$("#booking_item_id").find( |
186 |
"option[value='" + item.item_id + "']", |
187 |
).length |
188 |
) { |
180 |
// Create a DOM Option and de-select by default |
189 |
// Create a DOM Option and de-select by default |
181 |
let newOption = new Option(escape_str(item.external_id), item.item_id, false, false); |
190 |
let newOption = new Option( |
|
|
191 |
escape_str(item.external_id), |
192 |
item.item_id, |
193 |
false, |
194 |
false, |
195 |
); |
182 |
// Append it to the select |
196 |
// Append it to the select |
183 |
$('#booking_item_id').append(newOption); |
197 |
$("#booking_item_id").append(newOption); |
184 |
} |
198 |
} |
185 |
} |
199 |
} |
186 |
|
200 |
|
187 |
// Set disable function for periodPicker |
201 |
// Set disable function for periodPicker |
188 |
let disableExists = periodPicker.config.disable.filter(f => f.name === 'dateDisable'); |
202 |
let disableExists = periodPicker.config.disable.filter( |
189 |
if ( disableExists.length === 0 ) { |
203 |
(f) => f.name === "dateDisable", |
190 |
periodPicker.config.disable.push(function dateDisable(date){ |
204 |
); |
191 |
|
205 |
if (disableExists.length === 0) { |
192 |
// set local copy of selectedDates |
206 |
periodPicker.config.disable.push( |
193 |
let selectedDates = periodPicker.selectedDates; |
207 |
function dateDisable(date) { |
194 |
|
208 |
// set local copy of selectedDates |
195 |
// set booked counter |
209 |
let selectedDates = periodPicker.selectedDates; |
196 |
let booked = 0; |
210 |
|
197 |
|
211 |
// set booked counter |
198 |
// reset the unavailable items array |
212 |
let booked = 0; |
199 |
let unavailable_items = []; |
213 |
|
200 |
|
214 |
// reset the unavailable items array |
201 |
// reset the biblio level bookings array |
215 |
let unavailable_items = []; |
202 |
let biblio_bookings = []; |
216 |
|
203 |
|
217 |
// reset the biblio level bookings array |
204 |
// disable dates before selected date |
218 |
let biblio_bookings = []; |
205 |
if (!selectedDates[1] && (selectedDates[0] && selectedDates[0] > date)) { |
219 |
|
206 |
return true; |
220 |
// disable dates before selected date |
207 |
} |
221 |
if ( |
208 |
|
222 |
!selectedDates[1] && |
209 |
// iterate existing bookings |
223 |
selectedDates[0] && |
210 |
for (booking of bookings) { |
224 |
selectedDates[0] > date |
211 |
|
225 |
) { |
212 |
// Skip if we're editing this booking |
226 |
return true; |
213 |
if (booking_id && booking_id == booking.booking_id){ |
|
|
214 |
continue; |
215 |
} |
227 |
} |
216 |
|
228 |
|
217 |
let start_date = flatpickr.parseDate(booking.start_date); |
229 |
// iterate existing bookings |
218 |
let end_date = flatpickr.parseDate(booking.end_date); |
230 |
for (booking of bookings) { |
219 |
|
231 |
// Skip if we're editing this booking |
220 |
// patron has selected a start date (end date checks) |
232 |
if ( |
221 |
if (selectedDates[0]) { |
233 |
booking_id && |
|
|
234 |
booking_id == booking.booking_id |
235 |
) { |
236 |
continue; |
237 |
} |
222 |
|
238 |
|
223 |
// new booking start date is between existing booking start and end dates |
239 |
let start_date = flatpickr.parseDate( |
224 |
if (selectedDates[0] >= start_date && selectedDates[0] <= end_date) { |
240 |
booking.start_date, |
225 |
if (booking.item_id) { |
241 |
); |
226 |
if (unavailable_items.indexOf(booking.item_id) === -1) { |
242 |
let end_date = flatpickr.parseDate( |
227 |
unavailable_items.push(booking.item_id); |
243 |
booking.end_date, |
228 |
} |
244 |
); |
229 |
} else { |
245 |
|
230 |
if (biblio_bookings.indexOf(booking.booking_id) === -1) { |
246 |
// patron has selected a start date (end date checks) |
231 |
biblio_bookings.push(booking.booking_id); |
247 |
if (selectedDates[0]) { |
|
|
248 |
// new booking start date is between existing booking start and end dates |
249 |
if ( |
250 |
selectedDates[0] >= start_date && |
251 |
selectedDates[0] <= end_date |
252 |
) { |
253 |
if (booking.item_id) { |
254 |
if ( |
255 |
unavailable_items.indexOf( |
256 |
booking.item_id, |
257 |
) === -1 |
258 |
) { |
259 |
unavailable_items.push( |
260 |
booking.item_id, |
261 |
); |
262 |
} |
263 |
} else { |
264 |
if ( |
265 |
biblio_bookings.indexOf( |
266 |
booking.booking_id, |
267 |
) === -1 |
268 |
) { |
269 |
biblio_bookings.push( |
270 |
booking.booking_id, |
271 |
); |
272 |
} |
232 |
} |
273 |
} |
233 |
} |
274 |
} |
234 |
} |
|
|
235 |
|
275 |
|
236 |
// new booking end date would be between existing booking start and end dates |
276 |
// new booking end date would be between existing booking start and end dates |
237 |
else if (date >= start_date && date <= end_date) { |
277 |
else if ( |
238 |
if (booking.item_id) { |
278 |
date >= start_date && |
239 |
if (unavailable_items.indexOf(booking.item_id) === -1) { |
279 |
date <= end_date |
240 |
unavailable_items.push(booking.item_id); |
280 |
) { |
241 |
} |
281 |
if (booking.item_id) { |
242 |
} else { |
282 |
if ( |
243 |
if (biblio_bookings.indexOf(booking.booking_id) === -1) { |
283 |
unavailable_items.indexOf( |
244 |
biblio_bookings.push(booking.booking_id); |
284 |
booking.item_id, |
|
|
285 |
) === -1 |
286 |
) { |
287 |
unavailable_items.push( |
288 |
booking.item_id, |
289 |
); |
290 |
} |
291 |
} else { |
292 |
if ( |
293 |
biblio_bookings.indexOf( |
294 |
booking.booking_id, |
295 |
) === -1 |
296 |
) { |
297 |
biblio_bookings.push( |
298 |
booking.booking_id, |
299 |
); |
300 |
} |
245 |
} |
301 |
} |
246 |
} |
302 |
} |
247 |
} |
|
|
248 |
|
303 |
|
249 |
// new booking would span existing booking |
304 |
// new booking would span existing booking |
250 |
else if (selectedDates[0] <= start_date && date >= end_date) { |
305 |
else if ( |
251 |
if (booking.item_id) { |
306 |
selectedDates[0] <= start_date && |
252 |
if (unavailable_items.indexOf(booking.item_id) === -1) { |
307 |
date >= end_date |
253 |
unavailable_items.push(booking.item_id); |
308 |
) { |
254 |
} |
309 |
if (booking.item_id) { |
255 |
} else { |
310 |
if ( |
256 |
if (biblio_bookings.indexOf(booking.booking_id) === -1) { |
311 |
unavailable_items.indexOf( |
257 |
biblio_bookings.push(booking.booking_id); |
312 |
booking.item_id, |
|
|
313 |
) === -1 |
314 |
) { |
315 |
unavailable_items.push( |
316 |
booking.item_id, |
317 |
); |
318 |
} |
319 |
} else { |
320 |
if ( |
321 |
biblio_bookings.indexOf( |
322 |
booking.booking_id, |
323 |
) === -1 |
324 |
) { |
325 |
biblio_bookings.push( |
326 |
booking.booking_id, |
327 |
); |
328 |
} |
258 |
} |
329 |
} |
259 |
} |
330 |
} |
260 |
} |
|
|
261 |
|
331 |
|
262 |
// new booking would not conflict |
332 |
// new booking would not conflict |
263 |
else { |
333 |
else { |
264 |
continue; |
334 |
continue; |
265 |
} |
335 |
} |
266 |
|
336 |
|
267 |
// check that there are available items |
337 |
// check that there are available items |
268 |
// available = all bookable items - booked items - booked biblios |
338 |
// available = all bookable items - booked items - booked biblios |
269 |
let total_available = bookable_items.length - unavailable_items.length - biblio_bookings.length; |
339 |
let total_available = |
270 |
if (total_available === 0) { |
340 |
bookable_items.length - |
271 |
return true; |
341 |
unavailable_items.length - |
|
|
342 |
biblio_bookings.length; |
343 |
if (total_available === 0) { |
344 |
return true; |
345 |
} |
272 |
} |
346 |
} |
273 |
} |
|
|
274 |
|
347 |
|
275 |
// patron has not yet selected a start date (start date checks) |
348 |
// patron has not yet selected a start date (start date checks) |
276 |
else if (date <= end_date && date >= start_date) { |
349 |
else if ( |
|
|
350 |
date <= end_date && |
351 |
date >= start_date |
352 |
) { |
353 |
// same item, disable date |
354 |
if ( |
355 |
booking.item_id && |
356 |
booking.item_id == booking_item_id |
357 |
) { |
358 |
return true; |
359 |
} |
277 |
|
360 |
|
278 |
// same item, disable date |
361 |
// count all clashes, both item and biblio level |
279 |
if (booking.item_id && booking.item_id == booking_item_id) { |
362 |
booked++; |
280 |
return true; |
363 |
if (booked == bookable) { |
281 |
} |
364 |
return true; |
|
|
365 |
} |
282 |
|
366 |
|
283 |
// count all clashes, both item and biblio level |
367 |
// FIXME: The above is not intelligent enough to spot |
284 |
booked++; |
368 |
// cases where an item must be used for a biblio level booking |
285 |
if (booked == bookable) { |
369 |
// due to all other items being booking within the biblio level |
286 |
return true; |
370 |
// booking period... we end up with a clash |
|
|
371 |
// To reproduce: |
372 |
// * One bib with two bookable items. |
373 |
// * Add item level booking |
374 |
// * Add biblio level booking that extends one day beyond the item level booking |
375 |
// * Try to book the item without an item level booking from the day before the biblio level |
376 |
// booking is to be returned. Note this is a clash, the only item available for the biblio |
377 |
// level booking is the item you just booked out overlapping the end date. |
287 |
} |
378 |
} |
288 |
|
|
|
289 |
// FIXME: The above is not intelligent enough to spot |
290 |
// cases where an item must be used for a biblio level booking |
291 |
// due to all other items being booking within the biblio level |
292 |
// booking period... we end up with a clash |
293 |
// To reproduce: |
294 |
// * One bib with two bookable items. |
295 |
// * Add item level booking |
296 |
// * Add biblio level booking that extends one day beyond the item level booking |
297 |
// * Try to book the item without an item level booking from the day before the biblio level |
298 |
// booking is to be returned. Note this is a clash, the only item available for the biblio |
299 |
// level booking is the item you just booked out overlapping the end date. |
300 |
} |
379 |
} |
301 |
} |
380 |
}, |
302 |
}); |
381 |
); |
303 |
}; |
382 |
} |
304 |
|
383 |
|
305 |
// Setup listener for item select2 |
384 |
// Setup listener for item select2 |
306 |
$('#booking_item_id').on('select2:select', function(e) { |
385 |
$("#booking_item_id").on("select2:select", function (e) { |
307 |
booking_item_id = e.params.data.id ? e.params.data.id : null; |
386 |
booking_item_id = e.params.data.id |
|
|
387 |
? e.params.data.id |
388 |
: null; |
308 |
|
389 |
|
309 |
// redraw pariodPicker taking selected item into account |
390 |
// redraw pariodPicker taking selected item into account |
310 |
periodPicker.redraw(); |
391 |
periodPicker.redraw(); |
311 |
}); |
392 |
}); |
312 |
|
393 |
|
313 |
// Set onChange for flatpickr |
394 |
// Set onChange for flatpickr |
314 |
let changeExists = periodPicker.config.onChange.filter(f => f.name ==='periodChange'); |
395 |
let changeExists = periodPicker.config.onChange.filter( |
315 |
if(changeExists.length === 0) { |
396 |
(f) => f.name === "periodChange", |
316 |
periodPicker.config.onChange.push(function periodChange(selectedDates, dateStr, instance) { |
397 |
); |
317 |
// Range set, update hidden fields and set available items |
398 |
if (changeExists.length === 0) { |
318 |
if ( selectedDates[0] && selectedDates[1] ) { |
399 |
periodPicker.config.onChange.push( |
319 |
// set form fields from picker |
400 |
function periodChange( |
320 |
let picker_start = dayjs(selectedDates[0]); |
401 |
selectedDates, |
321 |
let picker_end = dayjs(selectedDates[1]).endOf('day'); |
402 |
dateStr, |
322 |
$('#booking_start_date').val(picker_start.toISOString()); |
403 |
instance, |
323 |
$('#booking_end_date').val(picker_end.toISOString()); |
404 |
) { |
324 |
|
405 |
// Range set, update hidden fields and set available items |
325 |
// set available items in select2 |
406 |
if (selectedDates[0] && selectedDates[1]) { |
326 |
let booked_items = bookings.filter(function(booking) { |
407 |
// set form fields from picker |
327 |
let start_date = flatpickr.parseDate(booking.start_date); |
408 |
let picker_start = dayjs(selectedDates[0]); |
328 |
let end_date = flatpickr.parseDate(booking.end_date); |
409 |
let picker_end = dayjs(selectedDates[1]).endOf( |
329 |
// This booking ends before the start of the new booking |
410 |
"day", |
330 |
if ( end_date <= selectedDates[0] ) { |
411 |
); |
331 |
return false; |
412 |
$("#booking_start_date").val( |
332 |
} |
413 |
picker_start.toISOString(), |
333 |
// This booking starts after then end of the new booking |
414 |
); |
334 |
if ( start_date >= selectedDates[1] ) { |
415 |
$("#booking_end_date").val( |
335 |
return false; |
416 |
picker_end.toISOString(), |
336 |
} |
417 |
); |
337 |
// This booking overlaps |
418 |
|
338 |
return true; |
419 |
// set available items in select2 |
339 |
}); |
420 |
let booked_items = bookings.filter( |
340 |
$("#booking_item_id > option").each(function() { |
421 |
function (booking) { |
341 |
let option = $(this); |
422 |
let start_date = flatpickr.parseDate( |
342 |
if ( booking_item_id && booking_item_id == option.val() ) { |
423 |
booking.start_date, |
343 |
option.prop('disabled',false); |
424 |
); |
344 |
} else if ( booked_items.some(function(booked_item){ |
425 |
let end_date = flatpickr.parseDate( |
345 |
return option.val() == booked_item.item_id; |
426 |
booking.end_date, |
346 |
}) ) { |
427 |
); |
347 |
option.prop('disabled',true); |
428 |
// This booking ends before the start of the new booking |
348 |
} else { |
429 |
if (end_date <= selectedDates[0]) { |
349 |
option.prop('disabled',false); |
430 |
return false; |
350 |
} |
431 |
} |
351 |
}); |
432 |
// This booking starts after then end of the new booking |
352 |
$('#booking_item_id').trigger('change.select2'); |
433 |
if (start_date >= selectedDates[1]) { |
353 |
} |
434 |
return false; |
354 |
// Range not set, reset field options |
435 |
} |
355 |
else { |
436 |
// This booking overlaps |
356 |
$('#booking_item_id > option').each(function() { |
437 |
return true; |
357 |
$(this).prop('disabled', false); |
438 |
}, |
358 |
}); |
439 |
); |
359 |
$('#booking_item_id').trigger('change.select2'); |
440 |
$("#booking_item_id > option").each( |
360 |
} |
441 |
function () { |
361 |
}); |
442 |
let option = $(this); |
362 |
}; |
443 |
if ( |
|
|
444 |
booking_item_id && |
445 |
booking_item_id == option.val() |
446 |
) { |
447 |
option.prop("disabled", false); |
448 |
} else if ( |
449 |
booked_items.some( |
450 |
function (booked_item) { |
451 |
return ( |
452 |
option.val() == |
453 |
booked_item.item_id |
454 |
); |
455 |
}, |
456 |
) |
457 |
) { |
458 |
option.prop("disabled", true); |
459 |
} else { |
460 |
option.prop("disabled", false); |
461 |
} |
462 |
}, |
463 |
); |
464 |
$("#booking_item_id").trigger("change.select2"); |
465 |
} |
466 |
// Range not set, reset field options |
467 |
else { |
468 |
$("#booking_item_id > option").each( |
469 |
function () { |
470 |
$(this).prop("disabled", false); |
471 |
}, |
472 |
); |
473 |
$("#booking_item_id").trigger("change.select2"); |
474 |
} |
475 |
}, |
476 |
); |
477 |
} |
363 |
|
478 |
|
364 |
// Enable flatpickr now we have date function populated |
479 |
// Enable flatpickr now we have date function populated |
365 |
periodPicker.redraw(); |
480 |
periodPicker.redraw(); |
366 |
$("#period_fields :input").prop('disabled', false); |
481 |
$("#period_fields :input").prop("disabled", false); |
367 |
|
482 |
|
368 |
// Redraw select with new options and enable |
483 |
// Redraw select with new options and enable |
369 |
$('#booking_item_id').trigger('change'); |
484 |
$("#booking_item_id").trigger("change"); |
370 |
$("#booking_item_id").prop("disabled", false); |
485 |
$("#booking_item_id").prop("disabled", false); |
371 |
|
486 |
|
372 |
// Set the flag to indicate that data has been fetched |
487 |
// Set the flag to indicate that data has been fetched |
373 |
dataFetched = true; |
488 |
dataFetched = true; |
374 |
|
489 |
|
375 |
// Set form values |
490 |
// Set form values |
376 |
setFormValues(patron_id,booking_item_id,start_date,end_date,periodPicker); |
491 |
setFormValues( |
|
|
492 |
patron_id, |
493 |
booking_item_id, |
494 |
start_date, |
495 |
end_date, |
496 |
periodPicker, |
497 |
); |
377 |
}, |
498 |
}, |
378 |
function(jqXHR, textStatus, errorThrown){ |
499 |
function (jqXHR, textStatus, errorThrown) { |
379 |
console.log("Fetch failed"); |
500 |
console.log("Fetch failed"); |
380 |
} |
501 |
}, |
381 |
); |
502 |
); |
382 |
} else { |
503 |
} else { |
383 |
setFormValues(patron_id,booking_item_id,start_date,end_date,periodPicker); |
504 |
setFormValues( |
384 |
}; |
505 |
patron_id, |
|
|
506 |
booking_item_id, |
507 |
start_date, |
508 |
end_date, |
509 |
periodPicker, |
510 |
); |
511 |
} |
385 |
}); |
512 |
}); |
386 |
|
513 |
|
387 |
function setFormValues(patron_id,booking_item_id,start_date,end_date,periodPicker){ |
514 |
function setFormValues( |
388 |
|
515 |
patron_id, |
|
|
516 |
booking_item_id, |
517 |
start_date, |
518 |
end_date, |
519 |
periodPicker, |
520 |
) { |
389 |
// If passed patron, pre-select |
521 |
// If passed patron, pre-select |
390 |
if (patron_id) { |
522 |
if (patron_id) { |
391 |
let patronSelect = $('#booking_patron_id'); |
523 |
let patronSelect = $("#booking_patron_id"); |
392 |
let patron = $.ajax({ |
524 |
let patron = $.ajax({ |
393 |
url: '/api/v1/patrons/' + patron_id, |
525 |
url: "/api/v1/patrons/" + patron_id, |
394 |
dataType: 'json', |
526 |
dataType: "json", |
395 |
type: 'GET' |
527 |
type: "GET", |
396 |
}); |
528 |
}); |
397 |
|
529 |
|
398 |
$.when(patron).done( |
530 |
$.when(patron).done(function (patron) { |
399 |
function(patron){ |
531 |
// clone patron_id to id (select2 expects an id field) |
400 |
|
532 |
patron.id = patron.patron_id; |
401 |
// clone patron_id to id (select2 expects an id field) |
533 |
patron.text = |
402 |
patron.id = patron.patron_id; |
534 |
escape_str(patron.surname) + |
403 |
patron.text = escape_str(patron.surname) + ", " + escape_str(patron.firstname); |
535 |
", " + |
404 |
|
536 |
escape_str(patron.firstname); |
405 |
// Add and select new option |
537 |
|
406 |
let newOption = new Option(patron.text, patron.id, true, true); |
538 |
// Add and select new option |
407 |
patronSelect.append(newOption).trigger('change'); |
539 |
let newOption = new Option(patron.text, patron.id, true, true); |
408 |
|
540 |
patronSelect.append(newOption).trigger("change"); |
409 |
// manually trigger the `select2:select` event |
541 |
|
410 |
patronSelect.trigger({ |
542 |
// manually trigger the `select2:select` event |
411 |
type: 'select2:select', |
543 |
patronSelect.trigger({ |
412 |
params: { |
544 |
type: "select2:select", |
413 |
data: patron |
545 |
params: { |
414 |
} |
546 |
data: patron, |
415 |
}); |
547 |
}, |
416 |
} |
548 |
}); |
417 |
); |
549 |
}); |
418 |
} |
550 |
} |
419 |
|
551 |
|
420 |
// Set booking start & end if this is an edit |
552 |
// Set booking start & end if this is an edit |
421 |
if ( start_date ) { |
553 |
if (start_date) { |
422 |
// Allow invalid pre-load so setDate can set date range |
554 |
// Allow invalid pre-load so setDate can set date range |
423 |
// periodPicker.set('allowInvalidPreload', true); |
555 |
// periodPicker.set('allowInvalidPreload', true); |
424 |
// FIXME: Why is this the case.. we're passing two valid Date objects |
556 |
// FIXME: Why is this the case.. we're passing two valid Date objects |
425 |
let start = new Date(start_date); |
557 |
let start = new Date(start_date); |
426 |
let end = new Date(end_date); |
558 |
let end = new Date(end_date); |
427 |
|
559 |
|
428 |
let dates = [ new Date(start_date), new Date(end_date) ]; |
560 |
let dates = [new Date(start_date), new Date(end_date)]; |
429 |
periodPicker.setDate(dates, true); |
561 |
periodPicker.setDate(dates, true); |
430 |
} |
562 |
} |
431 |
// Reset periodPicker, biblio_id may have been nulled |
563 |
// Reset periodPicker, biblio_id may have been nulled |
432 |
else { |
564 |
else { |
433 |
periodPicker.redraw(); |
565 |
periodPicker.redraw(); |
434 |
}; |
566 |
} |
435 |
|
567 |
|
436 |
// If passed an itemnumber, pre-select |
568 |
// If passed an itemnumber, pre-select |
437 |
if (booking_item_id) { |
569 |
if (booking_item_id) { |
438 |
$('#booking_item_id').val(booking_item_id).trigger('change'); |
570 |
$("#booking_item_id").val(booking_item_id).trigger("change"); |
439 |
} |
571 |
} |
440 |
} |
572 |
} |
441 |
|
573 |
|
442 |
$("#placeBookingForm").on('submit', function(e) { |
574 |
$("#placeBookingForm").on("submit", function (e) { |
443 |
e.preventDefault(); |
575 |
e.preventDefault(); |
444 |
|
576 |
|
445 |
let url = '/api/v1/bookings'; |
577 |
let url = "/api/v1/bookings"; |
446 |
|
578 |
|
447 |
let start_date = $('#booking_start_date').val(); |
579 |
let start_date = $("#booking_start_date").val(); |
448 |
let end_date = $('#booking_end_date').val(); |
580 |
let end_date = $("#booking_end_date").val(); |
449 |
let item_id = $('#booking_item_id').val(); |
581 |
let item_id = $("#booking_item_id").val(); |
450 |
|
582 |
|
451 |
if (!booking_id) { |
583 |
if (!booking_id) { |
452 |
let posting = $.post( |
584 |
let posting = $.post( |
453 |
url, |
585 |
url, |
454 |
JSON.stringify({ |
586 |
JSON.stringify({ |
455 |
"start_date": start_date, |
587 |
start_date: start_date, |
456 |
"end_date": end_date, |
588 |
end_date: end_date, |
457 |
"biblio_id": $('#booking_biblio_id').val(), |
589 |
biblio_id: $("#booking_biblio_id").val(), |
458 |
"item_id": item_id != 0 ? item_id : null, |
590 |
item_id: item_id != 0 ? item_id : null, |
459 |
"patron_id": $('#booking_patron_id').find(':selected').val() |
591 |
patron_id: $("#booking_patron_id").find(":selected").val(), |
460 |
}) |
592 |
}), |
461 |
); |
593 |
); |
462 |
|
594 |
|
463 |
posting.done(function(data) { |
595 |
posting.done(function (data) { |
464 |
// Update bookings store for subsequent bookings |
596 |
// Update bookings store for subsequent bookings |
465 |
bookings.push(data); |
597 |
bookings.push(data); |
466 |
|
598 |
|
467 |
// Update bookings page as required |
599 |
// Update bookings page as required |
468 |
if (typeof bookings_table !== 'undefined' && bookings_table !== null) { |
600 |
if ( |
|
|
601 |
typeof bookings_table !== "undefined" && |
602 |
bookings_table !== null |
603 |
) { |
469 |
bookings_table.api().ajax.reload(); |
604 |
bookings_table.api().ajax.reload(); |
470 |
} |
605 |
} |
471 |
if (typeof timeline !== 'undefined' && timeline !== null) { |
606 |
if (typeof timeline !== "undefined" && timeline !== null) { |
472 |
timeline.itemsData.add({ |
607 |
timeline.itemsData.add({ |
473 |
id: data.booking_id, |
608 |
id: data.booking_id, |
474 |
booking: data.booking_id, |
609 |
booking: data.booking_id, |
Lines 477-528
$("#placeBookingForm").on('submit', function(e) {
Link Here
|
477 |
end: dayjs(data.end_date).toDate(), |
612 |
end: dayjs(data.end_date).toDate(), |
478 |
content: $patron_to_html(booking_patron, { |
613 |
content: $patron_to_html(booking_patron, { |
479 |
display_cardnumber: true, |
614 |
display_cardnumber: true, |
480 |
url: false |
615 |
url: false, |
481 |
}), |
616 |
}), |
482 |
editable: { remove: true, updateTime: true }, |
617 |
editable: { remove: true, updateTime: true }, |
483 |
type: 'range', |
618 |
type: "range", |
484 |
group: data.item_id ? data.item_id : 0 |
619 |
group: data.item_id ? data.item_id : 0, |
485 |
}); |
620 |
}); |
486 |
timeline.focus(data.booking_id); |
621 |
timeline.focus(data.booking_id); |
487 |
} |
622 |
} |
488 |
|
623 |
|
489 |
// Update bookings counts |
624 |
// Update bookings counts |
490 |
$('.bookings_count').html(parseInt($('.bookings_count').html(), 10)+1); |
625 |
$(".bookings_count").html( |
|
|
626 |
parseInt($(".bookings_count").html(), 10) + 1, |
627 |
); |
491 |
|
628 |
|
492 |
// Close modal |
629 |
// Close modal |
493 |
$('#placeBookingModal').modal('hide'); |
630 |
$("#placeBookingModal").modal("hide"); |
494 |
}); |
631 |
}); |
495 |
|
632 |
|
496 |
posting.fail(function(data) { |
633 |
posting.fail(function (data) { |
497 |
$('#booking_result').replaceWith('<div id="booking_result" class="alert alert-danger">'+_("Failure")+'</div>'); |
634 |
$("#booking_result").replaceWith( |
|
|
635 |
'<div id="booking_result" class="alert alert-danger">' + |
636 |
_("Failure") + |
637 |
"</div>", |
638 |
); |
498 |
}); |
639 |
}); |
499 |
} else { |
640 |
} else { |
500 |
url += '/' + booking_id; |
641 |
url += "/" + booking_id; |
501 |
let putting = $.ajax({ |
642 |
let putting = $.ajax({ |
502 |
'method': 'PUT', |
643 |
method: "PUT", |
503 |
'url': url, |
644 |
url: url, |
504 |
'data': JSON.stringify({ |
645 |
data: JSON.stringify({ |
505 |
"booking_id": booking_id, |
646 |
booking_id: booking_id, |
506 |
"start_date": start_date, |
647 |
start_date: start_date, |
507 |
"end_date": end_date, |
648 |
end_date: end_date, |
508 |
"biblio_id": $('#booking_biblio_id').val(), |
649 |
biblio_id: $("#booking_biblio_id").val(), |
509 |
"item_id": item_id != 0 ? item_id : null, |
650 |
item_id: item_id != 0 ? item_id : null, |
510 |
"patron_id": $('#booking_patron_id').find(':selected').val() |
651 |
patron_id: $("#booking_patron_id").find(":selected").val(), |
511 |
}) |
652 |
}), |
512 |
}); |
653 |
}); |
513 |
|
654 |
|
514 |
putting.done(function(data) { |
655 |
putting.done(function (data) { |
515 |
update_success = 1; |
656 |
update_success = 1; |
516 |
|
657 |
|
517 |
// Update bookings store for subsequent bookings |
658 |
// Update bookings store for subsequent bookings |
518 |
let target = bookings.find((obj) => obj.booking_id === data.booking_id); |
659 |
let target = bookings.find( |
519 |
Object.assign(target,data); |
660 |
(obj) => obj.booking_id === data.booking_id, |
|
|
661 |
); |
662 |
Object.assign(target, data); |
520 |
|
663 |
|
521 |
// Update bookings page as required |
664 |
// Update bookings page as required |
522 |
if (typeof bookings_table !== 'undefined' && bookings_table !== null) { |
665 |
if ( |
|
|
666 |
typeof bookings_table !== "undefined" && |
667 |
bookings_table !== null |
668 |
) { |
523 |
bookings_table.api().ajax.reload(); |
669 |
bookings_table.api().ajax.reload(); |
524 |
} |
670 |
} |
525 |
if (typeof timeline !== 'undefined' && timeline !== null) { |
671 |
if (typeof timeline !== "undefined" && timeline !== null) { |
526 |
timeline.itemsData.update({ |
672 |
timeline.itemsData.update({ |
527 |
id: data.booking_id, |
673 |
id: data.booking_id, |
528 |
booking: data.booking_id, |
674 |
booking: data.booking_id, |
Lines 531-561
$("#placeBookingForm").on('submit', function(e) {
Link Here
|
531 |
end: dayjs(data.end_date).toDate(), |
677 |
end: dayjs(data.end_date).toDate(), |
532 |
content: $patron_to_html(booking_patron, { |
678 |
content: $patron_to_html(booking_patron, { |
533 |
display_cardnumber: true, |
679 |
display_cardnumber: true, |
534 |
url: false |
680 |
url: false, |
535 |
}), |
681 |
}), |
536 |
editable: { remove: true, updateTime: true }, |
682 |
editable: { remove: true, updateTime: true }, |
537 |
type: 'range', |
683 |
type: "range", |
538 |
group: data.item_id ? data.item_id : 0 |
684 |
group: data.item_id ? data.item_id : 0, |
539 |
}); |
685 |
}); |
540 |
timeline.focus(data.booking_id); |
686 |
timeline.focus(data.booking_id); |
541 |
} |
687 |
} |
542 |
|
688 |
|
543 |
// Close modal |
689 |
// Close modal |
544 |
$('#placeBookingModal').modal('hide'); |
690 |
$("#placeBookingModal").modal("hide"); |
545 |
}); |
691 |
}); |
546 |
|
692 |
|
547 |
putting.fail(function(data) { |
693 |
putting.fail(function (data) { |
548 |
$('#booking_result').replaceWith('<div id="booking_result" class="alert alert-danger">'+__("Failure")+'</div>'); |
694 |
$("#booking_result").replaceWith( |
|
|
695 |
'<div id="booking_result" class="alert alert-danger">' + |
696 |
__("Failure") + |
697 |
"</div>", |
698 |
); |
549 |
}); |
699 |
}); |
550 |
} |
700 |
} |
551 |
}); |
701 |
}); |
552 |
|
702 |
|
553 |
$('#placeBookingModal').on('hidden.bs.modal', function (e) { |
703 |
$("#placeBookingModal").on("hidden.bs.modal", function (e) { |
554 |
$('#booking_patron_id').val(null).trigger('change'); |
704 |
$("#booking_patron_id").val(null).trigger("change"); |
555 |
$('#booking_patron_id').empty(); |
705 |
$("#booking_patron_id").empty(); |
556 |
$('#booking_item_id').val(0).trigger('change'); |
706 |
$("#booking_item_id").val(0).trigger("change"); |
557 |
$('#period').get(0)._flatpickr.clear(); |
707 |
$("#period").get(0)._flatpickr.clear(); |
558 |
$('#booking_start_date').val(''); |
708 |
$("#booking_start_date").val(""); |
559 |
$('#booking_end_date').val(''); |
709 |
$("#booking_end_date").val(""); |
560 |
$('#booking_id').val(''); |
710 |
$("#booking_id").val(""); |
561 |
}); |
711 |
}); |
562 |
- |
|
|