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