Bug 30063: Overdues count
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / patron-search.inc
1 [% USE Koha %]
2 [% USE Branches %]
3 [% USE raw %]
4 [% USE To %]
5
6 [% BLOCK patron_search_filters_simple  %]
7     <form id="patron_search_form">
8         <div class="hint">Enter patron card number or partial name:</div>
9         <input type="text" size="40" id="search_patron_filter" class="focus" autocomplete="off" />
10         <input type="submit" value="Search" />
11     </form>
12 [% END %]
13
14 [% BLOCK patron_search_filters %]
15     <form id="patron_search_form">
16         <fieldset class="brief">
17             <h3>Search for patron</h3>
18             <ol>
19                 <li>
20                     <label for="search_patron_filter">Search:</label>
21                     <div class="hint">Enter patron card number or partial name:</div>
22                     <input type="text" id="search_patron_filter" value="[% search_filter | html %]" class="focus" />
23                 </li>
24
25                 [% FOR column IN columns %]
26                     [% SWITCH column %]
27                     [% CASE 'branch' %]
28                         <li>
29                             <label for="branchcode_filter">Library:</label>
30                             <select id="branchcode_filter">
31                                 [% SET libraries = Branches.all( only_from_group => 1 ) %]
32                                 [% IF libraries.size != 1 %]
33                                     <option value="">Any</option>
34                                 [% END %]
35                                 [% FOREACH l IN libraries %]
36                                     <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
37                                 [% END %]
38                             </select>
39                         </li>
40                     [% CASE 'category' %]
41                         <li>
42                             <label for="categorycode_filter">Category:</label>
43                             <select id="categorycode_filter">
44                                 <option value="">Any</option>
45                                 [% FOREACH category IN categories %]
46                                     <option value="[% category.categorycode | html %]">[% category.description | html %]</option>
47                                 [% END %]
48                             </select>
49                         </li>
50                     [% END %]
51                 [% END %]
52             </ol>
53             <fieldset class="action">
54                 <input type="submit" value="Search" />
55             </fieldset>
56         </fieldset>
57     </form>
58 [% END %]
59
60 [% BLOCK patron_search_table %]
61
62     [% IF filter == 'suggestions_managers' %]
63         <div class="hint">Only staff with superlibrarian or suggestions_manage permissions are returned in the search results</div>
64     [% ELSIF filter == 'orders_managers' %]
65         <div class="hint">Only staff with superlibrarian or acquisitions permissions (or order_manage permission if granular permissions are enabled) are returned in the search results</div>
66     [% ELSIF filter == 'funds_owners' OR filter == 'funds_users' %]
67         <div class="hint">Only staff with superlibrarian or acquisitions permissions (or budget_modify permission if granular permissions are enabled) are returned in the search results</div>
68     [% END %]
69
70     <div class="browse">
71         Browse by last name:
72         [% SET alphabet = Koha.Preference('alphabet').split(' ') %]
73         [% UNLESS alphabet.size %]
74             [% alphabet = ['A' .. 'Z'] %]
75         [% END %]
76         [% FOREACH letter IN alphabet %]
77             <a href="#" class="filterByLetter">[% letter | html %]</a>
78         [% END %]
79     </div>
80
81     <div id="[% table_id | html %]_search_results" style="display:none;">
82
83         <div id="info" class="dialog message" style="display: none;"></div>
84         <div id="error" class="dialog alert" style="display: none;"></div>
85
86
87         <input type="hidden" id="firstletter_filter" value="" />
88         [% IF open_on_row_click %]
89         <table id="[% table_id | html %]" class="selections-table">
90         [% ELSE %]
91         <table id="[% table_id | html %]">
92         [% END %]
93             <thead>
94                 <tr>
95                     [% FOR column IN columns %]
96                         [% SWITCH column %]
97                             [% CASE 'cardnumber' %]<th>Card</th>
98                             [% CASE 'dateofbirth' %]<th>Date of birth</th>
99                             [% CASE 'name' %]<th>Name</th>
100                             [% CASE 'name-address' %]<th>Name</th>
101                             [% CASE 'address' %]<th>Address</th>
102                             [% CASE 'address-library' %]<th>Address</th>
103                             [% CASE 'branch' %]<th data-filter="libraries">Library</th>
104                             [% CASE 'category' %]<th data-filter="categories">Category</th>
105                             [% CASE 'dateexpiry' %]<th>Expires on</td>
106                             [% CASE 'borrowernotes' %]<th>Notes</th>
107                             [% CASE 'phone' %]<th>Phone</th>
108                             [% CASE 'checkouts' %]<th>Checkouts</th>
109                             [% CASE 'account_balance' %]<th>Fines</th>
110                             [% CASE 'action' %]<th>&nbsp;</th>
111                         [% END %]
112                     [% END %]
113                 </tr>
114               </thead>
115             <tbody></tbody>
116         </table>
117     </div>
118
119 <!-- Patron preview modal -->
120 <div class="modal" id="patronPreview" tabindex="-1" role="dialog" aria-labelledby="patronPreviewLabel">
121     <div class="modal-dialog" role="document">
122         <div class="modal-content">
123             <div class="modal-header">
124                 <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
125                 <h4 class="modal-title" id="patronPreviewLabel"></h4>
126             </div>
127             <div class="modal-body">
128                 <div id="loading">
129                     <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> Loading
130                 </div>
131             </div>
132             <div class="modal-footer">
133                 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
134             </div>
135         </div>
136     </div>
137 </div>
138
139 [% END %]
140
141 [% BLOCK patron_search_js %]
142
143     [% IF redirect_if_one_result && !redirect_url %]
144         <script>console.log("Wrong call of patron_searh_js - missing redirect_url");</script>
145     [% END %]
146     <script>
147         let categories = [% To.json(categories) | $raw %];
148         let categories_map = categories.reduce((map, c) => {
149             map[c.categorycode] = c;
150             return map;
151         }, {});
152         let libraries  = [% To.json(libraries) | $raw %];
153         let libraries_map = libraries.reduce((map, l) => {
154             map[l.branchcode] = l;
155             return map;
156         }, {});
157
158         [% IF Koha.Preference('ExtendedPatronAttributes') && extended_attribute_types %]
159             let extended_attribute_types = [% To.json(extended_attribute_types || []) | $raw %];
160         [% END %]
161
162     </script>
163
164     [% INCLUDE 'datatables.inc' %]
165     [% INCLUDE 'js-date-format.inc' %]
166     [% INCLUDE 'js-patron-get-age.inc' %]
167     [% INCLUDE 'js-patron-format.inc' %]
168     [% INCLUDE 'js-patron-format-address.inc' %]
169
170     <script>
171         var first_draw = 0;
172         let patrons_table;
173
174         $(document).ready(function(){
175
176
177             $("#info").hide();
178             $("#error").hide();
179
180             let additional_filters = {
181                 surname: function(){
182                     let start_with = $("#firstletter_filter").val()
183                     if (!start_with) return "";
184                     return { "like": start_with + "%" }
185                 },
186                 "-and": function(){
187                     let filter = $("#search_patron_filter").val();
188                     if (!filter) return "";
189                     [% SET search_fields = Koha.Preference('DefaultPatronSearchFields') || 'surname,firstname,othernames,cardnumber,userid' %]
190                     return [
191                         [% FOR search_field IN search_fields.split(',') %]
192                         {"me.[% search_field | html %]":{"like":"%"+filter+"%"}},
193                         [% END %]
194                         [% IF Koha.Preference('ExtendedPatronAttributes') && extended_attribute_types %]
195                         {
196                             "extended_attributes.value": { "like": "%" + filter + "%" },
197                             "extended_attributes.code": extended_attribute_types
198                         }
199                         [% END %]
200                     ];
201                 }
202             };
203             [% UNLESS default_sort_column %]
204                 [% default_sort_column = "name" %]
205             [% END %]
206             [% SET order_column_index = 0 %]
207             [% SET embed = ['extended_attributes'] %]
208             patrons_table = $("#[% table_id | html %]").kohaTable({
209                 "ajax": {
210                     [% SWITCH filter %]
211                     [% CASE 'suggestions_managers' %]
212                         "url": '/api/v1/suggestions/managers',
213                     [% CASE 'baskets_managers' %]
214                         "url": '/api/v1/acquisitions/baskets/managers',
215                     [% CASE 'funds_owners' %]
216                         "url": '/api/v1/acquisitions/funds/owners',
217                     [% CASE 'funds_users' %]
218                         "url": '/api/v1/acquisitions/funds/users',
219                     [% CASE %]
220                         "url": '/api/v1/patrons',
221                     [% END %]
222                     "dataSrc": function ( json ) {
223                         [% IF redirect_if_one_result %]
224                             // redirect if there is only 1 result.
225                             if ( first_draw && json.recordsFiltered == 1 ) {
226                                 document.location.href = '[% redirect_url | url %]&borrowernumber=' + json.data[0].patron_id;
227                                 return false;
228                             }
229                             first_draw = 0;
230                         [% END %]
231                         return json.data;
232                     }
233                 },
234                 "drawCallback": function( settings ) {
235                     var api = this.api();
236                     var data = api.data();
237                     if ( data.length == 0 ) return;
238
239                     [% IF open_on_row_click %]
240                     $.each($(this).find("tbody tr"), function(index, tr) {
241                         let url = "[% on_click_url | url %]&borrowernumber=" + data[index].patron_id;
242                         $(tr).off('click').on('click', function() {
243                             document.location.href = url;
244                         }).addClass('clickable');
245                         $(tr).find("a.patron_name").attr('href', url);
246                     });
247                     [% ELSE %]
248                     $.each($(this).find("tbody tr"), function(index, tr) {
249                         $(tr).find("a.patron_name").addClass("patron_preview");
250                     });
251                     [% END %]
252                 },
253                 "iDeferLoading": 0,
254                 "columns": [
255                     [% FOR column IN columns %]
256                         [% IF default_sort_column == column %]
257                             [% order_column_index = loop.count - 1%]
258                         [% END %]
259                         [% SWITCH column %]
260                             [% CASE 'checkbox' %]
261                             {
262                                 "data": "borrowernumber",
263                                 "searchable": true,
264                                 "orderable": true,
265                                 "render": function( data, type, row, meta ) {
266                                     return "<input type=\"checkbox\" name="data ? escape_str($date(data) + " (" + _("%s years").format($get_age(data)) + ")") : "";
267
268                                     return "<label for='check" + data + "' class='content_hidden'>" + _("Select patron") + "</label><input type='checkbox' id='check" + data + "' class='selection' name='borrowernumber' value='" + data + "' />",
269                                 }
270                             }
271                             [% CASE 'cardnumber' %]
272                             {
273                                 "data": "cardnumber",
274                                 "searchable": true,
275                                 "orderable": true
276                             }
277                             [% CASE 'dateofbirth' %]
278                             {
279                                 "data": "date_of_birth",
280                                 "searchable": true,
281                                 "orderable": true,
282                                 "render": function( data, type, row, meta ) {
283                                     return data ? escape_str($date(data) + " (" + _("%s years").format($get_age(data)) + ")") : "";
284                                 }
285                             }
286                             [% CASE 'address' %]
287                             {
288                                 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
289                                 "searchable": true,
290                                 "orderable": true,
291                                  "render": function( data, type, row, meta ) {
292                                     let r = '<div class="address"><ul>';
293                                     r += $format_address(row, { no_line_break: 1 });
294                                     r += '</div></ul>';
295                                     return r;
296                                 }
297                             }
298                             [% CASE 'address-library' %]
299                             {
300                                 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
301                                 "searchable": true,
302                                 "orderable": true,
303                                 "render": function( data, type, row, meta ) {
304                                     let r = '<div class="address"><ul>';
305                                     r += $format_address(row, { no_line_break: 1 });
306                                     r += '</div></ul>';
307                                     r += " " + escape_str(libraries_map[row.library_id].branchname);
308                                     return r;
309                                 }
310                             }
311                             [% CASE 'name-address' %]
312                             {
313                                 "data": "me.firstname:me.surname:me.othernames:me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
314                                 "searchable": true,
315                                 "orderable": true,
316                                 "render": function( data, type, row, meta ) {
317                                     let patron_id = encodeURIComponent(row.patron_id);
318                                     let r = '';
319                                     [% IF ! open_on_row_click %]
320                                     r += "<a href=\"/cgi-bin/koha/members/moremember.pl?borrowernumber=" + patron_id + "\" class=\"patron_name\" data-borrowernumber=\"" + patron_id + "\" style=\"white-space:nowrap\">" + $patron_to_html(row, { invert_name: 1 }) + "</a>";
321                                     [% ELSE %]
322                                     r += $patron_to_html(row, { invert_name: 1 });
323                                     [% END %]
324                                     r += '<br/>';
325                                     r += '<div class="address"><ul>';
326                                     r += $format_address(row, { no_line_break: 1 });
327
328                                     if ( row.email ) {
329                                         r += "<li>" + _("Email: ") + "<a href='mailto:" + encodeURIComponent(row.email) + "'>" + escape_str(row.email) + "</a></li>";
330                                     }
331                                     r += '</ul></div>'
332
333                                     return r;
334                                 }
335                             }
336                             [% CASE 'name' %]
337                             {
338                                 "data": "me.firstname:me.surname:me.othernames",
339                                 "searchable": true,
340                                 "orderable": true,
341                                 "render": function( data, type, row, meta ) {
342                                     let patron_id = encodeURIComponent(row.patron_id);
343                                     return "<a href=\"/cgi-bin/koha/members/moremember.pl?borrowernumber=" + patron_id + "\" class=\"patron_name\" data-borrowernumber=\"" + patron_id + "\" style=\"white-space:nowrap\">" + $patron_to_html(row, { invert_name: 1 }) + "</a>";
344                                 }
345                             }
346                             [% CASE 'branch' %]
347                             {
348                                 "data": "library_id",
349                                 "searchable": true,
350                                 "orderable": true,
351                                 "render": function( data, type, row, meta ) {
352                                     return escape_str(libraries_map[data].branchname);
353                                 }
354                             }
355                             [% CASE 'category' %]
356                             {
357                                 "data": "category_id",
358                                 "searchable": true,
359                                 "orderable": true,
360                                 "render": function( data, type, row, meta ) {
361                                     return escape_str(categories_map[data].description);
362                                 }
363                             }
364                             [% CASE 'dateexpiry' %]
365                             {
366                                 "data": "expiry_date",
367                                 "searchable": true,
368                                 "orderable": true,
369                                 "render": function( data, type, row, meta ) {
370                                     return data ? escape_str($date(data)) : "";
371                                 }
372                             }
373                             [% CASE 'borrowernotes' %]
374                             {
375                                 "data": "staff_notes",
376                                 "searchable": true,
377                                 "orderable": true,
378                                 [%# We don't escape here, we allow html tag in staff notes %]
379                             }
380                             [% CASE 'phone' %]
381                             {
382                                 "data": "phone",
383                                 "searchable": true,
384                                 "orderable": true,
385                                 "render": function( data, type, row, meta ) {
386                                     return escape_str(data);
387                                 }
388                             }
389                             [% CASE 'checkouts' %][% embed.push('checkouts+count', 'overdues+count') %]
390                             {
391                                 "data": "",
392                                 "searchable": false,
393                                 "orderable": false,
394                                 "render": function( data, type, row, meta ) {
395                                     if ( row.overdues_count ) {
396                                         return "<span class='overdue'><strong>"+row.overdues_count + "</strong></span>";
397                                     } else {
398                                         return "0 / " + row.checkouts_count;
399                                     }
400                                 }
401                             }
402                             [% CASE 'account_balance' %][% embed.push('account_balance') %]
403                             {
404                                 "data": "",
405                                 "searchable": false,
406                                 "orderable": false,
407                                 "render": function( data, type, row, meta ) {
408                                     let r = "<span style='text-align: right; display: block;'><a href=\"/cgi-bin/koha/members/boraccount.pl?borrowernumber="+row.patron_id+"\">";
409                                     let balance_str = row.account_balance || 0;
410                                     balance_str = balance_str.escapeHtml().format_price();
411                                     if ( row.account_balance < 0 ) {
412                                         // FIXME Format price here
413                                         r += "<span class='credit'>" + balance_str + "</span>";
414                                     } else if ( row.account_balance > 0 ) {
415                                         r += "<span class='debit'><strong>" + balance_str  + "</strong></span>"
416                                     } else {
417                                         r += balance_str;
418                                     }
419                                     r += "</a></span>";
420                                     return r;
421                                 }
422                             }
423
424                             [% CASE 'action' %]
425                             {
426                                 "data": function( row, type, val, meta ) {
427
428                                     let patron_id = encodeURIComponent(row.patron_id);
429                                     [% IF selection_type == 'select' %]
430                                         return '<a href="#" class="btn btn-default btn-xs select_user" data-borrowernumber="' + patron_id + '">Select</a><input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '" value=\''+JSON.stringify(row)+'\' />';
431                                     [% ELSE %]
432                                         return '<a href="#" class="btn btn-default btn-xs add_user" data-borrowernumber="' + patron_id + '" data-firstname="' + encodeURIComponent(row.firstname) + '" data-surname="' + encodeURIComponent(row.surname) + '">Add</a><input type="hidden" id="borrower_data' + patron_id + '" name="borrower_data'+ patron_id + '" />';
433                                     [% END %]
434                                 },
435                                 "searchable": false,
436                                 "orderable": false
437                             }
438                         [% END %]
439                         [% UNLESS loop.last %],[% END %]
440                     [% END %]
441                 ],
442                 'embed': [% To.json(embed) | $raw %],
443                 "order": [[ [% order_column_index | html %], "asc" ]],
444                 'bAutoWidth': false,
445                 'sPaginationType': 'full_numbers',
446                 "iDisplayLength": [% Koha.Preference('PatronsPerPage') | html %],
447             }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters);
448
449             $("#patron_search_form").on('submit', filter);
450             $(".filterByLetter").on("click",function(e){
451                 e.preventDefault();
452                 filterByFirstLetterSurname($(this).text());
453             });
454             $("body").on("click",".add_user",function(e){
455                 e.preventDefault();
456                 var borrowernumber = $(this).data("borrowernumber");
457                 var firstname = $(this).data("firstname");
458                 var surname = $(this).data("surname");
459                 add_user( borrowernumber, firstname + " " + surname );
460             });
461
462             $("body").on("click",".select_user",function(e){
463                 e.preventDefault();
464                 var borrowernumber = $(this).data("borrowernumber");
465                 var borrower_data = $("#borrower_data"+borrowernumber).val();
466                 select_user( borrowernumber, JSON.parse(borrower_data) );
467             });
468
469             $("body").on("click",".patron_preview", function( e ){
470                 e.preventDefault();
471                 var borrowernumber = $(this).data("borrowernumber");
472                 var page = "/cgi-bin/koha/members/moremember.pl?print=brief&borrowernumber=" + borrowernumber;
473                 $("#patronPreview .modal-body").load( page + " div.container-fluid" );
474                 $('#patronPreview').modal({show:true});
475             });
476
477             $("#patronPreview").on('hidden.bs.modal', function (e) {
478                 $("#patronPreview .modal-body").html("<img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> Loading");
479             });
480
481         });
482
483         function filter() {
484             $("#firstletter_filter").val('');
485             $("#[% table_id | html %]_search_results").show();
486
487             let table_dt = patrons_table.DataTable();
488             [% FOR c IN columns %]
489                 [% SWITCH c %]
490                 [% CASE 'branch' %]
491                     library_id = $("#branchcode_filter").val() || "";
492                     patrons_table.find('thead tr:eq(1) th[data-filter="libraries"] select').val(library_id);
493                     table_dt.column([% loop.count - 1 %]).search(library_id ? '^'+library_id+'$' : '');
494                 [% CASE 'category' %]
495                     let category_id = $("#categorycode_filter").val() || "";
496                     patrons_table.find('thead tr:eq(1) th[data-filter="categories"] select').val(category_id);
497                     table_dt.column([% loop.count - 1 %]).search(category_id ? '^'+category_id+'$' : '');
498                 [% END %]
499             [% END %]
500             table_dt.search("");
501             first_draw = 1; // Only redirect if we are coming from here
502             table_dt.draw();
503             return false;
504         }
505
506         // User has clicked on a letter
507         function filterByFirstLetterSurname(letter) {
508             $("#firstletter_filter").val(letter);
509             $("#[% table_id | html %]_search_results").show();
510             patrons_table.DataTable().draw();
511         }
512
513         // modify parent window owner element
514         [% IF selection_type == 'add' %]
515             function add_user(borrowernumber, borrowername) {
516                 var p = window.opener;
517                 // In one place (serials/routing.tt), the page is reload on every add
518                 // We have to wait for the page to be there
519                 function wait_for_opener () {
520                     if ( ! $(opener.document).find('body').size() ) {
521                         setTimeout(wait_for_opener, 500);
522                     } else {
523                         [%# Note that add_user could sent data instead of borrowername too %]
524                         $("#info").hide();
525                         $("#error").hide();
526                         if ( p.add_user(borrowernumber, borrowername) < 0 ) {
527                             $("#error").html(_("Patron '%s' is already in the list.").format(borrowername));
528                             $("#error").show();
529                         } else {
530                             $("#info").html(_("Patron '%s' added.").format(borrowername));
531                             $("#info").show();
532                         }
533                     }
534                 }
535                 wait_for_opener();
536             }
537         [% ELSIF selection_type == 'select' %]
538             function select_user(borrowernumber, data) {
539                 var p = window.opener;
540                 [%  IF callback %]
541                     p.[% callback | html %](borrowernumber, data);
542                 [%  ELSE %]
543                     p.select_user(borrowernumber, data);
544                 [%  END %]
545                 window.close();
546             }
547         [% END %]
548     </script>
549 [% END %]