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" />
14 [% BLOCK patron_search_filters %]
15 <form id="patron_search_form">
16 <fieldset class="brief">
17 <h3>Search for patron</h3>
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" />
25 [% FOR column IN columns %]
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>
35 [% FOREACH l IN libraries %]
36 <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
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>
53 <fieldset class="action">
54 <input type="submit" value="Search" />
60 [% BLOCK patron_search_table %]
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>
72 [% SET alphabet = Koha.Preference('alphabet').split(' ') %]
73 [% UNLESS alphabet.size %]
74 [% alphabet = ['A' .. 'Z'] %]
76 [% FOREACH letter IN alphabet %]
77 <a href="#" class="filterByLetter">[% letter | html %]</a>
81 <div id="[% table_id | html %]_search_results" style="display:none;">
83 <div id="info" class="dialog message" style="display: none;"></div>
84 <div id="error" class="dialog alert" style="display: none;"></div>
87 <input type="hidden" id="firstletter_filter" value="" />
88 [% IF open_on_row_click %]
89 <table id="[% table_id | html %]" class="selections-table">
91 <table id="[% table_id | html %]">
95 [% FOR column IN columns %]
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> </th>
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">×</span></button>
125 <h4 class="modal-title" id="patronPreviewLabel"></h4>
127 <div class="modal-body">
129 <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> Loading
132 <div class="modal-footer">
133 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
141 [% BLOCK patron_search_js %]
143 [% IF redirect_if_one_result && !redirect_url %]
144 <script>console.log("Wrong call of patron_searh_js - missing redirect_url");</script>
147 let categories = [% To.json(categories) | $raw %];
148 let categories_map = categories.reduce((map, c) => {
149 map[c.categorycode] = c;
152 let libraries = [% To.json(libraries) | $raw %];
153 let libraries_map = libraries.reduce((map, l) => {
154 map[l.branchcode] = l;
158 [% IF Koha.Preference('ExtendedPatronAttributes') && extended_attribute_types %]
159 let extended_attribute_types = [% To.json(extended_attribute_types || []) | $raw %];
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' %]
174 $(document).ready(function(){
180 let additional_filters = {
182 let start_with = $("#firstletter_filter").val()
183 if (!start_with) return "";
184 return { "like": start_with + "%" }
187 let filter = $("#search_patron_filter").val();
188 if (!filter) return "";
189 [% SET search_fields = Koha.Preference('DefaultPatronSearchFields') || 'surname,firstname,othernames,cardnumber,userid' %]
191 [% FOR search_field IN search_fields.split(',') %]
192 {"me.[% search_field | html %]":{"like":"%"+filter+"%"}},
194 [% IF Koha.Preference('ExtendedPatronAttributes') && extended_attribute_types %]
196 "extended_attributes.value": { "like": "%" + filter + "%" },
197 "extended_attributes.code": extended_attribute_types
203 [% UNLESS default_sort_column %]
204 [% default_sort_column = "name" %]
206 [% SET order_column_index = 0 %]
207 [% SET embed = ['extended_attributes'] %]
208 patrons_table = $("#[% table_id | html %]").kohaTable({
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',
220 "url": '/api/v1/patrons',
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;
234 "drawCallback": function( settings ) {
235 var api = this.api();
236 var data = api.data();
237 if ( data.length == 0 ) return;
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);
248 $.each($(this).find("tbody tr"), function(index, tr) {
249 $(tr).find("a.patron_name").addClass("patron_preview");
255 [% FOR column IN columns %]
256 [% IF default_sort_column == column %]
257 [% order_column_index = loop.count - 1%]
260 [% CASE 'checkbox' %]
262 "data": "borrowernumber",
265 "render": function( data, type, row, meta ) {
266 return "<input type=\"checkbox\" name="data ? escape_str($date(data) + " (" + _("%s years").format($get_age(data)) + ")") : "";
268 return "<label for='check" + data + "' class='content_hidden'>" + _("Select patron") + "</label><input type='checkbox' id='check" + data + "' class='selection' name='borrowernumber' value='" + data + "' />",
271 [% CASE 'cardnumber' %]
273 "data": "cardnumber",
277 [% CASE 'dateofbirth' %]
279 "data": "date_of_birth",
282 "render": function( data, type, row, meta ) {
283 return data ? escape_str($date(data) + " (" + _("%s years").format($get_age(data)) + ")") : "";
288 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
291 "render": function( data, type, row, meta ) {
292 let r = '<div class="address"><ul>';
293 r += $format_address(row, { no_line_break: 1 });
298 [% CASE 'address-library' %]
300 "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
303 "render": function( data, type, row, meta ) {
304 let r = '<div class="address"><ul>';
305 r += $format_address(row, { no_line_break: 1 });
307 r += " " + escape_str(libraries_map[row.library_id].branchname);
311 [% CASE 'name-address' %]
313 "data": "me.firstname:me.surname:me.othernames:me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country",
316 "render": function( data, type, row, meta ) {
317 let patron_id = encodeURIComponent(row.patron_id);
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>";
322 r += $patron_to_html(row, { invert_name: 1 });
325 r += '<div class="address"><ul>';
326 r += $format_address(row, { no_line_break: 1 });
329 r += "<li>" + _("Email: ") + "<a href='mailto:" + encodeURIComponent(row.email) + "'>" + escape_str(row.email) + "</a></li>";
338 "data": "me.firstname:me.surname:me.othernames",
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>";
348 "data": "library_id",
351 "render": function( data, type, row, meta ) {
352 return escape_str(libraries_map[data].branchname);
355 [% CASE 'category' %]
357 "data": "category_id",
360 "render": function( data, type, row, meta ) {
361 return escape_str(categories_map[data].description);
364 [% CASE 'dateexpiry' %]
366 "data": "expiry_date",
369 "render": function( data, type, row, meta ) {
370 return data ? escape_str($date(data)) : "";
373 [% CASE 'borrowernotes' %]
375 "data": "staff_notes",
378 [%# We don't escape here, we allow html tag in staff notes %]
385 "render": function( data, type, row, meta ) {
386 return escape_str(data);
389 [% CASE 'checkouts' %][% embed.push('checkouts+count', 'overdues+count') %]
394 "render": function( data, type, row, meta ) {
395 if ( row.overdues_count ) {
396 return "<span class='overdue'><strong>"+row.overdues_count + "</strong></span>";
398 return "0 / " + row.checkouts_count;
402 [% CASE 'account_balance' %][% embed.push('account_balance') %]
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>"
426 "data": function( row, type, val, meta ) {
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)+'\' />';
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 + '" />';
439 [% UNLESS loop.last %],[% END %]
442 'embed': [% To.json(embed) | $raw %],
443 "order": [[ [% order_column_index | html %], "asc" ]],
445 'sPaginationType': 'full_numbers',
446 "iDisplayLength": [% Koha.Preference('PatronsPerPage') | html %],
447 }, typeof table_settings !== 'undefined' ? table_settings : null, 1, additional_filters);
449 $("#patron_search_form").on('submit', filter);
450 $(".filterByLetter").on("click",function(e){
452 filterByFirstLetterSurname($(this).text());
454 $("body").on("click",".add_user",function(e){
456 var borrowernumber = $(this).data("borrowernumber");
457 var firstname = $(this).data("firstname");
458 var surname = $(this).data("surname");
459 add_user( borrowernumber, firstname + " " + surname );
462 $("body").on("click",".select_user",function(e){
464 var borrowernumber = $(this).data("borrowernumber");
465 var borrower_data = $("#borrower_data"+borrowernumber).val();
466 select_user( borrowernumber, JSON.parse(borrower_data) );
469 $("body").on("click",".patron_preview", function( e ){
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});
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");
484 $("#firstletter_filter").val('');
485 $("#[% table_id | html %]_search_results").show();
487 let table_dt = patrons_table.DataTable();
488 [% FOR c IN columns %]
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+'$' : '');
501 first_draw = 1; // Only redirect if we are coming from here
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();
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);
523 [%# Note that add_user could sent data instead of borrowername too %]
526 if ( p.add_user(borrowernumber, borrowername) < 0 ) {
527 $("#error").html(_("Patron '%s' is already in the list.").format(borrowername));
530 $("#info").html(_("Patron '%s' added.").format(borrowername));
537 [% ELSIF selection_type == 'select' %]
538 function select_user(borrowernumber, data) {
539 var p = window.opener;
541 p.[% callback | html %](borrowernumber, data);
543 p.select_user(borrowernumber, data);