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