From f3c40f363fe240a123a7e1da5a80f246a273f985 Mon Sep 17 00:00:00 2001 From: Andrew Isherwood Date: Mon, 11 Mar 2019 11:56:28 +0000 Subject: [PATCH] Bug 18589: Show ILLs as part of patron profile This patch moves the display of a patron's ILL requests to be inline in the Patron Profile page, as per all other patron information. It includes a substantial refactor of koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt in that it moves the display of the illlist table (which is populated by an API request) into it's own separate include file. It also moves the datatable related Javascript for this table into it's own JS file. Doing this allows us to reuse both in both the new members/ill-requests.tt template and the pre-existing ill/ill-requests.tt template. To test: 1) Ensure ILL is enabled and your user has sufficient permissions 2) Ensure your user has some ILL requests 3) Navigate to the user's patron profile page 4) Click on the "Interlibrary loans" tab 5) Observe that the requests table is displayed inline 6) Observe that only your user's requests are displayed Signed-off-by: Niamh.Walker-Headon@it-tallaght.ie This is essentially a reimplementation of attachment 84796 to take into account all changes that had happened beneath this bug and also to ensure JS strings are translatable. --- Koha/REST/V1/Illrequests.pm | 10 +- ill/ill-requests.pl | 5 +- .../intranet-tmpl/prog/en/includes/circ-menu.inc | 2 +- .../prog/en/includes/ill-list-table-strings.inc | 19 + .../prog/en/includes/ill-list-table.inc | 42 ++ .../prog/en/modules/ill/ill-requests.tt | 590 +-------------------- .../prog/en/modules/members/ill-requests.tt | 63 +++ koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js | 533 +++++++++++++++++++ members/ill-requests.pl | 55 ++ 9 files changed, 743 insertions(+), 576 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table-strings.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/members/ill-requests.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js create mode 100755 members/ill-requests.pl diff --git a/Koha/REST/V1/Illrequests.pm b/Koha/REST/V1/Illrequests.pm index a5ec780d2e..c3c51a592d 100644 --- a/Koha/REST/V1/Illrequests.pm +++ b/Koha/REST/V1/Illrequests.pm @@ -55,7 +55,15 @@ sub list { } # Get all requests - my @requests = Koha::Illrequests->as_list; + # If necessary, only get those from a specified patron + my @requests; + if ($args->{borrowernumber}) { + @requests = Koha::Illrequests->search( + { borrowernumber => $args->{borrowernumber} } + ); + } else { + @requests = Koha::Illrequests->as_list; + } # Identify patrons & branches that # we're going to need and get them diff --git a/ill/ill-requests.pl b/ill/ill-requests.pl index 1598ea862c..45f99c2ca3 100755 --- a/ill/ill-requests.pl +++ b/ill/ill-requests.pl @@ -273,13 +273,12 @@ if ( $backends_available ) { my $active_filters = []; foreach my $filter(@{$possible_filters}) { if ($params->{$filter}) { - push @{$active_filters}, - { name => $filter, value => $params->{$filter}}; + push @{$active_filters}, "$filter=$params->{$filter}"; } } if (scalar @{$active_filters} > 0) { $template->param( - prefilters => $active_filters + prefilters => join(",", @{$active_filters}) ); } diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc index f6c3d5e284..0adbccee66 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc @@ -132,7 +132,7 @@ [% IF houseboundview %]
  • [% ELSE %]
  • [% END %]Housebound
  • [% END %] [% IF Koha.Preference('ILLModule') && CAN_user_ill %] -
  • Interlibrary loans
  • + [% IF illview %]
  • [% ELSE %]
  • [% END %]Interlibrary loans
  • [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table-strings.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table-strings.inc new file mode 100644 index 0000000000..3ef5e6e650 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table-strings.inc @@ -0,0 +1,19 @@ + diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table.inc new file mode 100644 index 0000000000..df26181f14 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table.inc @@ -0,0 +1,42 @@ +
    + [% IF prefilters.length > 0 %] + + [% ELSE %] +
    + [% END %] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Request IDAuthorTitleArticle titleIssueVolumeYearPagesTypeOrder IDPatronBibliographic recordBranchStatus Placed on Updated onReplied Completed onAccess URLCostCommentsOPAC notesStaff notesBackend
    +
    diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt index 85bb7cb2ea..a096f32015 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt @@ -507,43 +507,8 @@

    View ILL requests

    Details for all requests

    + [% INCLUDE 'ill-list-table.inc' %] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Request IDAuthorTitleArticle titleIssueVolumeYearPagesTypeOrder IDPatronBibliographic recordBranchStatus Placed on Updated onReplied Completed onAccess URLCostCommentsOPAC notesStaff notesBackend
    [% ELSE %] @@ -555,548 +520,31 @@ -[% TRY %] -[% PROCESS backend_jsinclude %] -[% CATCH %] -[% END %] - [% MACRO jsinclude BLOCK %] [% INCLUDE 'datatables.inc' %] [% INCLUDE 'columns_settings.inc' %] [% INCLUDE 'calendar.inc' %] [% Asset.js("lib/jquery/plugins/jquery.checkboxes.min.js") | $raw %] + [% INCLUDE 'ill-list-table-strings.inc' %] + [% Asset.js("js/ill-list-table.js") | $raw %] +[% END %] + +[% TRY %] +[% PROCESS backend_jsinclude %] +[% CATCH %] [% END %] [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/ill-requests.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/ill-requests.tt new file mode 100644 index 0000000000..cab9a6c733 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/ill-requests.tt @@ -0,0 +1,63 @@ +[% USE raw %] +[% USE Asset %] +[% USE Branches %] +[% USE Koha %] +[% USE KohaDates %] +[% SET footerjs = 1 %] +[% USE AuthorisedValues %] +[% USE ColumnsSettings %] + +[% INCLUDE 'doc-head-open.inc' %] +Koha › Patrons › ILL requests for [% INCLUDE 'patron-title.inc' no_html = 1 %] +[% INCLUDE 'doc-head-close.inc' %] + + + + [% INCLUDE 'header.inc' %] + [% INCLUDE 'patron-search.inc' %] + + + +
    + +
    +
    +
    +
    +

    ILL requests

    + [% INCLUDE 'ill-list-table.inc' %] +
    +
    +
    +
    + [% INCLUDE 'circ-menu.inc' %] +
    +
    + + [% MACRO jsinclude BLOCK %] + [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'columns_settings.inc' %] + [% INCLUDE 'calendar.inc' %] + [% Asset.js("lib/jquery/plugins/jquery.checkboxes.min.js") | $raw %] + + [% INCLUDE 'ill-list-table-strings.inc' %] + [% Asset.js("js/ill-list-table.js") | $raw %] + [% END %] + +[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js b/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js new file mode 100644 index 0000000000..41e7ecc956 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js @@ -0,0 +1,533 @@ +$(document).ready(function() { + + // Illview Datatable setup + + var table; + + // Filters that are active + var activeFilters = {}; + + // Get any prefilters + var prefilters = $('table#ill-requests').data('prefilters'); + + // Fields we need to expand (flatten) + var expand = [ + 'metadata', + 'patron', + 'library' + ]; + + // Expanded fields + // This is auto populated + var expanded = {}; + + // Filterable columns + var filterable = { + status: { + prep: function(tableData, oData) { + var uniques = {}; + tableData.forEach(function(row) { + var resolvedName; + if (row.status_alias) { + resolvedName = row.status_alias.lib; + } else { + resolvedName = getStatusName( + oData[0].capabilities[row.status].name + ); + } + uniques[resolvedName] = 1 + }); + Object.keys(uniques).sort().forEach(function(unique) { + $('#illfilter_status').append( + '' + ); + }); + }, + listener: function() { + var me = 'status'; + $('#illfilter_status').change(function() { + var sel = $('#illfilter_status option:selected').val(); + if (sel && sel.length > 0) { + activeFilters[me] = function() { + table.api().column(13).search(sel); + } + } else { + if (activeFilters.hasOwnProperty(me)) { + delete activeFilters[me]; + } + } + }); + }, + clear: function() { + $('#illfilter_status').val(''); + } + }, + pickupBranch: { + prep: function(tableData, oData) { + var uniques = {}; + tableData.forEach(function(row) { + uniques[row.library_branchname] = 1 + }); + Object.keys(uniques).sort().forEach(function(unique) { + $('#illfilter_branchname').append( + '' + ); + }); + }, + listener: function() { + var me = 'pickupBranch'; + $('#illfilter_branchname').change(function() { + var sel = $('#illfilter_branchname option:selected').val(); + if (sel && sel.length > 0) { + activeFilters[me] = function() { + table.api().column(12).search(sel); + } + } else { + if (activeFilters.hasOwnProperty(me)) { + delete activeFilters[me]; + } + } + }); + }, + clear: function() { + $('#illfilter_branchname').val(''); + } + }, + patron: { + listener: function() { + var me = 'patron'; + $('#illfilter_patron').change(function() { + var val = $('#illfilter_patron').val(); + if (val && val.length > 0) { + activeFilters[me] = function() { + table.api().column(10).search(val); + } + } else { + if (activeFilters.hasOwnProperty(me)) { + delete activeFilters[me]; + } + } + }); + }, + clear: function() { + $('#illfilter_patron').val(''); + } + }, + dateModified: { + clear: function() { + $('#illfilter_datemodified_start, #illfilter_datemodified_end').val(''); + } + }, + datePlaced: { + clear: function() { + $('#illfilter_dateplaced_start, #illfilter_dateplaced_end').val(''); + } + } + }; + + // Expand any fields we're expanding + var expandExpand = function(row) { + expand.forEach(function(thisExpand) { + if (row.hasOwnProperty(thisExpand)) { + if (!expanded.hasOwnProperty(thisExpand)) { + expanded[thisExpand] = []; + } + var expandObj = row[thisExpand]; + Object.keys(expandObj).forEach( + function(thisExpandCol) { + var expColName = thisExpand + '_' + thisExpandCol.replace(/\s/g,'_'); + // Keep a list of fields that have been expanded + // so we can create toggle links for them + if (expanded[thisExpand].indexOf(expColName) == -1) { + expanded[thisExpand].push(expColName); + } + expandObj[expColName] = + expandObj[thisExpandCol]; + delete expandObj[thisExpandCol]; + } + ); + $.extend(true, row, expandObj); + delete row[thisExpand]; + } + }); + }; + + // Strip the expand prefix if it exists, we do this for display + var stripPrefix = function(value) { + expand.forEach(function(thisExpand) { + var regex = new RegExp(thisExpand + '_', 'g'); + value = value.replace(regex, ''); + }); + return value; + }; + + // Our 'render' function for borrowerlink + var createPatronLink = function(data, type, row) { + var patronLink = ''; + if ( row.patron_firstname ) { + patronLink = patronLink + row.patron_firstname + ' '; + } + patronLink = patronLink + row.patron_surname + + ' (' + row.patron_cardnumber + ')' + ''; + return patronLink; + }; + + // Our 'render' function for biblio_id + var createBiblioLink = function(data, type, row) { + return (row.biblio_id) ? + '' + + row.biblio_id + + '' : ''; + }; + + // Our 'render' function for title + var createTitle = function(data, type, row) { + return ( + row.hasOwnProperty('metadata_container_title') && + row.metadata_container_title + ) ? row.metadata_container_title : row.metadata_title; + }; + + // Render function for request ID + var createRequestId = function(data, type, row) { + return row.id_prefix + row.illrequest_id; + }; + + // Render function for type + var createType = function(data, type, row) { + if (!row.hasOwnProperty('metadata_Type') || !row.metadata_Type) { + if (row.hasOwnProperty('medium') && row.medium) { + row.metadata_Type = row.medium; + } else { + row.metadata_Type = null; + } + } + return row.metadata_Type; + }; + + // Render function for request status + var createStatus = function(data, type, row, meta) { + if (row.status_alias) { + return row.status_alias.lib + ? row.status_alias.lib + : row.status_alias.authorised_value; + } else { + var origData = meta.settings.oInit.originalData; + if (origData.length > 0) { + var status_name = meta.settings.oInit.originalData[0].capabilities[ + row.status + ].name; + return getStatusName(status_name, row); + } else { + return ''; + } + } + }; + + var getStatusName = function(origName, row) { + switch( origName ) { + case "New request": + return ill_statuses.new; + case "Requested": + return ill_statuses.req; + case "Requested from partners": + var statStr = ill_statuses.genreq; + if ( + row.hasOwnProperty('requested_partners') && + row.requested_partners && + row.requested_partners.length > 0 + ) { + statStr += ' (' + row.requested_partners + ')'; + } + return statStr; + case "Request reverted": + return ill_statuses.rev; + case "Queued request": + return ill_statuses.que; + case "Cancellation requested": + return ill_statuses.canc; + case "Completed": + return ill_statuses.comp; + case "Delete request": + return ill_statuses.del; + default: + return origName; + } + }; + + // Render function for creating a row's action link + var createActionLink = function(data, type, row) { + return '' + ill_manage + ''; + }; + + // Columns that require special treatment + var specialCols = { + action: { + func: createActionLink, + skipSanitize: true + }, + illrequest_id: { + func: createRequestId + }, + status: { + func: createStatus + }, + biblio_id: { + name: ill_columns.biblio_id, + func: createBiblioLink, + skipSanitize: true + }, + metadata_title: { + func: createTitle + }, + metadata_Type: { + func: createType + }, + updated: { + name: ill_columns.updated + }, + patron: { + skipSanitize: true, + func: createPatronLink + } + }; + + // Display the modal containing request supplier metadata + $('#ill-request-display-log').on('click', function(e) { + e.preventDefault(); + $('#requestLog').modal({show:true}); + }); + + // Toggle request attributes in Illview + $('#toggle_requestattributes').on('click', function(e) { + e.preventDefault(); + $('#requestattributes').toggleClass('content_hidden'); + }); + + // Toggle new comment form in Illview + $('#toggle_addcomment').on('click', function(e) { + e.preventDefault(); + $('#addcomment').toggleClass('content_hidden'); + }); + + // Filter partner list + $('#partner_filter').keyup(function() { + var needle = $('#partner_filter').val(); + $('#partners > option').each(function() { + var regex = new RegExp(needle, 'i'); + if ( + needle.length == 0 || + $(this).is(':selected') || + $(this).text().match(regex) + ) { + $(this).show(); + } else { + $(this).hide(); + } + }); + }); + + // Display the modal containing request supplier metadata + $('#ill-request-display-metadata').on('click', function(e) { + e.preventDefault(); + $('#dataPreview').modal({show:true}); + }); + + // Allow us to chain Datatable render helpers together, so we + // can use our custom functions and render.text(), which + // provides us with data sanitization + $.fn.dataTable.render.multi = function(renderArray) { + return function(d, type, row, meta) { + for(var r = 0; r < renderArray.length; r++) { + var toCall = renderArray[r].hasOwnProperty('display') ? + renderArray[r].display : + renderArray[r]; + d = toCall(d, type, row, meta); + } + return d; + } + } + + // Get our data from the API and process it prior to passing + // it to datatables + var filterParam = prefilters ? '&' + prefilters : ''; + var ajax = $.ajax( + '/api/v1/illrequests?embed=metadata,patron,capabilities,library,status_alias,comments,requested_partners' + + filterParam + ).done(function() { + var data = JSON.parse(ajax.responseText); + // Make a copy, we'll be removing columns next and need + // to be able to refer to data that has been removed + var dataCopy = $.extend(true, [], data); + // Expand columns that need it and create an array + // of all column names + $.each(dataCopy, function(k, row) { + expandExpand(row); + }); + + // Assemble an array of column definitions for passing + // to datatables + var colData = []; + columns_settings.forEach(function(thisCol) { + var colName = thisCol.columnname; + // Create the base column object + var colObj = $.extend({}, thisCol); + colObj.name = colName; + colObj.className = colName; + colObj.defaultContent = ''; + + // We may need to process the data going in this + // column, so do it if necessary + if ( + specialCols.hasOwnProperty(colName) && + specialCols[colName].hasOwnProperty('func') + ) { + var renderArray = [ + specialCols[colName].func + ]; + if (!specialCols[colName].skipSanitize) { + renderArray.push( + $.fn.dataTable.render.text() + ); + } + + colObj.render = $.fn.dataTable.render.multi( + renderArray + ); + } else { + colObj.data = colName; + colObj.render = $.fn.dataTable.render.text() + } + // Make sure properties that aren't present in the API + // response are populated with null to avoid Datatables + // choking on their absence + dataCopy.forEach(function(thisData) { + if (!thisData.hasOwnProperty(colName)) { + thisData[colName] = null; + } + }); + colData.push(colObj); + }); + + // Initialise the datatable + table = KohaTable("ill-requests", { + 'aoColumnDefs': [ + { // Last column shouldn't be sortable or searchable + 'aTargets': [ 'actions' ], + 'bSortable': false, + 'bSearchable': false + }, + { // When sorting 'placed', we want to use the + // unformatted column + 'aTargets': [ 'placed_formatted'], + 'iDataSort': 14 + }, + { // When sorting 'updated', we want to use the + // unformatted column + 'aTargets': [ 'updated_formatted'], + 'iDataSort': 16 + }, + { // When sorting 'completed', we want to use the + // unformatted column + 'aTargets': [ 'completed_formatted'], + 'iDataSort': 19 + } + ], + 'aaSorting': [[ 16, 'desc' ]], // Default sort, updated descending + 'processing': true, // Display a message when manipulating + 'sPaginationType': "full_numbers", // Pagination display + 'deferRender': true, // Improve performance on big datasets + 'data': dataCopy, + 'columns': colData, + 'originalData': data, // Enable render functions to access + // our original data + 'initComplete': function() { + + // Prepare any filter elements that need it + for (var el in filterable) { + if (filterable.hasOwnProperty(el)) { + if (filterable[el].hasOwnProperty('prep')) { + filterable[el].prep(dataCopy, data); + } + if (filterable[el].hasOwnProperty('listener')) { + filterable[el].listener(); + } + } + } + + } + }, columns_settings); + + // Custom date range filtering + $.fn.dataTable.ext.search.push(function(settings, data, dataIndex) { + var placedStart = $('#illfilter_dateplaced_start').datepicker('getDate'); + var placedEnd = $('#illfilter_dateplaced_end').datepicker('getDate'); + var modifiedStart = $('#illfilter_datemodified_start').datepicker('getDate'); + var modifiedEnd = $('#illfilter_datemodified_end').datepicker('getDate'); + var rowPlaced = data[14] ? new Date(data[14]) : null; + var rowModified = data[16] ? new Date(data[16]) : null; + var placedPassed = true; + var modifiedPassed = true; + if (placedStart && rowPlaced && rowPlaced < placedStart) { + placedPassed = false + }; + if (placedEnd && rowPlaced && rowPlaced > placedEnd) { + placedPassed = false; + } + if (modifiedStart && rowModified && rowModified < modifiedStart) { + modifiedPassed = false + }; + if (modifiedEnd && rowModified && rowModified > modifiedEnd) { + modifiedPassed = false; + } + + return placedPassed && modifiedPassed; + + }); + + } + ); + + var clearSearch = function() { + table.api().search('').columns().search(''); + activeFilters = {}; + for (var filter in filterable) { + if ( + filterable.hasOwnProperty(filter) && + filterable[filter].hasOwnProperty('clear') + ) { + filterable[filter].clear(); + } + } + table.api().draw(); + }; + + // Apply any search filters, or clear any previous + // ones + $('#illfilter_form').submit(function(event) { + event.preventDefault(); + table.api().search('').columns().search(''); + for (var active in activeFilters) { + if (activeFilters.hasOwnProperty(active)) { + activeFilters[active](); + } + } + table.api().draw(); + }); + + // Clear all filters + $('#clear_search').click(function() { + clearSearch(); + }); + +}); diff --git a/members/ill-requests.pl b/members/ill-requests.pl new file mode 100755 index 0000000000..fad46ea91f --- /dev/null +++ b/members/ill-requests.pl @@ -0,0 +1,55 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright 2018 PTFS Europe +# +# 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 +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; + +use CGI qw ( -utf8 ); +use C4::Auth; +use C4::Output; +use C4::Members; +use C4::Members::Attributes qw(GetBorrowerAttributes); +use Koha::Patrons; + +my $input = new CGI; + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => "members/ill-requests.tt", + query => $input, + type => "intranet", + authnotrequired => 0, + flagsrequired => { borrowers => 'edit_borrowers' }, + debug => 1, + } +); + +my $borrowernumber = $input->param('borrowernumber'); + +my $logged_in_user = Koha::Patrons->find( $loggedinuser ) or die "Not logged in"; +my $patron = Koha::Patrons->find( $borrowernumber ); +output_and_exit_if_error( $input, $cookie, $template, { module => 'members', logged_in_user => $logged_in_user, current_patron => $patron } ); + +my $category = $patron->category; +$template->param( + prefilters => "borrowernumber=$borrowernumber", + patron => $patron, + illview => 1, +); + +output_html_with_http_headers $input, $cookie, $template->output; -- 2.11.0