Bug 29002: Add bookings view
[koha.git] / koha-tmpl / intranet-tmpl / prog / js / place_booking_modal.js
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                 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());
253
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] ) {
260                             return false;
261                         }
262                         // This booking starts after then end of the new booking
263                         if ( start_date >= selectedDates[1] ) {
264                             return false;
265                         }
266                         // This booking overlaps
267                         return true;
268                     });
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;
275                         }) ) {
276                             option.prop('disabled',true);
277                         } else {
278                             option.prop('disabled',false);
279                         }
280                     });
281                 }
282             });
283         },
284         function(jqXHR, textStatus, errorThrown){
285             console.log("Fetch failed");
286         }
287     );
288 });
289
290 $("#placeBookingForm").on('submit', function(e) {
291     e.preventDefault();
292
293     var url = '/api/v1/bookings';
294
295     var start_date = $('#booking_start_date').val();
296     var end_date = $('#booking_end_date').val();
297     var item_id = $('#booking_item_id').val();
298
299     var posting = $.post(
300         url,
301         JSON.stringify({
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()
307         })
308     );
309
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();
314         }
315
316         // Close modal
317         $('#placeBookingModal').modal('hide');
318
319         // Reset form
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('');
325     });
326
327     posting.fail(function(data) {
328         $('#booking_result').replaceWith('<div id="booking_result" class="alert alert-danger">Failure</div>');
329     });
330 });