Bug 23493: Replace rowGrouping with rowGroup
[koha.git] / koha-tmpl / intranet-tmpl / prog / js / checkouts.js
1 /* global PATRON_NOTE */
2
3 $(document).ready(function() {
4     $.ajaxSetup ({ cache: false });
5
6     var barcodefield = $("#barcode");
7
8     var onHoldDueDateSet = false;
9
10     var onHoldChecked = function() {
11         var isChecked = false;
12         $('input[data-on-reserve]').each(function() {
13             if ($(this).is(':checked')) {
14                 isChecked=true;
15             }
16         });
17         return isChecked;
18     };
19
20     var showHideOnHoldRenewal = function() {
21         // Display the date input
22         if (onHoldChecked()) {
23             $('#newonholdduedate').show()
24         } else {
25             $('#newonholdduedate').hide();
26         }
27     };
28
29     // Handle the select all/none links for checkouts table columns
30     $("#CheckAllRenewals").on("click",function(){
31         $("#UncheckAllCheckins").click();
32         $(".renew:visible").prop("checked", true);
33         showHideOnHoldRenewal();
34         return false;
35     });
36     $("#UncheckAllRenewals").on("click",function(){
37         $(".renew:visible").prop("checked", false);
38         showHideOnHoldRenewal();
39         return false;
40     });
41
42     $("#CheckAllCheckins").on("click",function(){
43         $("#UncheckAllRenewals").click();
44         $(".checkin:visible").prop("checked", true);
45         return false;
46     });
47     $("#UncheckAllCheckins").on("click",function(){
48         $(".checkin:visible").prop("checked", false);
49         return false;
50     });
51
52     $("#newduedate").on("change", function() {
53         if (!onHoldDueDateSet) {
54             $('#newonholdduedate input').val($('#newduedate').val());
55         }
56     });
57
58     $("#newonholdduedate").on("change", function() {
59         onHoldDueDateSet = true;
60     });
61
62     // Don't allow both return and renew checkboxes to be checked
63     $(document).on("change", '.renew', function(){
64         if ( $(this).is(":checked") ) {
65             $( "#checkin_" + $(this).val() ).prop("checked", false);
66         }
67     });
68     $(document).on("change", '.checkin', function(){
69         if ( $(this).is(":checked") ) {
70             $( "#renew_" + $(this).val() ).prop("checked", false);
71         }
72     });
73
74     // Display on hold due dates input when an on hold item is
75     // selected
76     $(document).on('change', '.renew', function(){
77         showHideOnHoldRenewal();
78     });
79
80     $("#output_format > option:first-child").attr("selected", "selected");
81     $("select[name='csv_profile_id']").hide();
82     $(document).on("change", '#issues-table-output-format', function(){
83         if ( $(this).val() == 'csv' ) {
84             $("select[name='csv_profile_id']").show();
85         } else {
86             $("select[name='csv_profile_id']").hide();
87         }
88     });
89
90     // Clicking the table cell checks the checkbox inside it
91     $(document).on("click", 'td', function(e){
92         if(e.target.tagName.toLowerCase() == 'td'){
93           $(this).find("input:checkbox:visible").each( function() {
94             $(this).click();
95           });
96         }
97     });
98
99     // Handle renewals and returns
100     $("#RenewCheckinChecked").on("click",function(){
101         $(".checkin:checked:visible").each(function() {
102             itemnumber = $(this).val();
103
104             $(this).replaceWith("<img id='checkin_" + itemnumber + "' src='" + interface + "/" + theme + "/img/spinner-small.gif' />");
105
106             params = {
107                 itemnumber:     itemnumber,
108                 borrowernumber: borrowernumber,
109                 branchcode:     branchcode,
110                 exempt_fine:    $("#exemptfine").is(':checked')
111             };
112
113             $.post( "/cgi-bin/koha/svc/checkin", params, function( data ) {
114                 id = "#checkin_" + data.itemnumber;
115
116                 content = "";
117                 if ( data.returned ) {
118                     content = CIRCULATION_RETURNED;
119                     $(id).parent().parent().addClass('ok');
120                     $('#date_due_' + data.itemnumber).html(CIRCULATION_RETURNED);
121                     if ( data.patronnote != null ) {
122                         $('.patron_note_' + data.itemnumber).html( PATRON_NOTE + ": " + data.patronnote);
123                     }
124                 } else {
125                     content = CIRCULATION_NOT_RETURNED;
126                     $(id).parent().parent().addClass('warn');
127                 }
128
129                 $(id).replaceWith( content );
130             }, "json")
131         });
132
133         $(".renew:checked:visible").each(function() {
134             var override_limit = $("#override_limit").is(':checked') ? 1 : 0;
135
136             var isOnReserve = $(this).data().hasOwnProperty('onReserve');
137
138             var itemnumber = $(this).val();
139
140             $(this).parent().parent().replaceWith("<img id='renew_" + itemnumber + "' src='" + interface + "/" + theme + "/img/spinner-small.gif' />");
141
142             var params = {
143                 itemnumber:      itemnumber,
144                 borrowernumber:  borrowernumber,
145                 branchcode:      branchcode,
146                 override_limit:  override_limit,
147             };
148
149             // Determine which due date we need to use
150             var dueDate = isOnReserve ?
151                 $("#newonholdduedate input").val() :
152                 $("#newduedate").val();
153
154             if (dueDate && dueDate.length > 0) {
155                 params.date_due = dueDate
156             }
157
158             $.post( "/cgi-bin/koha/svc/renew", params, function( data ) {
159                 var id = "#renew_" + data.itemnumber;
160
161                 var content = "";
162                 if ( data.renew_okay ) {
163                     content = CIRCULATION_RENEWED_DUE + " " + data.date_due;
164                     $('#date_due_' + data.itemnumber).replaceWith( data.date_due );
165                 } else {
166                     content = CIRCULATION_RENEW_FAILED + " ";
167                     if ( data.error == "no_checkout" ) {
168                         content += NOT_CHECKED_OUT;
169                     } else if ( data.error == "too_many" ) {
170                         content += TOO_MANY_RENEWALS;
171                     } else if ( data.error == "on_reserve" ) {
172                         content += ON_RESERVE;
173                     } else if ( data.error == "restriction" ) {
174                         content += NOT_RENEWABLE_RESTRICTION;
175                     } else if ( data.error == "overdue" ) {
176                         content += NOT_RENEWABLE_OVERDUE;
177                     } else if ( data.error ) {
178                         content += data.error;
179                     } else {
180                         content += REASON_UNKNOWN;
181                     }
182                 }
183
184                 $(id).replaceWith( content );
185             }, "json")
186         });
187
188         // Refocus on barcode field if it exists
189         if ( $("#barcode").length ) {
190             $("#barcode").focus();
191         }
192
193         // Prevent form submit
194         return false;
195     });
196
197     $("#RenewAll").on("click",function(){
198         $("#CheckAllRenewals").click();
199         $("#UncheckAllCheckins").click();
200         showHideOnHoldRenewal();
201         $("#RenewCheckinChecked").click();
202
203         // Prevent form submit
204         return false;
205     });
206
207     var ymd = $.datepicker.formatDate('yy-mm-dd', new Date());
208
209     $('#issues-table').hide();
210     $('#issues-table-actions').hide();
211     $('#issues-table-load-immediately').change(function(){
212         if ( this.checked && typeof issuesTable === 'undefined') {
213             $('#issues-table-load-now-button').click();
214         }
215         barcodefield.focus();
216     });
217     $('#issues-table-load-now-button').click(function(){
218         LoadIssuesTable();
219         barcodefield.focus();
220         return false;
221     });
222
223     if ( Cookies.get("issues-table-load-immediately-" + script) == "true" ) {
224         LoadIssuesTable();
225         $('#issues-table-load-immediately').prop('checked', true);
226     }
227     $('#issues-table-load-immediately').on( "change", function(){
228         Cookies.set("issues-table-load-immediately-" + script, $(this).is(':checked'), { expires: 365 });
229     });
230
231     function LoadIssuesTable() {
232         $('#issues-table-loading-message').hide();
233         $('#issues-table').show();
234         $('#issues-table-actions').show();
235
236         issuesTable = KohaTable("issues-table", {
237             "oLanguage": {
238                 "sEmptyTable" : MSG_DT_LOADING_RECORDS,
239                 "sProcessing": MSG_DT_LOADING_RECORDS,
240             },
241             "bAutoWidth": false,
242             "dom": 'B<"clearfix">rt',
243             "aoColumns": [
244                 {
245                     "mDataProp": function( oObj ) {
246                         return oObj.sort_order;
247                     }
248                 },
249                 {
250                     "mDataProp": function( oObj ) {
251                         if ( oObj.issued_today ) {
252                             return "<strong>" + TODAYS_CHECKOUTS + "</strong>";
253                         } else {
254                             return "<strong>" + PREVIOUS_CHECKOUTS + "</strong>";
255                         }
256                     }
257                 },
258                 {
259                     "mDataProp": "date_due",
260                     "bVisible": false,
261                 },
262                 {
263                     "iDataSort": 2, // Sort on hidden unformatted date due column
264                     "mDataProp": function( oObj ) {
265                         var due = oObj.date_due_formatted;
266
267                         if ( oObj.date_due_overdue ) {
268                             due = "<span class='overdue'>" + due + "</span>";
269                         }
270
271                         due = "<span id='date_due_" + oObj.itemnumber + "' class='date_due'>" + due + "</span>";
272
273                         if ( oObj.lost && oObj.claims_returned ) {
274                             due += "<span class='lost claims_returned'>" + oObj.lost.escapeHtml() + "</span>";
275                         } else if ( oObj.lost ) {
276                             due += "<span class='lost'>" + oObj.lost.escapeHtml() + "</span>";
277                         }
278
279                         if ( oObj.damaged ) {
280                             due += "<span class='dmg'>" + oObj.damaged.escapeHtml() + "</span>";
281                         }
282
283                         var patron_note = " <span class='patron_note_" + oObj.itemnumber + "'></span>";
284                         due +="<br>" + patron_note;
285
286                         return due;
287                     }
288                 },
289                 {
290                     "mDataProp": function ( oObj ) {
291                         title = "<span id='title_" + oObj.itemnumber + "' class='strong'><a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber="
292                               + oObj.biblionumber
293                               + "'>"
294                               + oObj.title.escapeHtml();
295
296                         $.each(oObj.subtitle, function( index, value ) {
297                                   title += " " + value.escapeHtml();
298                         });
299
300                         title += " " + oObj.part_number + " " + oObj.part_name;
301
302                         if ( oObj.enumchron ) {
303                             title += " (" + oObj.enumchron.escapeHtml() + ")";
304                         }
305
306                         title += "</a></span>";
307
308                         if ( oObj.author ) {
309                             title += " " + BY.replace( "_AUTHOR_",  " " + oObj.author.escapeHtml() );
310                         }
311
312                         if ( oObj.itemnotes ) {
313                             var span_class = "text-muted";
314                             if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
315                                 span_class = "circ-hlt";
316                             }
317                             title += " - <span class='" + span_class + " item-note-public'>" + oObj.itemnotes.escapeHtml() + "</span>";
318                         }
319
320                         if ( oObj.itemnotes_nonpublic ) {
321                             var span_class = "text-danger";
322                             if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
323                                 span_class = "circ-hlt";
324                             }
325                             title += " - <span class='" + span_class + " item-note-nonpublic'>" + oObj.itemnotes_nonpublic.escapeHtml() + "</span>";
326                         }
327
328                         var onsite_checkout = '';
329                         if ( oObj.onsite_checkout == 1 ) {
330                             onsite_checkout += " <span class='onsite_checkout'>(" + INHOUSE_USE + ")</span>";
331                         }
332
333                         title += " "
334                               + "<a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
335                               + oObj.biblionumber
336                               + "&itemnumber="
337                               + oObj.itemnumber
338                               + "#"
339                               + oObj.itemnumber
340                               + "'>"
341                               + (oObj.barcode ? oObj.barcode.escapeHtml() : "")
342                               + "</a>"
343                               + onsite_checkout
344
345                         return title;
346                     },
347                     "sType": "anti-the"
348                 },
349                 {
350                     "mDataProp": function ( oObj ) {
351                         return oObj.recordtype_description.escapeHtml();
352                     }
353                 },
354                 {
355                     "mDataProp": function ( oObj ) {
356                         return oObj.itemtype_description.escapeHtml();
357                     }
358                 },
359                 {
360                     "mDataProp": function ( oObj ) {
361                         return ( oObj.collection ? oObj.collection.escapeHtml() : '' );
362                     }
363                 },
364                 {
365                     "mDataProp": function ( oObj ) {
366                         return ( oObj.location ? oObj.location.escapeHtml() : '' );
367                     }
368                 },
369                 {
370                     "mDataProp": function ( oObj ) {
371                         return oObj.homebranch.escapeHtml();
372                     }
373                 },
374                 {
375                     "mDataProp": "issuedate",
376                     "bVisible": false,
377                 },
378                 {
379                     "iDataSort": 9, // Sort on hidden unformatted issuedate column
380                     "mDataProp": "issuedate_formatted",
381                 },
382                 {
383                     "mDataProp": function ( oObj ) {
384                         return oObj.branchname.escapeHtml();
385                     }
386                 },
387                 {
388                     "mDataProp": function ( oObj ) {
389                         return ( oObj.itemcallnumber ? oObj.itemcallnumber.escapeHtml() : '' );
390                     }
391                 },
392                 {
393                     "mDataProp": function ( oObj ) {
394                         if ( ! oObj.charge ) oObj.charge = 0;
395                         return '<span style="text-align: right; display: block;">' + parseFloat(oObj.charge).toFixed(2) + '<span>';
396                     }
397                 },
398                 {
399                     "mDataProp": function ( oObj ) {
400                         if ( ! oObj.fine ) oObj.fine = 0;
401                         return '<span style="text-align: right; display: block;">' + parseFloat(oObj.fine).toFixed(2)  + '<span>';
402                     }
403                 },
404                 {
405                     "mDataProp": function ( oObj ) {
406                         if ( ! oObj.price ) oObj.price = 0;
407                         return '<span style="text-align: right; display: block;">' + parseFloat(oObj.price).toFixed(2) + '<span>';
408                     }
409                 },
410                 {
411                     "bSortable": false,
412                     "bVisible": AllowCirculate ? true : false,
413                     "mDataProp": function ( oObj ) {
414                         var content = "";
415                         var msg = "";
416                         var span_style = "";
417                         var span_class = "";
418
419                         if ( oObj.can_renew ) {
420                             // Do nothing
421                         } else if ( oObj.can_renew_error == "on_reserve" ) {
422                             msg += "<span>"
423                                     + "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" + oObj.biblionumber + "'>" + ON_HOLD + "</a>"
424                                     + "</span>";
425
426                             span_style = "display: none";
427                             span_class = "renewals-allowed-on_reserve";
428                         } else if ( oObj.can_renew_error == "too_many" ) {
429                             msg += "<span class='renewals-disabled'>"
430                                     + NOT_RENEWABLE
431                                     + "</span>";
432
433                             span_style = "display: none";
434                             span_class = "renewals-allowed";
435                         } else if ( oObj.can_renew_error == "restriction" ) {
436                             msg += "<span class='renewals-disabled'>"
437                                     + NOT_RENEWABLE_RESTRICTION
438                                     + "</span>";
439
440                             span_style = "display: none";
441                             span_class = "renewals-allowed";
442                         } else if ( oObj.can_renew_error == "overdue" ) {
443                             msg += "<span class='renewals-disabled'>"
444                                     + NOT_RENEWABLE_OVERDUE
445                                     + "</span>";
446
447                             span_style = "display: none";
448                             span_class = "renewals-allowed";
449                         } else if ( oObj.can_renew_error == "too_soon" ) {
450                             msg += "<span class='renewals-disabled'>"
451                                     + NOT_RENEWABLE_TOO_SOON.format( oObj.can_renew_date )
452                                     + "</span>";
453
454                             span_style = "display: none";
455                             span_class = "renewals-allowed";
456                         } else if ( oObj.can_renew_error == "auto_too_soon" ) {
457                             msg += "<span class='renewals-disabled'>"
458                                     + NOT_RENEWABLE_AUTO_TOO_SOON
459                                     + "</span>";
460
461                             span_style = "display: none";
462                             span_class = "renewals-allowed";
463                         } else if ( oObj.can_renew_error == "auto_too_late" ) {
464                             msg += "<span class='renewals-disabled'>"
465                                     + NOT_RENEWABLE_AUTO_TOO_LATE
466                                     + "</span>";
467
468                             span_style = "display: none";
469                             span_class = "renewals-allowed";
470                         } else if ( oObj.can_renew_error == "auto_too_much_oweing" ) {
471                             msg += "<span class='renewals-disabled'>"
472                                     + NOT_RENEWABLE_AUTO_TOO_MUCH_OWEING
473                                     + "</span>";
474
475                             span_style = "display: none";
476                             span_class = "renewals-allowed";
477                         } else if ( oObj.can_renew_error == "auto_account_expired" ) {
478                             msg += "<span class='renewals-disabled'>"
479                                     + NOT_RENEWABLE_AUTO_ACCOUNT_EXPIRED
480                                     + "</span>";
481
482                             span_style = "display: none";
483                             span_class = "renewals-allowed";
484                         } else if ( oObj.can_renew_error == "auto_renew" ) {
485                             msg += "<span class='renewals-disabled'>"
486                                     + NOT_RENEWABLE_AUTO_RENEW
487                                     + "</span>";
488
489                             span_style = "display: none";
490                             span_class = "renewals-allowed";
491                         } else if ( oObj.can_renew_error == "onsite_checkout" ) {
492                             // Don't display something if it's an onsite checkout
493                         } else if ( oObj.can_renew_error == "item_denied_renewal" ) {
494                             content += "<span class='renewals-disabled'>"
495                                     + NOT_RENEWABLE_DENIED
496                                     + "</span>";
497
498                             span_style = "display: none";
499                             span_class = "renewals-allowed";
500                         } else {
501                             msg += "<span class='renewals-disabled'>"
502                                     + oObj.can_renew_error
503                                     + "</span>";
504
505                             span_style = "display: none";
506                             span_class = "renewals-allowed";
507                         }
508
509                         var can_force_renew = ( oObj.onsite_checkout == 0 ) &&
510                             ( oObj.can_renew_error != "on_reserve" || (oObj.can_renew_error == "on_reserve" && AllowRenewalOnHoldOverride))
511                             ? true : false;
512                         var can_renew = ( oObj.renewals_remaining > 0  && !oObj.can_renew_error );
513                         content += "<span>";
514                         if ( can_renew || can_force_renew ) {
515                             content += "<span style='padding: 0 1em;'>" + oObj.renewals_count + "</span>";
516                             content += "<span class='" + span_class + "' style='" + span_style + "'>"
517                                     +  "<input type='checkbox' ";
518                             if ( oObj.date_due_overdue && can_renew ) {
519                                 content += "checked='checked' ";
520                             }
521                             if (oObj.can_renew_error == "on_reserve") {
522                                 content += "data-on-reserve ";
523                             }
524                             content += "class='renew' id='renew_" + oObj.itemnumber + "' name='renew' value='" + oObj.itemnumber +"'/>"
525                                     +  "</span>";
526                         }
527                         content += msg;
528                         if ( can_renew || can_force_renew ) {
529                             content += "<span class='renewals'>("
530                                     + RENEWALS_REMAINING.format( oObj.renewals_remaining, oObj.renewals_allowed )
531                                     + ")</span>";
532                         }
533
534                         content += "</span>";
535
536                         return content;
537                     }
538                 },
539                 {
540                     "bSortable": false,
541                     "bVisible": AllowCirculate ? true : false,
542                     "mDataProp": function ( oObj ) {
543                         if ( oObj.can_renew_error == "on_reserve" ) {
544                             return "<a href='/cgi-bin/koha/reserve/request.pl?biblionumber=" + oObj.biblionumber + "'>" + ON_HOLD + "</a>";
545                         } else {
546                             return "<input type='checkbox' class='checkin' id='checkin_" + oObj.itemnumber + "' name='checkin' value='" + oObj.itemnumber +"'></input>";
547                         }
548                     }
549                 },
550                 {
551                     "bVisible": ClaimReturnedLostValue ? true : false,
552                     "bSortable": false,
553                     "mDataProp": function ( oObj ) {
554                         let content = "";
555
556                         if ( oObj.return_claim_id ) {
557                           content = `<span class="badge">${oObj.return_claim_created_on_formatted}</span>`;
558                         } else {
559                           content = `<a class="btn btn-default btn-xs claim-returned-btn" data-itemnumber="${oObj.itemnumber}"><i class="fa fa-exclamation-circle"></i> ${RETURN_CLAIMED_MAKE}</a>`;
560                         }
561                         return content;
562                     }
563                 },
564                 {
565                     "bVisible": exports_enabled == 1 ? true : false,
566                     "bSortable": false,
567                     "mDataProp": function ( oObj ) {
568                         var s = "<input type='checkbox' name='itemnumbers' value='" + oObj.itemnumber + "' style='visibility:hidden;' />";
569
570                         s += "<input type='checkbox' class='export' id='export_" + oObj.biblionumber + "' name='biblionumbers' value='" + oObj.biblionumber + "' />";
571                         return s;
572                     }
573                 }
574             ],
575             "fnFooterCallback": function ( nRow, aaData, iStart, iEnd, aiDisplay ) {
576                 var total_charge = 0;
577                 var total_fine  = 0;
578                 var total_price = 0;
579                 for ( var i=0; i < aaData.length; i++ ) {
580                     total_charge += aaData[i]['charge'] * 1;
581                     total_fine += aaData[i]['fine'] * 1;
582                     total_price  += aaData[i]['price'] * 1;
583                 }
584                 $("#totaldue").html(total_charge.toFixed(2));
585                 $("#totalfine").html(total_fine.toFixed(2));
586                 $("#totalprice").html(total_price.toFixed(2));
587             },
588             "bPaginate": false,
589             "bProcessing": true,
590             "bServerSide": false,
591             "sAjaxSource": '/cgi-bin/koha/svc/checkouts',
592             "fnServerData": function ( sSource, aoData, fnCallback ) {
593                 aoData.push( { "name": "borrowernumber", "value": borrowernumber } );
594
595                 $.getJSON( sSource, aoData, function (json) {
596                     fnCallback(json)
597                 } );
598             },
599             "rowGroup":{
600                 "dataSrc": "issued_today",
601                 "startRender": function ( rows, group ) {
602                     if ( group ) {
603                         return $('<tr/>').append("<td colspan='100%'><strong>" + TODAYS_CHECKOUTS + "</strong></td>");
604                     } else {
605                         return $('<tr/>').append("<td colspan='100%'><strong>" + PREVIOUS_CHECKOUTS + "</strong></td>");
606                     }
607                 }
608             },
609             "fnInitComplete": function(oSettings, json) {
610                 // Disable rowGrouping plugin after first use
611                 // so any sorting on the table doesn't use it
612                 //var oSettings = issuesTable.fnSettings();
613
614                 // Build a summary of checkouts grouped by itemtype
615                 var checkoutsByItype = json.aaData.reduce(function (obj, row) {
616                     obj[row.type_for_stat] = (obj[row.type_for_stat] || 0) + 1;
617                     return obj;
618                 }, {});
619                 var ul = $('<ul>');
620                 Object.keys(checkoutsByItype).sort().forEach(function (itype) {
621                     var li = $('<li>')
622                         .append($('<strong>').html(itype || MSG_NO_ITEMTYPE))
623                         .append(': ' + checkoutsByItype[itype]);
624                     ul.append(li);
625                 })
626                 $('<details>')
627                     .addClass('checkouts-by-itemtype')
628                     .append($('<summary>').html(MSG_CHECKOUTS_BY_ITEMTYPE))
629                     .append(ul)
630                     .insertBefore(oSettings.nTableWrapper)
631             },
632         }, columns_settings_issues_table);
633
634         if ( $("#issues-table").length ) {
635             $("#issues-table_processing").position({
636                 of: $( "#issues-table" ),
637                 collision: "none"
638             });
639         }
640     }
641
642     // Don't load relatives' issues table unless it is clicked on
643     var relativesIssuesTable;
644     $("#relatives-issues-tab").click( function() {
645         if ( ! relativesIssuesTable ) {
646             relativesIssuesTable = $("#relatives-issues-table").dataTable($.extend(true, {}, dataTablesDefaults, {
647                 "bAutoWidth": false,
648                 "sDom": "rt",
649                 "aaSorting": [],
650                 "aoColumns": [
651                     {
652                         "mDataProp": "date_due",
653                         "bVisible": false,
654                     },
655                     {
656                         "iDataSort": 0, // Sort on hidden unformatted date due column
657                         "mDataProp": function( oObj ) {
658                             var today = new Date();
659                             var due = new Date( oObj.date_due );
660                             if ( today > due ) {
661                                 return "<span class='overdue'>" + oObj.date_due_formatted + "</span>";
662                             } else {
663                                 return oObj.date_due_formatted;
664                             }
665                         }
666                     },
667                     {
668                         "mDataProp": function ( oObj ) {
669                             title = "<span class='strong'><a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber="
670                                   + oObj.biblionumber
671                                   + "'>"
672                                   + oObj.title.escapeHtml();
673
674                             $.each(oObj.subtitle, function( index, value ) {
675                                       title += " " + value.escapeHtml();
676                             });
677
678                             title += " " + oObj.part_number + " " + oObj.part_name;
679
680                             if ( oObj.enumchron ) {
681                                 title += " (" + oObj.enumchron.escapeHtml() + ")";
682                             }
683
684                             title += "</a></span>";
685
686                             if ( oObj.author ) {
687                                 title += " " + BY.replace( "_AUTHOR_", " " + oObj.author.escapeHtml() );
688                             }
689
690                             if ( oObj.itemnotes ) {
691                                 var span_class = "";
692                                 if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
693                                     span_class = "circ-hlt";
694                                 }
695                                 title += " - <span class='" + span_class + "'>" + oObj.itemnotes.escapeHtml() + "</span>"
696                             }
697
698                             if ( oObj.itemnotes_nonpublic ) {
699                                 var span_class = "";
700                                 if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
701                                     span_class = "circ-hlt";
702                                 }
703                                 title += " - <span class='" + span_class + "'>" + oObj.itemnotes_nonpublic.escapeHtml() + "</span>"
704                             }
705
706                             var onsite_checkout = '';
707                             if ( oObj.onsite_checkout == 1 ) {
708                                 onsite_checkout += " <span class='onsite_checkout'>(" + INHOUSE_USE + ")</span>";
709                             }
710
711                             title += " "
712                                   + "<a href='/cgi-bin/koha/catalogue/moredetail.pl?biblionumber="
713                                   + oObj.biblionumber
714                                   + "&itemnumber="
715                                   + oObj.itemnumber
716                                   + "#"
717                                   + oObj.itemnumber
718                                   + "'>"
719                                   + (oObj.barcode ? oObj.barcode.escapeHtml() : "")
720                                   + "</a>"
721                                   + onsite_checkout;
722
723                             return title;
724                         },
725                         "sType": "anti-the"
726                     },
727                     {
728                         "mDataProp": function ( oObj ) {
729                             return oObj.recordtype_description.escapeHtml();
730                         }
731                     },
732                     {
733                         "mDataProp": function ( oObj ) {
734                             return oObj.itemtype_description.escapeHtml();
735                         }
736                     },
737                     {
738                         "mDataProp": function ( oObj ) {
739                             return ( oObj.collection ? oObj.collection.escapeHtml() : '' );
740                         }
741                     },
742                     {
743                         "mDataProp": function ( oObj ) {
744                             return ( oObj.location ? oObj.location.escapeHtml() : '' );
745                         }
746                     },
747                     {
748                         "mDataProp": "issuedate",
749                         "bVisible": false,
750                     },
751                     {
752                         "iDataSort": 7, // Sort on hidden unformatted issuedate column
753                         "mDataProp": "issuedate_formatted",
754                     },
755                     {
756                         "mDataProp": function ( oObj ) {
757                             return oObj.branchname.escapeHtml();
758                         }
759                     },
760                     {
761                         "mDataProp": function ( oObj ) {
762                             return ( oObj.itemcallnumber ? oObj.itemcallnumber.escapeHtml() : '' );
763                         }
764                     },
765                     {
766                         "mDataProp": function ( oObj ) {
767                             if ( ! oObj.charge ) oObj.charge = 0;
768                             return parseFloat(oObj.charge).toFixed(2);
769                         }
770                     },
771                     {
772                         "mDataProp": function ( oObj ) {
773                             if ( ! oObj.fine ) oObj.fine = 0;
774                             return parseFloat(oObj.fine).toFixed(2);
775                         }
776                     },
777                     {
778                         "mDataProp": function ( oObj ) {
779                             if ( ! oObj.price ) oObj.price = 0;
780                             return parseFloat(oObj.price).toFixed(2);
781                         }
782                     },
783                     {
784                         "mDataProp": function( oObj ) {
785                             return "<a href='/cgi-bin/koha/members/moremember.pl?borrowernumber=" + oObj.borrowernumber + "'>"
786                                 + oObj.borrower.firstname.escapeHtml()
787                                 + " " +
788                                 oObj.borrower.surname.escapeHtml()
789                                 + " (" + oObj.borrower.cardnumber.escapeHtml() + ")</a>"
790                         }
791                     },
792                 ],
793                 "bPaginate": false,
794                 "bProcessing": true,
795                 "bServerSide": false,
796                 "sAjaxSource": '/cgi-bin/koha/svc/checkouts',
797                 "fnServerData": function ( sSource, aoData, fnCallback ) {
798                     $.each(relatives_borrowernumbers, function( index, value ) {
799                         aoData.push( { "name": "borrowernumber", "value": value } );
800                     });
801
802                     $.getJSON( sSource, aoData, function (json) {
803                         fnCallback(json)
804                     } );
805                 },
806             }));
807         }
808     });
809
810     if ( $("#relatives-issues-table").length ) {
811         $("#relatives-issues-table_processing").position({
812             of: $( "#relatives-issues-table" ),
813             collision: "none"
814         });
815     }
816
817     if ( AllowRenewalLimitOverride || AllowRenewalOnHoldOverride ) {
818         $( '#override_limit' ).click( function () {
819             if ( this.checked ) {
820                 if ( AllowRenewalLimitOverride ) {
821                     $( '.renewals-allowed' ).show();
822                     $( '.renewals-disabled' ).hide();
823                 }
824                 if ( AllowRenewalOnHoldOverride ) {
825                     $( '.renewals-allowed-on_reserve' ).show();
826                 }
827             } else {
828                 $( '.renewals-allowed' ).hide();
829                 $( '.renewals-allowed-on_reserve' ).hide();
830                 $( '.renewals-disabled' ).show();
831             }
832         } ).prop('checked', false);
833     }
834
835     // Handle return claims
836     $(document).on("click", '.claim-returned-btn', function(e){
837         e.preventDefault();
838         itemnumber = $(this).data('itemnumber');
839
840         $('#claims-returned-itemnumber').val(itemnumber);
841         $('#claims-returned-notes').val("");
842         $('#claims-returned-charge-lost-fee').attr('checked', false)
843         $('#claims-returned-modal').modal()
844     });
845     $(document).on("click", '#claims-returned-modal-btn-submit', function(e){
846         let itemnumber = $('#claims-returned-itemnumber').val();
847         let notes = $('#claims-returned-notes').val();
848         let fee = $('#claims-returned-charge-lost-fee').attr('checked') ? true : false;
849
850         $('#claims-returned-modal').modal('hide')
851
852         $(`.claim-returned-btn[data-itemnumber='${itemnumber}']`).replaceWith(`<img id='return_claim_spinner_${itemnumber}' src='${interface}/${theme}/img/spinner-small.gif' />`);
853
854         params = {
855             item_id: itemnumber,
856             notes: notes,
857             charge_lost_fee: fee,
858             created_by: logged_in_user_borrowernumber,
859         };
860
861         $.post( '/api/v1/return_claims', JSON.stringify(params), function( data ) {
862
863             id = "#return_claim_spinner_" + data.item_id;
864
865             let created_on = new Date(data.created_on);
866
867             let content = "";
868             if ( data.claim_id ) {
869                 content = `<span class="badge">${created_on.toLocaleDateString()}</span>`;
870                 $(id).parent().parent().addClass('ok');
871             } else {
872                 content = RETURN_CLAIMED_FAILURE;
873                 $(id).parent().parent().addClass('warn');
874             }
875
876             $(id).replaceWith( content );
877
878             refreshReturnClaimsTable();
879         }, "json")
880
881     });
882
883
884     // Don't load return claims table unless it is clicked on
885     var returnClaimsTable;
886     $("#return-claims-tab").click( function() {
887         refreshReturnClaimsTable();
888     });
889
890     function refreshReturnClaimsTable(){
891         loadReturnClaimsTable();
892         $("#return-claims-table").DataTable().ajax.reload();
893     }
894     function loadReturnClaimsTable() {
895         if ( ! returnClaimsTable ) {
896             returnClaimsTable = $("#return-claims-table").dataTable({
897                 "bAutoWidth": false,
898                 "sDom": "rt",
899                 "aaSorting": [],
900                 "aoColumns": [
901                     {
902                         "mDataProp": "id",
903                         "bVisible": false,
904                     },
905                     {
906                         "mDataProp": function ( oObj ) {
907                               let title = `<a class="return-claim-title strong" href="/cgi-bin/koha/circ/request-rcticle.pl?biblionumber=[% rc.checkout.item.biblionumber | html %]">
908                                   ${oObj.title}
909                                   ${oObj.enumchron || ""}
910                               </a>`;
911                               if ( oObj.author ) {
912                                 title += `by ${oObj.author}`;
913                               }
914                               title += `<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=${oObj.biblionumber}&itemnumber=${oObj.itemnumber}">${oObj.barcode}</a>`;
915
916                               return title;
917                         }
918                     },
919                     {
920                         "sClass": "return-claim-notes-td",
921                         "mDataProp": function ( oObj ) {
922                             return `
923                                 <span id="return-claim-notes-static-${oObj.id}" class="return-claim-notes" data-return-claim-id="${oObj.id}">${oObj.notes}</span>
924                                 <i style="float:right" class="fa fa-pencil-square-o" title="Double click to edit"></i>
925                             `;
926                         }
927                     },
928                     {
929                         "mDataProp": function ( oObj ) {
930                             let created_on = new Date( oObj.created_on );
931                             return created_on.toLocaleDateString();
932                         }
933                     },
934                     {
935                         "mDataProp": function ( oObj ) {
936                             let updated_on = new Date( oObj.updated_on );
937                             return updated_on.toLocaleDateString();
938                         }
939                     },
940                     {
941                         "mDataProp": function ( oObj ) {
942                             if ( ! oObj.resolution ) return "";
943
944                             let desc = `<strong>${oObj.resolution_data.lib}</strong> on <i>${oObj.resolved_on}</i>`;
945                             if (oObj.resolved_by_data) desc += ` by <a href="/cgi-bin/koha/circ/circulation.pl?borrowernumber=${oObj.resolved_by_data.borrowernumber}">${oObj.resolved_by_data.firstname || ""} ${oObj.resolved_by_data.surname || ""}</a>`;
946                             return desc;
947                         }
948                     },
949                     {
950                         "mDataProp": function ( oObj ) {
951                             let delete_html = oObj.resolved_on
952                                 ? `<li><a href="#" class="return-claim-tools-delete" data-return-claim-id="${oObj.id}"><i class="fa fa-trash"></i> Delete</a></li>`
953                                 : "";
954                             let resolve_html = ! oObj.resolution
955                                 ? `<li><a href="#" class="return-claim-tools-resolve" data-return-claim-id="${oObj.id}"><i class="fa fa-check-square"></i> Resolve</a></li>`
956                                 : "";
957
958                             return `
959                                 <div class="btn-group">
960                                   <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
961                                     Actions <span class="caret"></span>
962                                   </button>
963                                   <ul class="dropdown-menu">
964                                     <li><a href="#" class="return-claim-tools-editnotes" data-return-claim-id="${oObj.id}"><i class="fa fa-edit"></i> Edit notes</a></li>
965                                     ${resolve_html}
966                                     ${delete_html}
967                                   </ul>
968                                 </div>
969                             `;
970                         }
971                     },
972                 ],
973                 "bPaginate": false,
974                 "bProcessing": true,
975                 "bServerSide": false,
976                 "sAjaxSource": '/cgi-bin/koha/svc/return_claims',
977                 "fnServerData": function ( sSource, aoData, fnCallback ) {
978                     aoData.push( { "name": "borrowernumber", "value": borrowernumber } );
979
980                     $.getJSON( sSource, aoData, function (json) {
981                         let resolved = json.resolved;
982                         let unresolved = json.unresolved;
983
984                         $('#return-claims-count-resolved').text(resolved);
985                         $('#return-claims-count-unresolved').text(unresolved);
986
987                         fnCallback(json)
988                     } );
989                 },
990             });
991         }
992     }
993
994     $('body').on('click', '.return-claim-tools-editnotes', function() {
995         let id = $(this).data('return-claim-id');
996         $(`#return-claim-notes-static-${id}`).parent().dblclick();
997     });
998     $('body').on('dblclick', '.return-claim-notes-td', function() {
999         let elt = $(this).children('.return-claim-notes');
1000         let id = elt.data('return-claim-id');
1001         if ( $(`#return-claim-notes-editor-textarea-${id}`).length == 0 ) {
1002             let note = elt.text();
1003             let editor = `
1004                 <span id="return-claim-notes-editor-${id}">
1005                     <textarea id="return-claim-notes-editor-textarea-${id}">${note}</textarea>
1006                     <br/>
1007                     <a class="btn btn-default btn-xs claim-returned-notes-editor-submit" data-return-claim-id="${id}"><i class="fa fa-save"></i> Update</a>
1008                     <a class="claim-returned-notes-editor-cancel" data-return-claim-id="${id}" href="#">Cancel</a>
1009                 </span>
1010             `;
1011             elt.hide();
1012             $(editor).insertAfter( elt );
1013         }
1014     });
1015
1016     $('body').on('click', '.claim-returned-notes-editor-submit', function(){
1017         let id = $(this).data('return-claim-id');
1018         let notes = $(`#return-claim-notes-editor-textarea-${id}`).val();
1019
1020         let params = {
1021             notes: notes,
1022             updated_by: logged_in_user_borrowernumber
1023         };
1024
1025         $(this).parent().remove();
1026
1027         $.ajax({
1028             url: `/api/v1/return_claims/${id}/notes`,
1029             type: 'PUT',
1030             data: JSON.stringify(params),
1031             success: function( data ) {
1032                 let notes = $(`#return-claim-notes-static-${id}`);
1033                 notes.text(data.notes);
1034                 notes.show();
1035             },
1036             contentType: "json"
1037         });
1038     });
1039
1040     $('body').on('click', '.claim-returned-notes-editor-cancel', function(){
1041         let id = $(this).data('return-claim-id');
1042         $(this).parent().remove();
1043         $(`#return-claim-notes-static-${id}`).show();
1044     });
1045
1046     // Hanld return claim deletion
1047     $('body').on('click', '.return-claim-tools-delete', function() {
1048         let confirmed = confirm(CONFIRM_DELETE_RETURN_CLAIM);
1049         if ( confirmed ) {
1050             let id = $(this).data('return-claim-id');
1051
1052             $.ajax({
1053                 url: `/api/v1/return_claims/${id}`,
1054                 type: 'DELETE',
1055                 success: function( data ) {
1056                     refreshReturnClaimsTable();
1057                 }
1058             });
1059         }
1060     });
1061
1062     // Handle return claim resolution
1063     $('body').on('click', '.return-claim-tools-resolve', function() {
1064         let id = $(this).data('return-claim-id');
1065
1066         $('#claims-returned-resolved-modal-id').val(id);
1067         $('#claims-returned-resolved-modal').modal()
1068     });
1069
1070     $(document).on('click', '#claims-returned-resolved-modal-btn-submit', function(e) {
1071         let resolution = $('#claims-returned-resolved-modal-resolved-code').val();
1072         let id = $('#claims-returned-resolved-modal-id').val();
1073
1074         $('#claims-returned-resolved-modal-btn-submit-spinner').show();
1075         $('#claims-returned-resolved-modal-btn-submit-icon').hide();
1076
1077         params = {
1078           resolution: resolution,
1079           updated_by: logged_in_user_borrowernumber
1080         };
1081
1082         $.ajax({
1083             url: `/api/v1/return_claims/${id}/resolve`,
1084             type: 'PUT',
1085             data: JSON.stringify(params),
1086             success: function( data ) {
1087                 $('#claims-returned-resolved-modal-btn-submit-spinner').hide();
1088                 $('#claims-returned-resolved-modal-btn-submit-icon').show();
1089                 $('#claims-returned-resolved-modal').modal('hide')
1090
1091                 refreshReturnClaimsTable();
1092             },
1093             contentType: "json"
1094         });
1095
1096     });
1097
1098  });