From e9ec598fcb371e8da0fff2ba8c68ce944abecbfb Mon Sep 17 00:00:00 2001 From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> Date: Wed, 2 Feb 2022 18:00:58 +0100 Subject: [PATCH] Bug 30055: Make patron searches use the REST API This patch will rewrite some of our patron searches to make them use the REST API routes (and so the powerful the DataTables wrapper which will bring all the nice DT feature to filter, sort, etc.) The patron searches we will take into account here are those that we use to select a patron in a pop-up: * Guarantor * Suggestion's manager * Patron's card * Serial routing list * Users to notify when order is received * Manager of an acquisition basket * Owner and users of a fund Regarding permissions there are two main problematics: * Filter a patron set by patrons having a specific subpermissions (in case of adding a manager to a suggestion or when we deal with acquisition and funds). We added a new Koha::Patrons->filter_by_have_subpermission method that will take in parameter a subpermission. To make thing transparent for the callers we are adding new routes, like /suggestions/managers to list the possible managers of suggestions. * Restrict/allow access to the default patron searches /patrons We need to access it when a logged in patron does not have borrowers permission. Ideally we need a separate "search_borrowers" subpermissions but it's considered outside the scope of this change. For each patch you will take care of testing the different permissions that are into effect (either for the logged in patron or the patrons returned by the search). The tables should contain the same columns as prior to this patch, except for "categories" and "library". We have the filter on top of the page and so we need to add them to the table as new columns if they weren't there before. Test plan (for this patch): Search for guarantor and select Test plan (for all patches): Add/Select patrons from the correct place where you can search for patrons, play extensively with the filters/pagination/etc Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org> Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> --- .../prog/en/includes/js-patron-format.inc | 2 +- .../prog/en/modules/common/patron_search.tt | 312 --------------- .../prog/en/modules/members/search.tt | 368 ++++++++++++++++++ .../members/tables/guarantor_search.tt | 36 -- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 63 ++- koha-tmpl/intranet-tmpl/prog/js/members.js | 8 +- members/{guarantor_search.pl => search.pl} | 22 +- 7 files changed, 433 insertions(+), 378 deletions(-) delete mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/common/patron_search.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt delete mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/guarantor_search.tt rename members/{guarantor_search.pl => search.pl} (69%) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc index 4dd4798564..0c1daae17f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/js-patron-format.inc @@ -26,7 +26,7 @@ firstname += ' (' + escape_str(patron.other_name) + ')'; } if ( config && config.invert_name ) { - name = surname + ', ' + firstname; + name = surname + ( firstname ? ', ' + firstname : '' ); } else { name = firstname + ' ' + surname; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/common/patron_search.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/common/patron_search.tt deleted file mode 100644 index 9009d7a187..0000000000 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/common/patron_search.tt +++ /dev/null @@ -1,312 +0,0 @@ -[% USE raw %] -[% USE Asset %] -[% USE Koha %] -[% USE Branches %] -[% SET footerjs = 1 %] -[% INCLUDE 'doc-head-open.inc' %] -<title>Patron search › Koha</title> -[% INCLUDE 'doc-head-close.inc' %] -<style> .modal-body .close { display: none; } </style> -</head> - -<body id="common_patron_search" class="common"> -<div id="patron_search" class="yui-t7"> - <div class="container-fluid"> - - <form id="searchform"> - <fieldset class="brief"> - <h3>Search for patron</h3> - <ol> - <li> - <label for="searchmember_filter">Search:</label> - <input type="text" id="searchmember_filter" value="[% searchmember | html %]" class="focus" /> - </li> - <li> - <label for="categorycode_filter">Category:</label> - <select id="categorycode_filter"> - <option value="">Any</option> - [% FOREACH category IN categories %] - <option value="[% category.categorycode | html %]">[% category.description | html %]</option> - [% END %] - </select> - </li> - <li> - <label for="branchcode_filter">Library:</label> - <select id="branchcode_filter"> - [% SET libraries = Branches.all( only_from_group => 1 ) %] - [% IF libraries.size != 1 %] - <option value="">Any</option> - [% END %] - [% FOREACH l IN libraries %] - <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option> - [% END %] - </select> - </li> - </ol> - <fieldset class="action"> - <input type="submit" value="Search" /> - </fieldset> - </fieldset> - </form> - - [% IF patrons_with_acq_perm_only %] - <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> - [% END %] - - [% IF patrons_with_suggestion_perm_only %] - <div class="hint">Only staff with superlibrarian or suggestions_manage permissions are returned in the search results</div> - [% END %] - - <div class="browse"> - Browse by last name: - [% FOREACH letter IN alphabet.split(' ') %] - <a href="#" class="filterByLetter">[% letter | html %]</a> - [% END %] - </div> - - <div id="info" class="dialog message"></div> - <div id="error" class="dialog alert"></div> - - <input type="hidden" id="firstletter_filter" value="" /> - <div id="searchresults"> - <table id="memberresultst"> - <thead> - <tr> - [% FOR column IN columns %] - [% SWITCH column %] - [% CASE 'cardnumber' %]<th>Card</th> - [% CASE 'dateofbirth' %]<th>Date of birth</th> - [% CASE 'address' %]<th>Address</th> - [% CASE 'name' %]<th>Name</th> - [% CASE 'branch' %]<th>Library</th> - [% CASE 'category' %]<th>Category</th> - [% CASE 'dateexpiry' %]<th>Expires on</td> - [% CASE 'borrowernotes' %]<th>Notes</th> - [% CASE 'action' %]<th> </th> - [% END %] - [% END %] - </tr> - </thead> - <tbody></tbody> - </table> - </div> - -<div id="closewindow"><a href="#" class="btn btn-default btn-default close">Close</a></div> - -<!-- Patron preview modal --> -<div class="modal" id="patronPreview" tabindex="-1" role="dialog" aria-labelledby="patronPreviewLabel"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 class="modal-title" id="patronPreviewLabel"></h4> - </div> - <div class="modal-body"> - <div id="loading"> - <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> Loading - </div> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> - </div> - </div> - </div> -</div> - -</div> -</div> - -[% MACRO jsinclude BLOCK %] - [% INCLUDE 'datatables.inc' %] - - <script> - var search = 1; - $(document).ready(function(){ - $("#info").hide(); - $("#error").hide(); - - [% IF view != "show_results" %] - $("#searchresults").hide(); - search = 0; - [% END %] - - // Apply DataTables on the results table - dtMemberResults = $("#memberresultst").dataTable($.extend(true, {}, dataTablesDefaults, { - 'bServerSide': true, - 'sAjaxSource': "/cgi-bin/koha/svc/members/search", - 'fnServerData': function(sSource, aoData, fnCallback) { - if ( ! search ) { - return; - } - aoData.push({ - 'name': 'searchmember', - 'value': $("#searchmember_filter").val() - },{ - 'name': 'firstletter', - 'value': $("#firstletter_filter").val() - },{ - 'name': 'categorycode', - 'value': $("#categorycode_filter").val() - },{ - 'name': 'branchcode', - 'value': $("#branchcode_filter").val() - },{ - 'name': 'name_sorton', - 'value': 'borrowers.surname borrowers.firstname' - },{ - 'name': 'cardnumber_sorton', - 'value': 'borrowers.cardnumber', - },{ - 'name': 'dateofbirth_sorton', - 'value': 'borrowers.dateofbirth', - },{ - 'name': 'dateexpiry_sorton', - 'value': 'borrowers.dateexpiry', - },{ - 'name': 'category_sorton', - 'value': 'categories.description', - },{ - 'name': 'branch_sorton', - 'value': 'branches.branchname' - },{ - 'name': 'borrowernotes_sorton', - 'value': 'borrowers.borrowernotes' - },{ - 'name': 'template_path', - 'value': '[% json_template | html %]', - },{ - 'name': 'selection_type', - 'value': '[% selection_type | html %]', - } - [% IF patrons_with_acq_perm_only %] - ,{ - 'name': 'has_permission', - 'value': 'acquisition.order_manage', - } - [% ELSIF patrons_with_suggestion_perm_only %] - ,{ - 'name': 'has_permission', - 'value': 'suggestions.suggestions_manage', - } - [% END %] - ); - $.ajax({ - 'dataType': 'json', - 'type': 'POST', - 'url': sSource, - 'data': aoData, - 'success': function(json){ - fnCallback(json); - } - }); - }, - 'aoColumns':[ - [% FOR column IN columns %] - [% IF column == 'action' %] - { 'mDataProp': 'dt_action', 'bSortable': false, 'sClass': 'actions' } - [% ELSIF column == 'address' %] - { 'mDataProp': 'dt_address', 'bSortable': false } - [% ELSE %] - { 'mDataProp': 'dt_[% column | html %]' } - [% END %] - [% UNLESS loop.last %],[% END %] - [% END %] - ], - 'bAutoWidth': false, - 'sPaginationType': 'full_numbers', - "iDisplayLength": [% Koha.Preference('PatronsPerPage') | html %], - 'aaSorting': [[[% aaSorting || 0 | html %], 'asc']], - 'bFilter': false, - 'bProcessing': true, - })); - - $("#searchform").on('submit', filter); - $(".filterByLetter").on("click",function(e){ - e.preventDefault(); - filterByFirstLetterSurname($(this).text()); - }); - $("body").on("click",".add_user",function(e){ - e.preventDefault(); - var borrowernumber = $(this).data("borrowernumber"); - var firstname = $(this).data("firstname"); - var surname = $(this).data("surname"); - add_user( borrowernumber, firstname + " " + surname ); - }); - - $("body").on("click",".select_user",function(e){ - e.preventDefault(); - var borrowernumber = $(this).data("borrowernumber"); - var borrower_data = $("#borrower_data"+borrowernumber).val(); - select_user( borrowernumber, JSON.parse(borrower_data) ); - }); - - $("body").on("click",".patron_preview", function( e ){ - e.preventDefault(); - var borrowernumber = $(this).data("borrowernumber"); - var page = "/cgi-bin/koha/members/moremember.pl?print=brief&borrowernumber=" + borrowernumber; - $("#patronPreview .modal-body").load( page + " div.container-fluid" ); - $('#patronPreview').modal({show:true}); - }); - - $("#patronPreview").on('hidden.bs.modal', function (e) { - $("#patronPreview .modal-body").html("<img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> Loading"); - }); - - }); - - function filter() { - search = 1; - $("#firstletter_filter").val(''); - $("#searchresults").show(); - dtMemberResults.fnDraw(); - return false; - } - - // User has clicked on a letter - function filterByFirstLetterSurname(letter) { - $("#firstletter_filter").val(letter); - search = 1; - $("#searchresults").show(); - dtMemberResults.fnDraw(); - } - - // modify parent window owner element - [% IF selection_type == 'add' %] - function add_user(borrowernumber, borrowername) { - var p = window.opener; - // In one place (serials/routing.tt), the page is reload on every add - // We have to wait for the page to be there - function wait_for_opener () { - if ( ! $(opener.document).find('body').size() ) { - setTimeout(wait_for_opener, 500); - } else { - [%# Note that add_user could sent data instead of borrowername too %] - $("#info").hide(); - $("#error").hide(); - if ( p.add_user(borrowernumber, borrowername) < 0 ) { - $("#error").html(_("Patron '%s' is already in the list.").format(borrowername)); - $("#error").show(); - } else { - $("#info").html(_("Patron '%s' added.").format(borrowername)); - $("#info").show(); - } - } - } - wait_for_opener(); - } - [% ELSIF selection_type == 'select' %] - function select_user(borrowernumber, data) { - var p = window.opener; - [% IF callback %] - p.[% callback | html %](borrowernumber, data); - [% ELSE %] - p.select_user(borrowernumber, data); - [% END %] - window.close(); - } - [% END %] - </script> -[% END %] - -[% SET popup_window = 1 %] -[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt new file mode 100644 index 0000000000..0bcc2cfd1d --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/search.tt @@ -0,0 +1,368 @@ +[% USE raw %] +[% USE To %] +[% USE Asset %] +[% USE Koha %] +[% USE Branches %] +[% USE Categories %] +[% SET footerjs = 1 %] +[% INCLUDE 'doc-head-open.inc' %] +[% SET libraries = Branches.all %] +[% SET categories = Categories.all.unblessed %] +<title>Patron search › Koha</title> +[% INCLUDE 'doc-head-close.inc' %] +<style> .modal-body .close { display: none; } </style> +</head> + +<body id="common_patron_search" class="common"> +<div id="patron_search" class="yui-t7"> + <div class="container-fluid"> + + <form id="searchform"> + <fieldset class="brief"> + <h3>Search for patron</h3> + <ol> + <li> + <label for="searchmember_filter">Search:</label> + <input type="text" id="searchmember_filter" value="[% searchmember | html %]" class="focus" /> + </li> + + [% FOR column IN columns %] + [% SWITCH column %] + [% CASE 'branch' %] + <li> + <label for="branchcode_filter">Library:</label> + <select id="branchcode_filter"> + [% SET libraries = Branches.all( only_from_group => 1 ) %] + [% IF libraries.size != 1 %] + <option value="">Any</option> + [% END %] + [% FOREACH l IN libraries %] + <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option> + [% END %] + </select> + </li> + [% CASE 'category' %] + <li> + <label for="categorycode_filter">Category:</label> + <select id="categorycode_filter"> + <option value="">Any</option> + [% FOREACH category IN categories %] + <option value="[% category.categorycode | html %]">[% category.description | html %]</option> + [% END %] + </select> + </li> + [% END %] + [% END %] + </ol> + <fieldset class="action"> + <input type="submit" value="Search" /> + </fieldset> + </fieldset> + </form> + + [% IF patrons_with_acq_perm_only %] + <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> + [% END %] + + [% IF patrons_with_suggestion_perm_only %] + <div class="hint">Only staff with superlibrarian or suggestions_manage permissions are returned in the search results</div> + [% END %] + + <div class="browse"> + Browse by last name: + [% FOREACH letter IN alphabet.split(' ') %] + <a href="#" class="filterByLetter">[% letter | html %]</a> + [% END %] + </div> + + <div id="info" class="dialog message"></div> + <div id="error" class="dialog alert"></div> + + <input type="hidden" id="firstletter_filter" value="" /> + <div id="searchresults"> + <table id="memberresultst"> + <thead> + <tr> + [% FOR column IN columns %] + [% SWITCH column %] + [% CASE 'cardnumber' %]<th>Card</th> + [% CASE 'dateofbirth' %]<th>Date of birth</th> + [% CASE 'address' %]<th>Address</th> + [% CASE 'name' %]<th>Name</th> + [% CASE 'branch' %]<th data-filter="libraries">Library</th> + [% CASE 'category' %]<th data-filter="categories">Category</th> + [% CASE 'dateexpiry' %]<th>Expires on</td> + [% CASE 'borrowernotes' %]<th>Notes</th> + [% CASE 'action' %]<th> </th> + [% END %] + [% END %] + </tr> + </thead> + <tbody></tbody> + </table> + </div> + +<div id="closewindow"><a href="#" class="btn btn-default btn-default close">Close</a></div> + +<!-- Patron preview modal --> +<div class="modal" id="patronPreview" tabindex="-1" role="dialog" aria-labelledby="patronPreviewLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="patronPreviewLabel"></h4> + </div> + <div class="modal-body"> + <div id="loading"> + <img src="[% interface | html %]/[% theme | html %]/img/spinner-small.gif" alt="" /> Loading + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + </div> + </div> + </div> +</div> + +</div> +</div> + +[% MACRO jsinclude BLOCK %] + <script> + let categories = [% To.json(categories) | $raw %]; + let categories_map = categories.reduce((map, c) => { + map[c.categorycode] = c; + return map; + }, {}); + let libraries = [% To.json(libraries) | $raw %]; + let libraries_map = libraries.reduce((map, l) => { + map[l.branchcode] = l; + return map; + }, {}); + </script> + [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'js-date-format.inc' %] + [% INCLUDE 'js-patron-get-age.inc' %] + [% INCLUDE 'js-patron-format.inc' %] + [% INCLUDE 'js-patron-format-address.inc' %] + + <script> + var search = 0; + let patrons_table; + $(document).ready(function(){ + $("#info").hide(); + $("#error").hide(); + + $("#searchresults").hide(); + + let additional_filters = { + surname: function(){ + let start_with = $("#firstletter_filter").val() + if (!start_with) return ""; + return { "like": start_with + "%" } + } + }; + patrons_table = $("#memberresultst").kohaTable({ + "ajax": { + "url": '/api/v1/patrons' + }, + "order": [[ 1, "asc" ]], + "iDeferLoading": 0, + "columns": [ + [% FOR column IN columns %] + [% SWITCH column %] + [% CASE 'cardnumber' %] + { + "data": "cardnumber", + "searchable": true, + "orderable": true + } + [% CASE 'dateofbirth' %] + { + "data": "date_of_birth", + "searchable": true, + "orderable": true, + "render": function( data, type, row, meta ) { + return data ? escape_str($date(data) + " (" + _("%s years").format($get_age(data)) + ")") : ""; + } + } + [% CASE 'address' %] + { + "data": "me.street_number:me.address:me.address2:me.city:me.state:me.postal_code:me.country", + "searchable": true, + "orderable": true, + "render": function( data, type, row, meta ) { + return escape_str($format_address(row) + " ") + escape_str(libraries_map[row.library_id].branchname); + } + } + [% CASE 'name' %] + { + "data": "me.firstname:me.surname", + "searchable": true, + "orderable": true, + "render": function( data, type, row, meta ) { + return $patron_to_html(row, { invert_name: 1 }); + } + } + [% CASE 'branch' %] + { + "data": "library_id", + "searchable": true, + "orderable": true, + "render": function( data, type, row, meta ) { + return escape_str(libraries_map[data].branchname); + } + } + [% CASE 'category' %] + { + "data": "category_id", + "searchable": true, + "orderable": true, + "render": function( data, type, row, meta ) { + return escape_str(categories_map[data].description); + } + } + [% CASE 'dateexpiry' %] + { + "data": "expiry_date", + "searchable": true, + "orderable": true, + "render": function( data, type, row, meta ) { + return data ? escape_str($date(data)) : ""; + } + } + [% CASE 'borrowernotes' %] + { + "data": "staff_notes", + "searchable": true, + "orderable": true, + [%# We don't escape here, we allow html tag in staff notes %] + } + [% CASE 'action' %] + { + "data": function( row, type, val, meta ) { + + let patron_id = encodeURIComponent(row.patron_id); + [% IF selection_type == 'select' %] + 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)+'\' />'; + [% ELSE %] + 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 + '" />'; + [% END %] + }, + "searchable": false, + "orderable": false + } + [% END %] + [% UNLESS loop.last %],[% END %] + [% END %] + ], + 'bAutoWidth': false, + 'sPaginationType': 'full_numbers', + "iDisplayLength": [% Koha.Preference('PatronsPerPage') | html %], + }, null, 1, additional_filters); + + $("#searchform").on('submit', filter); + $(".filterByLetter").on("click",function(e){ + e.preventDefault(); + filterByFirstLetterSurname($(this).text()); + }); + $("body").on("click",".add_user",function(e){ + e.preventDefault(); + var borrowernumber = $(this).data("borrowernumber"); + var firstname = $(this).data("firstname"); + var surname = $(this).data("surname"); + add_user( borrowernumber, firstname + " " + surname ); + }); + + $("body").on("click",".select_user",function(e){ + e.preventDefault(); + var borrowernumber = $(this).data("borrowernumber"); + var borrower_data = $("#borrower_data"+borrowernumber).val(); + select_user( borrowernumber, JSON.parse(borrower_data) ); + }); + + $("body").on("click",".patron_preview", function( e ){ + e.preventDefault(); + var borrowernumber = $(this).data("borrowernumber"); + var page = "/cgi-bin/koha/members/moremember.pl?print=brief&borrowernumber=" + borrowernumber; + $("#patronPreview .modal-body").load( page + " div.container-fluid" ); + $('#patronPreview').modal({show:true}); + }); + + $("#patronPreview").on('hidden.bs.modal', function (e) { + $("#patronPreview .modal-body").html("<img src=\"[% interface | html %]/[% theme | html %]/img/spinner-small.gif\" alt=\"\" /> Loading"); + }); + + }); + + function filter() { + search = 1; + $("#firstletter_filter").val(''); + $("#searchresults").show(); + + let table_dt = patrons_table.DataTable(); + [% FOR c IN columns %] + [% SWITCH c %] + [% CASE 'branch' %] + let library_id = $("#branchcode_filter").val(); + patrons_table.find('thead tr:eq(1) th[data-filter="libraries"] select').val(library_id); + table_dt.column([% loop.count - 1 %]).search(library_id); + [% CASE 'category' %] + let category_id = $("#categorycode_filter").val(); + patrons_table.find('thead tr:eq(1) th[data-filter="categories"] select').val(category_id); + table_dt.column([% loop.count - 1 %]).search(category_id); + [% END %] + [% END %] + table_dt.search($("#searchmember_filter").val()); + table_dt.draw(); + return false; + } + + // User has clicked on a letter + function filterByFirstLetterSurname(letter) { + $("#firstletter_filter").val(letter); + search = 1; + $("#searchresults").show(); + patrons_table.fnDraw(); + } + + // modify parent window owner element + [% IF selection_type == 'add' %] + function add_user(borrowernumber, borrowername) { + var p = window.opener; + // In one place (serials/routing.tt), the page is reload on every add + // We have to wait for the page to be there + function wait_for_opener () { + if ( ! $(opener.document).find('body').size() ) { + setTimeout(wait_for_opener, 500); + } else { + [%# Note that add_user could sent data instead of borrowername too %] + $("#info").hide(); + $("#error").hide(); + if ( p.add_user(borrowernumber, borrowername) < 0 ) { + $("#error").html(_("Patron '%s' is already in the list.").format(borrowername)); + $("#error").show(); + } else { + $("#info").html(_("Patron '%s' added.").format(borrowername)); + $("#info").show(); + } + } + } + wait_for_opener(); + } + [% ELSIF selection_type == 'select' %] + function select_user(borrowernumber, data) { + var p = window.opener; + [% IF callback %] + p.[% callback | html %](borrowernumber, data); + [% ELSE %] + p.select_user(borrowernumber, data); + [% END %] + window.close(); + } + [% END %] + </script> +[% END %] + +[% SET popup_window = 1 %] +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/guarantor_search.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/guarantor_search.tt deleted file mode 100644 index fd55506c11..0000000000 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/tables/guarantor_search.tt +++ /dev/null @@ -1,36 +0,0 @@ -[% USE raw %] -[% USE To %] -[% USE Branches %] -[% USE KohaDates %] -{ - "sEcho": [% sEcho | html %], - "iTotalRecords": [% iTotalRecords | html %], - "iTotalDisplayRecords": [% iTotalDisplayRecords | html %], - "aaData": [ - [% FOREACH data IN aaData %] - { - "dt_cardnumber": - "[% data.cardnumber | html %]", - "dt_name": - "[% INCLUDE 'patron-title.inc' borrowernumber = data.borrowernumber category_type = data.category_type firstname = data.firstname surname = data.surname othernames = data.othernames cardnumber = data.cardnumber invert_name = 1%]", - "dt_dateofbirth": - "[% INCLUDE 'patron-age.inc' patron = data %]", - "dt_address": - "[% INCLUDE escape_address data=data %]", - "dt_action": - "<a href=\"#\" class=\"btn btn-default btn-xs select_user\" data-borrowernumber=\"[% data.borrowernumber | html %]\">Select</a><input type=\"hidden\" id=\"borrower_data[% data.borrowernumber | html %]\" name=\"borrower_data[% data.borrowernumber | html %]\" value=\"[% To.json(data) | html %]\" />" - }[% UNLESS loop.last %],[% END %] - [% END %] - ] -} -[% BLOCK escape_address %] -[%~ SET address = data.streetnumber _ ' ' %] -[%~ IF data.address %][% SET address = address _ data.address _ ' ' %][% END %] -[%~ IF data.address2 %][% SET address = address _ data.address2 _ ' ' %][% END %] -[%~ IF data.city %][% SET address = address _ data.city _ ' ' %][% END %] -[%~ IF data.state %][% SET address = address _ data.state _ ' ' %][% END %] -[%~ IF data.zipcode %][% SET address = address _ data.zipcode _ ' ' %][% END %] -[%~ IF data.country %][% SET address = address _ data.country _ ' ' %][% END %] -[%~ SET address = address _ Branches.GetName( data.branchcode ) %] -[%~ address | html | $To ~%] -[% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 2d88fcdd05..c41866745e 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -599,7 +599,20 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { }); if ( default_filters ) { - and_query_parameters.push(default_filters); + let additional_filters = {}; + for ( f in default_filters ) { + if ( typeof(default_filters[f]) === 'function' ) { + let val = default_filters[f](); + if ( val != undefined && val != "" ) { + additional_filters[f] = val; + } + } else { + additional_filters[f] = default_filters[f]; + } + } + if ( Object.keys(additional_filters).length ) { + and_query_parameters.push(additional_filters); + } } query_parameters = and_query_parameters; if ( or_query_parameters.length) { @@ -775,16 +788,50 @@ jQuery.fn.dataTable.ext.errMode = function(settings, note, message) { $(this).find('thead tr:eq(1) th').each( function (i) { var is_searchable = table_dt.settings()[0].aoColumns[i].bSearchable; if ( is_searchable ) { - var title = $(this).text(); - var existing_search = table_dt.column(i).search(); - if ( existing_search ) { - $(this).html( '<input type="text" value="%s" style="width: 100%" />'.format(existing_search) ); + let input_type = 'input'; + if ( $(this).data('filter') ) { + input_type = 'select' + let filter_type = $(this).data('filter'); + if ( filter_type == 'libraries' ) { + var existing_search = table_dt.column(i).search(); + let select = $('<select><option value=""></option></select'); + + $(libraries).each(function(){ + let o = $('<option value="%s">%s</option>'.format(this.branchcode, this.branchname)); + if ( existing_search == this.branchcode ) { + o.prop("selected", "selected"); + } + o.appendTo(select); + }); + $(this).html( select ); + } else if ( filter_type == 'categories' ) { + var existing_search = table_dt.column(i).search(); + let select = $('<select><option value=""></option></select'); + + $(categories).each(function(){ + let o = $('<option value="%s">%s</option>'.format(this.categorycode, this.description)); + if ( existing_search == this.categorycode ) { + o.prop("selected", "selected"); + } + o.appendTo(select); + }); + $(this).html( select ); + } else { + console.log("Unknown filter " + filter_type); + return; + } } else { - var search_title = _("%s search").format(title); - $(this).html( '<input type="text" placeholder="%s" style="width: 100%" />'.format(search_title) ); + var title = $(this).text(); + var existing_search = table_dt.column(i).search(); + if ( existing_search ) { + $(this).html( '<input type="text" value="%s" style="width: 100%" />'.format(existing_search) ); + } else { + var search_title = _("%s search").format(title); + $(this).html( '<input type="text" placeholder="%s" style="width: 100%" />'.format(search_title) ); + } } - $( 'input', this ).on( 'keyup change', function () { + $( input_type, this ).on( 'keyup change', function () { if ( table_dt.column(i).search() !== this.value ) { table_dt .column(i) diff --git a/koha-tmpl/intranet-tmpl/prog/js/members.js b/koha-tmpl/intranet-tmpl/prog/js/members.js index a9e6a870ae..5559ac0363 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/members.js +++ b/koha-tmpl/intranet-tmpl/prog/js/members.js @@ -77,12 +77,12 @@ function update_category_code(category_code) { } function select_user(borrowernumber, borrower, relationship) { - let is_guarantor = $(`.guarantor-details[data-borrowernumber=${borrower.borrowernumber}]`).length; + let is_guarantor = $(`.guarantor-details[data-borrowernumber=${borrowernumber}]`).length; if ( is_guarantor ) { alert("Patron is already a guarantor for this patron"); } else { - $('#guarantor_id').val(borrower.borrowernumber); + $('#guarantor_id').val(borrowernumber); $('#guarantor_surname').val(borrower.surname); $('#guarantor_firstname').val(borrower.firstname); @@ -113,7 +113,7 @@ function select_user(borrowernumber, borrower, relationship) { fieldset.find('.new_guarantor_relationship').first().val( guarantor_relationship ); $('#relationship').find('option:eq(0)').prop('selected', true); - fieldset.find('.guarantor-details').first().attr( 'data-borrowernumber', borrower.borrowernumber ); + fieldset.find('.guarantor-details').first().attr( 'data-borrowernumber', borrowernumber ); $('#guarantor_relationships').append( fieldset ); fieldset.show(); @@ -197,7 +197,7 @@ $(document).ready(function(){ $('body').on('click', '#guarantor_search', function(e) { e.preventDefault(); - var newin = window.open('guarantor_search.pl','popup','width=800,height=600,resizable=no,toolbar=false,scrollbars=yes,top'); + var newin = window.open('/cgi-bin/koha/members/search.pl?columns=cardnumber,name,category,branch,dateofbirth,address,action','popup','width=800,height=600,resizable=no,toolbar=false,scrollbars=yes,top'); }); $('#guarantor_relationships').on('click', '.guarantor_cancel', function(e) { diff --git a/members/guarantor_search.pl b/members/search.pl similarity index 69% rename from members/guarantor_search.pl rename to members/search.pl index 40142eb913..630f509177 100755 --- a/members/guarantor_search.pl +++ b/members/search.pl @@ -2,8 +2,6 @@ # This file is part of Koha. # -# Copyright 2014 BibLibre -# # Koha is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or @@ -22,35 +20,25 @@ use Modern::Perl; use CGI qw ( -utf8 ); use C4::Auth qw( get_template_and_user ); use C4::Output qw( output_html_with_http_headers ); -use C4::Members; - -use Koha::Patron::Categories; my $input = CGI->new; -my $dbh = C4::Context->dbh; - my ( $template, $loggedinuser, $cookie, $staff_flags ) = get_template_and_user( - { template_name => "common/patron_search.tt", + { template_name => "members/search.tt", query => $input, type => "intranet", - flagsrequired => { borrowers => 'edit_borrowers' }, + flagsrequired => { catalogue => '*' }, } ); -my $q = $input->param('q') || ''; -my $op = $input->param('op') || ''; - my $referer = $input->referer(); -my $patron_categories = Koha::Patron::Categories->search_with_library_limits; +my @columns = split ',', $input->param('columns'); + $template->param( view => ( $input->request_method() eq "GET" ) ? "show_form" : "show_results", - columns => ['cardnumber', 'name', 'dateofbirth', 'address', 'action' ], - json_template => 'members/tables/guarantor_search.tt', + columns => \@columns, selection_type => 'select', alphabet => ( C4::Context->preference('alphabet') || join ' ', 'A' .. 'Z' ), - categories => $patron_categories, - aaSorting => 1, ); output_html_with_http_headers( $input, $cookie, $template->output ); -- 2.35.1