Bug 18589: Show ILLs as part of patron profile
authorAndrew Isherwood <andrew.isherwood@ptfs-europe.com>
Mon, 11 Mar 2019 11:56:28 +0000 (11:56 +0000)
committerNick Clemens <nick@bywatersolutions.com>
Fri, 22 Mar 2019 20:21:46 +0000 (20:21 +0000)
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.

Signed-off-by: Josef Moravec <josef.moravec@gmail.com>

Signed-off-by: Nick Clemens <nick@bywatersolutions.com>

Koha/REST/V1/Illrequests.pm
ill/ill-requests.pl
koha-tmpl/intranet-tmpl/prog/en/includes/circ-menu.inc
koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table-strings.inc [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/includes/ill-list-table.inc [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt
koha-tmpl/intranet-tmpl/prog/en/modules/members/ill-requests.tt [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/js/ill-list-table.js [new file with mode: 0644]
members/ill-requests.pl [new file with mode: 0755]

index a5ec780..c3c51a5 100644 (file)
@@ -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
index 1598ea8..45f99c2 100755 (executable)
@@ -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})
             );
         }
 
index f6c3d5e..0adbcce 100644 (file)
         [% IF houseboundview %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/housebound.pl?borrowernumber=[% patron.borrowernumber | uri %]">Housebound</a></li>
     [% END %]
     [% IF Koha.Preference('ILLModule') && CAN_user_ill %]
-        <li><a href="/cgi-bin/koha/ill/ill-requests.pl?borrowernumber=[% patron.borrowernumber | uri %]">Interlibrary loans</a></li>
+        [% IF illview %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/ill-requests.pl?borrowernumber=[% patron.borrowernumber | uri %]">Interlibrary loans</a></li>
     [% END %]
 </ul></div>
 
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 (file)
index 0000000..3ef5e6e
--- /dev/null
@@ -0,0 +1,19 @@
+<script>
+    var ill_borrower_details = _('View borrower details');
+    var ill_biblio_details = _('View biblio details');
+    var ill_statuses = {
+        new: _('New request'),
+        req: _('Requested'),
+        genreq: _('Requested from partners'),
+        rev: _('Reverted'),
+        que: _('Queued request'),
+        canc: _('Cancellation requested'),
+        comp: _('Completed'),
+        del: _('Delete request')
+    };
+    var ill_manage = _('Manage request');
+    var ill_columns = {
+        biblio_id: _('Bibliographic record ID'),
+        updated: _('Updated on')
+    };
+</script>
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 (file)
index 0000000..df26181
--- /dev/null
@@ -0,0 +1,42 @@
+<div>
+       [% IF prefilters.length > 0 %]
+       <table id="ill-requests" data-prefilters="[% prefilters | html %]">
+       [% ELSE %]
+       <table id="ill-requests">
+       [% END %]
+               <thead>
+                       <tr id="illview-header">
+                               <th scope="col">Request ID</th>
+                               <th scope="col">Author</th>
+                               <th scope="col">Title</th>
+                               <th scope="col">Article title</th>
+                               <th scope="col">Issue</th>
+                               <th scope="col">Volume</th>
+                               <th scope="col">Year</th>
+                               <th scope="col">Pages</th>
+                               <th scope="col">Type</th>
+                               <th scope="col">Order ID</th>
+                               <th scope="col">Patron</th>
+                               <th scope="col">Bibliographic record</th>
+                               <th scope="col">Branch</th>
+                               <th scope="col">Status</th>
+                               <th scope="col" class="placed">&nbsp;</th>
+                               <th scope="col" class="placed_formatted">Placed on</th>
+                               <th scope="col" class="updated">&nbsp;</th>
+                               <th scope="col" class="updated_formatted">Updated on</th>
+                               <th scope="col">Replied</th>
+                               <th scope="col" class="completed">&nbsp;</th>
+                               <th scope="col" class="completed_formatted">Completed on</th>
+                               <th scope="col">Access URL</th>
+                               <th scope="col">Cost</th>
+                               <th scope="col">Comments</th>
+                               <th scope="col">OPAC notes</th>
+                               <th scope="col">Staff notes</th>
+                               <th scope="col">Backend</th>
+                               <th scope="col" class="actions"></th>
+                       </tr>
+               </thead>
+               <tbody id="illview-body">
+               </tbody>
+       </table>
+</div>
index 85bb7cb..a096f32 100644 (file)
                     <h1>View ILL requests</h1>
                     <div id="results">
                         <h3>Details for all requests</h3>
+                        [% INCLUDE 'ill-list-table.inc' %]
 
-                        <table id="ill-requests">
-                            <thead>
-                                <tr id="illview-header">
-                                    <th scope="col">Request ID</th>
-                                    <th scope="col">Author</th>
-                                    <th scope="col">Title</th>
-                                    <th scope="col">Article title</th>
-                                    <th scope="col">Issue</th>
-                                    <th scope="col">Volume</th>
-                                    <th scope="col">Year</th>
-                                    <th scope="col">Pages</th>
-                                    <th scope="col">Type</th>
-                                    <th scope="col">Order ID</th>
-                                    <th scope="col">Patron</th>
-                                    <th scope="col">Bibliographic record</th>
-                                    <th scope="col">Branch</th>
-                                    <th scope="col">Status</th>
-                                    <th scope="col" class="placed">&nbsp;</th>
-                                    <th scope="col" class="placed_formatted">Placed on</th>
-                                    <th scope="col" class="updated">&nbsp;</th>
-                                    <th scope="col" class="updated_formatted">Updated on</th>
-                                    <th scope="col">Replied</th>
-                                    <th scope="col" class="completed">&nbsp;</th>
-                                    <th scope="col" class="completed_formatted">Completed on</th>
-                                    <th scope="col">Access URL</th>
-                                    <th scope="col">Cost</th>
-                                    <th scope="col">Comments</th>
-                                    <th scope="col">OPAC notes</th>
-                                    <th scope="col">Staff notes</th>
-                                    <th scope="col">Backend</th>
-                                    <th scope="col" class="actions"></th>
-                                </tr>
-                            </thead>
-                            <tbody id="illview-body">
-                            </tbody>
-                        </table>
                     </div>
                 [% ELSE %]
                 <!-- Custom Backend Action -->
         </div>
     </div>
 
-[% 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 %]
     <script>
-        $(document).ready(function() {
-
-            // Illview Datatable setup
-
-            var columns_settings = [% ColumnsSettings.GetColumns( 'illrequests', 'ill-requests', 'ill-requests', 'json' ) %];
-
-            var table;
-
-            // Filters that are active
-            var activeFilters = {};
-
-            // 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(
-                                '<option value="' + unique  +
-                                '">' + unique +  '</option>'
-                            );
-                        });
-                    },
-                    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(
-                                '<option value="' + unique  +
-                                '">' + unique +  '</option>'
-                            );
-                        });
-                    },
-                    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 = '<a title="' + _("View borrower details") + '" ' +
-                    'href="/cgi-bin/koha/members/moremember.pl?' +
-                    'borrowernumber='+row.borrowernumber+'">';
-                if ( row.patron_firstname ) {
-                    patronLink = patronLink + row.patron_firstname + ' ';
-                }
-                patronLink = patronLink + row.patron_surname +
-                    ' (' + row.patron_cardnumber + ')' + '</a>';
-                return patronLink;
-            };
-
-            // Our 'render' function for biblio_id
-            var createBiblioLink = function(data, type, row) {
-                return (row.biblio_id) ?
-                    '<a title="' + _("View biblio details") + '" ' +
-                    'href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=' +
-                    row.biblio_id + '">' +
-                    row.biblio_id +
-                    '</a>' : '';
-            };
-
-            // 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 _("New request");
-                    case "Requested":
-                        return _("Requested");
-                    case "Requested from partners":
-                        var statStr = _("Requested from partners");
-                        if (
-                            row.hasOwnProperty('requested_partners') &&
-                            row.requested_partners &&
-                            row.requested_partners.length > 0
-                        ) {
-                            statStr += ' (' + row.requested_partners + ')';
-                        }
-                        return statStr;
-                    case "Request reverted":
-                        return _("Request reverted");
-                    case "Queued request":
-                        return _("Queued request");
-                    case "Cancellation requested":
-                        return _("Cancellation requested");
-                    case "Completed":
-                        return _("Completed");
-                    case "Delete request":
-                        return _("Delete request");
-                    default:
-                        return origName;
-                }
-            };
-
-            // Render function for creating a row's action link
-            var createActionLink = function(data, type, row) {
-                return '<a class="btn btn-default btn-sm" ' +
-                    'href="/cgi-bin/koha/ill/ill-requests.pl?' +
-                    'method=illview&amp;illrequest_id=' +
-                    row.illrequest_id +
-                    '">' + _("Manage request") + '</a>';
-            };
-
-            // Columns that require special treatment
-            var specialCols = {
-                action: {
-                    func: createActionLink,
-                    skipSanitize: true
-                },
-                illrequest_id: {
-                    func: createRequestId
-                },
-                status: {
-                    func: createStatus
-                },
-                biblio_id: {
-                    name: _("Bibliograpic record ID"),
-                    func: createBiblioLink,
-                    skipSanitize: true
-                },
-                metadata_title: {
-                    func: createTitle
-                },
-                metadata_Type: {
-                    func: createType
-                },
-                updated: {
-                    name: _("Updated on"),
-                },
-                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 ajax = $.ajax(
-                '/api/v1/illrequests?embed=metadata,patron,capabilities,library,status_alias,comments,requested_partners'
-                ).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();
-            });
-
-        });
+        // Date format for datepickers
+        var dateMap = {
+            dmydot: 'dd.mm.yy',
+            iso: 'yy-mm-dd',
+            metric: 'dd/mm/yy',
+            us: 'mm/dd/yy'
+        };
+        var dateFormat = dateMap['[% Koha.Preference('dateformat') | html %]'];
+        var prefilters = '[% prefilters | html %]';
+        // Set column settings
+        var columns_settings = [% ColumnsSettings.GetColumns( 'illrequests', 'ill-requests', 'ill-requests', 'json' ) %];
     </script>
