Bug 30081: Add item type column to holds table
[koha.git] / koha-tmpl / intranet-tmpl / prog / js / holds.js
1 function display_pickup_location (state) {
2     var $text;
3     if ( state.needs_override === true ) {
4         $text = $(
5             '<span>' + state.text + '</span> <span style="float:right;" title="' +
6             __("This pickup location is not allowed according to circulation rules") +
7             '"><i class="fa fa-exclamation-circle" aria-hidden="true"></i></span>'
8         );
9     }
10     else {
11         $text = $('<span>'+state.text+'</span>');
12     }
13
14     return $text;
15 };
16
17 (function ($) {
18
19     /**
20      * Generate a Select2 dropdown for pickup locations
21      *
22      * It expects the select object to contain several data-* attributes
23      * - data-pickup-location-source: 'biblio', 'item' or 'hold' (default)
24      * - data-patron-id: required for 'biblio' and 'item'
25      * - data-biblio-id: required for 'biblio' only
26      * - data-item-id: required for 'item' only
27      *
28      * @return {Object} The Select2 instance
29      */
30
31     $.fn.pickup_locations_dropdown = function () {
32         var select = $(this);
33         var pickup_location_source = $(this).data('pickup-location-source');
34         var patron_id = $(this).data('patron-id');
35         var biblio_id = $(this).data('biblio-id');
36         var item_id = $(this).data('item-id');
37         var hold_id = $(this).data('hold-id');
38
39         var url;
40
41         if ( pickup_location_source === 'biblio' ) {
42             url = '/api/v1/biblios/' + encodeURIComponent(biblio_id) + '/pickup_locations';
43         }
44         else if ( pickup_location_source === 'item' ) {
45             url = '/api/v1/items/' + encodeURIComponent(item_id) + '/pickup_locations';
46         }
47         else { // hold
48             url = '/api/v1/holds/' + encodeURIComponent(hold_id) + '/pickup_locations';
49         }
50
51         select.kohaSelect({
52             width: 'style',
53             allowClear: false,
54             ajax: {
55                 url: url,
56                 delay: 300, // wait 300 milliseconds before triggering the request
57                 cache: true,
58                 dataType: 'json',
59                 data: function (params) {
60                     var search_term = (params.term === undefined) ? '' : params.term;
61                     var query = {
62                         "q": JSON.stringify({"name":{"-like":'%'+search_term+'%'}}),
63                         "_order_by": "name",
64                         "_page": params.page
65                     };
66
67                     if ( pickup_location_source !== 'hold' ) {
68                         query["patron_id"] = patron_id;
69                     }
70
71                     return query;
72                 },
73                 processResults: function (data) {
74                     var results = [];
75                     data.results.forEach( function ( pickup_location ) {
76                         results.push(
77                             {
78                                 "id": pickup_location.library_id.escapeHtml(),
79                                 "text": pickup_location.name.escapeHtml(),
80                                 "needs_override": pickup_location.needs_override
81                             }
82                         );
83                     });
84                     return { "results": results, "pagination": { "more": data.pagination.more } };
85                 }
86             },
87             templateResult: display_pickup_location
88         });
89
90         return select;
91     };
92 })(jQuery);
93
94 /* global __ dataTablesDefaults borrowernumber SuspendHoldsIntranet */
95 $(document).ready(function() {
96
97     function suspend_hold(hold_id, end_date) {
98
99         var params;
100         if ( end_date !== null && end_date !== '' ) params = JSON.stringify({ "end_date": end_date });
101
102         return $.ajax({
103             method: 'POST',
104             url: '/api/v1/holds/'+encodeURIComponent(hold_id)+'/suspension',
105             contentType: 'application/json',
106             data: params
107         });
108     }
109
110     function resume_hold(hold_id) {
111         return $.ajax({
112             method: 'DELETE',
113             url: '/api/v1/holds/'+encodeURIComponent(hold_id)+'/suspension'
114         });
115     }
116
117     var holdsTable;
118
119     // Don't load holds table unless it is clicked on
120     $("#holds-tab").on( "click", function(){ load_holds_table() } );
121
122     // If the holds tab is preselected on load, we need to load the table
123     if ( $("#holds-tab").parent().hasClass('ui-state-active') ) { load_holds_table() }
124
125     function load_holds_table() {
126         var holds = new Array();
127         if ( ! holdsTable ) {
128             var title;
129             holdsTable = $("#holds-table").dataTable($.extend(true, {}, dataTablesDefaults, {
130                 "bAutoWidth": false,
131                 "sDom": "rt",
132                 "columns": [
133                     {
134                         "data": { _: "reservedate_formatted", "sort": "reservedate" }
135                     },
136                     {
137                         "mDataProp": function ( oObj ) {
138                             title = "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber="
139                                   + oObj.biblionumber
140                                   + "'>"
141                                   + oObj.title.escapeHtml();
142
143                             $.each(oObj.subtitle, function( index, value ) {
144                                 title += " " + value.escapeHtml();
145                             });
146
147                             title += " " + oObj.part_number + " " + oObj.part_name;
148
149                             if ( oObj.enumchron ) {
150                                 title += " (" + oObj.enumchron.escapeHtml() + ")";
151                             }
152
153                             title += "</a>";
154
155                             if ( oObj.author ) {
156                                 title += " " + __("by _AUTHOR_").replace("_AUTHOR_", oObj.author.escapeHtml());
157                             }
158
159                             if ( oObj.itemnotes ) {
160                                 var span_class = "";
161                                 if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
162                                     span_class = "circ-hlt";
163                                 }
164                                 title += " - <span class='" + span_class + "'>" + oObj.itemnotes.escapeHtml() + "</span>"
165                             }
166
167                             return title;
168                         }
169                     },
170                     {
171                         "mDataProp": function( oObj ) {
172                             return oObj.itemcallnumber && oObj.itemcallnumber.escapeHtml() || "";
173                         }
174                     },
175                     {
176                         "mDataProp": function( oObj ) {
177                             var data = "";
178                             if ( oObj.itemtype ) {
179                                 data += oObj.itemtype;
180                             }
181                             return data;
182                         }
183                     },
184                     {
185                         "mDataProp": function( oObj ) {
186                             var data = "";
187                             if ( oObj.barcode ) {
188                                 data += " <a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
189                                   + oObj.biblionumber
190                                   + "&itemnumber="
191                                   + oObj.itemnumber
192                                   + "#item"
193                                   + oObj.itemnumber
194                                   + "'>"
195                                   + oObj.barcode.escapeHtml()
196                                   + "</a>";
197                             }
198                             return data;
199                         }
200                     },
201                     {
202                         "mDataProp": function( oObj ) {
203                             if( oObj.branches.length > 1 && oObj.found !== 'W' && oObj.found !== 'T' ){
204                                 var branchSelect='<select priority='+oObj.priority+' class="hold_location_select" data-hold-id="'+oObj.reserve_id+'" reserve_id="'+oObj.reserve_id+'" name="pick-location" data-pickup-location-source="hold">';
205                                 for ( var i=0; i < oObj.branches.length; i++ ){
206                                     var selectedbranch;
207                                     var setbranch;
208                                     if( oObj.branches[i].selected ){
209
210                                         selectedbranch = " selected='selected' ";
211                                         setbranch = __(" (current) ");
212                                     } else if ( oObj.branches[i].pickup_location == 0 ) {
213                                         continue;
214                                     } else{
215                                         selectedbranch = '';
216                                         setbranch = '';
217                                     }
218                                     branchSelect += '<option value="'+ oObj.branches[i].branchcode.escapeHtml() +'"'+selectedbranch+'>'+oObj.branches[i].branchname.escapeHtml()+setbranch+'</option>';
219                                 }
220                                 branchSelect +='</select>';
221                                 return branchSelect;
222                             }
223                             else { return oObj.branchcode.escapeHtml() || ""; }
224                         }
225                     },
226                     { "data": { _: "expirationdate_formatted", "sort": "expirationdate" } },
227                     {
228                         "mDataProp": function( oObj ) {
229                             if ( oObj.priority && parseInt( oObj.priority ) && parseInt( oObj.priority ) > 0 ) {
230                                 return oObj.priority;
231                             } else {
232                                 return "";
233                             }
234                         }
235                     },
236                     {
237                         "bSortable": false,
238                         "mDataProp": function( oObj ) {
239                             return "<select name='rank-request'>"
240                                  +"<option value='n'>" + __("No") + "</option>"
241                                  +"<option value='del'>" + __("Yes") + "</option>"
242                                  + "</select>"
243                                  + "<input type='hidden' name='biblionumber' value='" + oObj.biblionumber + "'>"
244                                  + "<input type='hidden' name='borrowernumber' value='" + borrowernumber + "'>"
245                                  + "<input type='hidden' name='reserve_id' value='" + oObj.reserve_id + "'>";
246                         }
247                     },
248                     {
249                         "bSortable": false,
250                         "visible": SuspendHoldsIntranet,
251                         "mDataProp": function( oObj ) {
252                             holds[oObj.reserve_id] = oObj; //Store holds for later use
253
254                             if ( oObj.found ) {
255                                 return "";
256                             } else if ( oObj.suspend == 1 ) {
257                                 return "<a class='hold-resume btn btn-default btn-xs' data-hold-id='" + oObj.reserve_id + "'>"
258                                      +"<i class='fa fa-play'></i> " + __("Resume") + "</a>";
259                             } else {
260                                 return "<a class='hold-suspend btn btn-default btn-xs' data-hold-id='" + oObj.reserve_id + "' data-hold-title='"+ oObj.title +"'>"
261                                      +"<i class='fa fa-pause'></i> " + __("Suspend") + "</a>";
262                             }
263                         }
264                     },
265                     {
266                         "mDataProp": function( oObj ) {
267                             var data = "";
268
269                             if ( oObj.suspend == 1 ) {
270                                 data += "<p>" + __("Hold is <strong>suspended</strong>");
271                                 if ( oObj.suspend_until ) {
272                                     data += " " + __("until %s").format(oObj.suspend_until_formatted);
273                                 }
274                                 data += "</p>";
275                             }
276
277                             if ( oObj.itemtype_limit ) {
278                                 data += __("Next available %s item").format(oObj.itemtype_limit);
279                             }
280
281                             if ( oObj.barcode ) {
282                                 data += "<em>";
283                                 if ( oObj.found == "W" ) {
284
285                                     if ( oObj.waiting_here ) {
286                                         data += __("Item is <strong>waiting here</strong>");
287                                         if (oObj.desk_name) {
288                                             data += ", " + __("at %s").format(oObj.desk_name.escapeHtml());
289                                         }
290                                     } else {
291                                         data += __("Item is <strong>waiting</strong>");
292                                         data += " " + __("at %s").format(oObj.waiting_at);
293                                         if (oObj.desk_name) {
294                                             data += ", " + __("at %s").format(oObj.desk_name.escapeHtml());
295                                         }
296
297                                     }
298
299                                 } else if ( oObj.transferred ) {
300                                     data += __("Item is <strong>in transit</strong> from %s since %s").format(oObj.from_branch, oObj.date_sent);
301                                 } else if ( oObj.not_transferred ) {
302                                     data += __("Item hasn't been transferred yet from %s").format(oObj.not_transferred_by);
303                                 }
304                                 data += "</em>";
305                             }
306                             return data;
307                         }
308                     }
309                 ],
310                 "bPaginate": false,
311                 "bProcessing": true,
312                 "bServerSide": false,
313                 "ajax": {
314                     "url": '/cgi-bin/koha/svc/holds',
315                     "data": function ( d ) {
316                         d.borrowernumber = borrowernumber;
317                     }
318                 },
319             }));
320
321             $('#holds-table').on( 'draw.dt', function () {
322                 $(".hold-suspend").on( "click", function() {
323                     var hold_id    = $(this).data('hold-id');
324                     var hold_title = $(this).data('hold-title');
325                     $("#suspend-modal-title").html( hold_title );
326                     $("#suspend-modal-submit").data( 'hold-id', hold_id );
327                     $('#suspend-modal').modal('show');
328                 });
329
330                 $(".hold-resume").on("click", function () {
331                     var hold_id = $(this).data('hold-id');
332                     resume_hold(
333                         hold_id
334                     ).success(function () {
335                         holdsTable.api().ajax.reload();
336                     }).error(function (jqXHR, textStatus, errorThrown) {
337                         if (jqXHR.status === 404) {
338                             alert(__("Unable to resume, hold not found"));
339                         }
340                         else {
341                             alert(__("Your request could not be processed. Check the logs"));
342                         }
343                         holdsTable.api().ajax.reload();
344                     });
345                 });
346
347                 $(".hold_location_select").each(function(){ $(this).pickup_locations_dropdown(); });
348
349                 $(".hold_location_select").on("change", function(){
350                     $(this).prop("disabled",true);
351                     var cur_select = $(this);
352                     var res_id = $(this).attr('reserve_id');
353                     $(this).after('<div id="updating_reserveno'+res_id+'" class="waiting"><img src="/intranet-tmpl/prog/img/spinner-small.gif" alt="" /><span class="waiting_msg"></span></div>');
354                     var api_url = '/api/v1/holds/' + encodeURIComponent(res_id) + '/pickup_location';
355                     $.ajax({
356                         method: "PUT",
357                         url: api_url,
358                         data: JSON.stringify({ "pickup_library_id": $(this).val() }),
359                         headers: { "x-koha-override": "any" },
360                         success: function( data ){ holdsTable.api().ajax.reload(); },
361                         error: function( jqXHR, textStatus, errorThrown) {
362                             alert('There was an error:'+textStatus+" "+errorThrown);
363                             cur_select.prop("disabled",false);
364                             $("#updating_reserveno"+res_id).remove();
365                             cur_select.val( cur_select.children('option[selected="selected"]').val() );
366                         },
367                     });
368                 });
369
370             });
371
372             if ( $("#holds-table").length ) {
373                 $("#holds-table_processing").position({
374                     of: $( "#holds-table" ),
375                     collision: "none"
376                 });
377             }
378         }
379     }
380
381     $("body").append("\
382         <div id='suspend-modal' class='modal fade' role='dialog' aria-hidden='true'>\
383             <div class='modal-dialog'>\
384             <div class='modal-content'>\
385             <form id='suspend-modal-form' class='form-inline'>\
386                 <div class='modal-header'>\
387                     <button type='button' class='closebtn' data-dismiss='modal' aria-hidden='true'>×</button>\
388                     <h3 id='suspend-modal-label'>" + __("Suspend hold on") + " <i><span id='suspend-modal-title'></span></i></h3>\
389                 </div>\
390 \
391                 <div class='modal-body'>\
392                     <input type='hidden' id='suspend-modal-reserve_id' name='reserve_id' />\
393 \
394                     <label for='suspend-modal-until'>" + __("Suspend until:") + "</label>\
395                     <input name='suspend_until' id='suspend-modal-until' class='suspend-until flatpickr' data-flatpickr-futuredate='true' size='10' />\
396 \
397                     <p><a class='btn btn-link' id='suspend-modal-clear-date' >" + __("Clear date to suspend indefinitely") + "</a></p>\
398 \
399                 </div>\
400 \
401                 <div class='modal-footer'>\
402                     <button id='suspend-modal-submit' class='btn btn-primary' type='submit' name='submit'>" + __("Suspend") + "</button>\
403                     <a href='#' data-dismiss='modal' aria-hidden='true' class='cancel'>" + __("Cancel") + "</a>\
404                 </div>\
405             </form>\
406             </div>\
407             </div>\
408         </div>\
409     ");
410
411     $("#suspend-modal-clear-date").on( "click", function() { $("#suspend-modal-until").val(""); } );
412
413     $("#suspend-modal-submit").on( "click", function( e ) {
414         e.preventDefault();
415         var suspend_until_date = $("#suspend-modal-until").val();
416         if ( suspend_until_date !== null ) suspend_until_date = $date(suspend_until_date, {dateformat:"rfc3339"});
417         suspend_hold(
418             $(this).data('hold-id'),
419             suspend_until_date
420         ).success(function () {
421             holdsTable.api().ajax.reload();
422         }).error(function (jqXHR, textStatus, errorThrown) {
423             if (jqXHR.status === 404) {
424                 alert(__("Unable to suspend, hold not found"));
425             }
426             else {
427                 alert(__("Your request could not be processed. Check the logs"));
428             }
429             holdsTable.api().ajax.reload();
430         }).done(function() {
431             $("#suspend-modal-until").val(""); // clean the input
432             $('#suspend-modal').modal('hide');
433         });
434     });
435
436     $(".toggle-suspend").on('click', function(e) {
437         e.preventDefault();
438         let reserve_id     = $(this).data('reserve-id');
439         let biblionumber   = $(this).data('biblionumber');
440         let suspend_until  = $('#suspend_until_' + reserve_id).val();
441         window.location.href='request.pl?action=toggleSuspend&amp;reserve_id=' + reserve_id + '&amp;biblionumber=' + biblionumber + '&amp;suspend_until=' + suspend_until;
442         return false;
443     });
444 });