+    [% 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 (file)
index 0000000..cab9a6c
--- /dev/null
@@ -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' %]
+<title>Koha &rsaquo; Patrons &rsaquo; ILL requests for [% INCLUDE 'patron-title.inc' no_html = 1 %]</title>
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+
+<body>
+       [% INCLUDE 'header.inc' %]
+       [% INCLUDE 'patron-search.inc' %]
+
+       <div id="breadcrumbs">
+               <a href="/cgi-bin/koha/mainpage.pl">Home</a>
+               &rsaquo; <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>
+               &rsaquo; ILL requests for [% INCLUDE 'patron-title.inc' %]
+       </div>
+
+       <div id="doc3" class="yui-t1">
+
+       <div id="bd">
+               <div id="yui-main">
+                       <div class="yui-b">
+                               <div class="yui-g">
+                                       <h2>ILL requests</h2>
+                                       [% INCLUDE 'ill-list-table.inc' %]
+                               </div>
+                       </div>
+               </div>
+               <div class="yui-b">
+                       [% INCLUDE 'circ-menu.inc' %]
+               </div>
+       </div>
+
+       [% MACRO jsinclude BLOCK %]
+               [% INCLUDE 'datatables.inc' %]
+               [% INCLUDE 'columns_settings.inc' %]
+               [% INCLUDE 'calendar.inc' %]
+               [% Asset.js("lib/jquery/plugins/jquery.checkboxes.min.js") | $raw %]
+               <script>
+                       // Date format for datepickers
+                       var dateMap = {
+                               dmydot: 'dd.mm.yy',
+                               iso: 'yy-mm-dd',
+                               metric: 'dd/mm/yy',
+                               us: 'mm/dd/yy'
+                       };
+                       var dateFormat = dateMap['[% Koha.Preference('dateformat') | html %]'];
+                       var prefilters = '[% prefilters | html %]';
+                       // Set column settings
+                       var columns_settings = [% ColumnsSettings.GetColumns( 'illrequests', 'ill-requests', 'ill-requests', 'json' ) %];
+               </script>
+        [% 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 (file)
index 0000000..41e7ecc
--- /dev/null
@@ -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(
+                        '<option value="' + unique  +
+                        '">' + unique +  '</option>'
+                    );
+                });
+            },
+            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(
+                        '<option value="' + unique  +
+                        '">' + unique +  '</option>'
+                    );
+                });
+            },
+            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 = '<a title="' + ill_borrower_details + '" ' +
+            'href="/cgi-bin/koha/members/moremember.pl?' +
+            'borrowernumber='+row.borrowernumber+'">';
+        if ( row.patron_firstname ) {
+            patronLink = patronLink + row.patron_firstname + ' ';
+        }
+        patronLink = patronLink + row.patron_surname +
+            ' (' + row.patron_cardnumber + ')' + '</a>';
+        return patronLink;
+    };
+
+    // Our 'render' function for biblio_id
+    var createBiblioLink = function(data, type, row) {
+        return (row.biblio_id) ?
+            '<a title="' + ill_biblio_details + '" ' +
+            'href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=' +
+            row.biblio_id + '">' +
+            row.biblio_id +
+            '</a>' : '';
+    };
+
+    // 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 '<a class="btn btn-default btn-sm" ' +
+            'href="/cgi-bin/koha/ill/ill-requests.pl?' +
+            'method=illview&amp;illrequest_id=' +
+            row.illrequest_id +
+            '">' + ill_manage + '</a>';
+    };
+
+    // 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 (executable)
index 0000000..fad46ea
--- /dev/null
@@ -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 <http://www.gnu.org/licenses>.
+
+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;