Bugzilla – Attachment 185742 Details for
Bug 15219
Server-side processing and pagination on checkouts tables
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 15219: Add pagination on checkouts tables
Bug-15219-Add-pagination-on-checkouts-tables.patch (text/plain), 42.45 KB, created by
Julian Maurice
on 2025-08-25 12:59:06 UTC
(
hide
)
Description:
Bug 15219: Add pagination on checkouts tables
Filename:
MIME Type:
Creator:
Julian Maurice
Created:
2025-08-25 12:59:06 UTC
Size:
42.45 KB
patch
obsolete
>From 7b9b566ccd53bfed9f312d18a6a32f8afb8e63e4 Mon Sep 17 00:00:00 2001 >From: Julian Maurice <julian.maurice@biblibre.com> >Date: Wed, 18 Nov 2015 15:52:14 +0100 >Subject: [PATCH] Bug 15219: Add pagination on checkouts tables > >Some libraries have patrons with more than 1000 checkouts. Loading all >of them at once can be very long, and sometimes can cause timeout >errors. >This patch prevent that by enabling pagination on checkouts tables. >This affects patron's checkouts and patron's relatives' checkouts tables >on pages circ/circulation.pl and members/moremember.pl. > >As pagination can be useless and cumbersome with small >sets of data, a new system preference is introduced to control this >behaviour (pagination is disabled by default). > >Additionally, this patch adds a switch to turn off and on row grouping >(today's checkouts vs previous checkouts) (on by default) > >Test plan: >1. Find (or create) a patron that have at least 10 checkouts and where > the relatives checkouts table contain at least 10 checkouts. >2. Go to the patron's detail page and check everything works fine > in both tables (sorting, pagination, the data itself, ...) >3. Do the same on circulation page (circulation.pl) >4. Enable system preference ServerSideCheckoutsTable >5. Repeat steps 2 and 3 >--- > admin/columns_settings.yml | 60 ----- > .../data/mysql/atomicupdate/bug-15219.pl | 15 ++ > installer/data/mysql/mandatory/sysprefs.sql | 3 +- > .../en/includes/checkouts-table-footer.inc | 9 +- > .../prog/en/includes/checkouts-table.inc | 10 +- > .../en/includes/relatives-issues-table.inc | 2 - > .../admin/preferences/circulation.pref | 6 + > .../prog/en/modules/circ/circulation.tt | 1 + > .../prog/en/modules/members/moremember.tt | 1 + > koha-tmpl/intranet-tmpl/prog/js/checkouts.js | 207 +++++++++++------- > svc/checkouts | 138 +++++++----- > 11 files changed, 240 insertions(+), 212 deletions(-) > create mode 100644 installer/data/mysql/atomicupdate/bug-15219.pl > >diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml >index 7b9e4e7ec87..20227c30855 100644 >--- a/admin/columns_settings.yml >+++ b/admin/columns_settings.yml >@@ -1313,21 +1313,6 @@ modules: > moremember: > issues-table: > columns: >- - >- columnname: sort_order >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 >- - >- columnname: todays_or_previous_checkouts >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 >- - >- columnname: due_date_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: due_date > - >@@ -1342,11 +1327,6 @@ modules: > columnname: location > - > columnname: homebranch >- - >- columnname: checkout_on_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: checkout_on > - >@@ -1431,11 +1411,6 @@ modules: > > relatives-issues-table: > columns: >- - >- columnname: due_date_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: due_date > - >@@ -1448,11 +1423,6 @@ modules: > columnname: collection > - > columnname: location >- - >- columnname: checkout_on_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: checkout_on > - >@@ -1687,21 +1657,6 @@ modules: > circulation: > issues-table: > columns: >- - >- columnname: sort_order >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 >- - >- columnname: todays_or_previous_checkouts >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 >- - >- columnname: due_date_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: due_date > - >@@ -1716,11 +1671,6 @@ modules: > columnname: location > - > columnname: homebranch >- - >- columnname: checkout_on_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: checkout_on > - >@@ -1750,11 +1700,6 @@ modules: > > relatives-issues-table: > columns: >- - >- columnname: due_date_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: due_date > - >@@ -1767,11 +1712,6 @@ modules: > columnname: collection > - > columnname: location >- - >- columnname: checkout_on_unformatted >- cannot_be_toggled: 1 >- cannot_be_modified: 1 >- is_hidden: 1 > - > columnname: checkout_on > - >diff --git a/installer/data/mysql/atomicupdate/bug-15219.pl b/installer/data/mysql/atomicupdate/bug-15219.pl >new file mode 100644 >index 00000000000..19d386dda92 >--- /dev/null >+++ b/installer/data/mysql/atomicupdate/bug-15219.pl >@@ -0,0 +1,15 @@ >+use Modern::Perl; >+ >+return { >+ bug_number => "15219", >+ description => "Add syspref ServerSideCheckoutsTables", >+ up => sub { >+ my ($args) = @_; >+ my ($dbh, $out) = @$args{qw(dbh out)}; >+ >+ $dbh->do(q{ >+ INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) >+ VALUES ('ServerSideCheckoutsTables','0',NULL,'If enabled, use the "server-side processing" feature for checkouts tables. Can improve performance when there is a big number of checkouts to display.','YesNo') >+ }); >+ }, >+}; >diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql >index 67cd533dafb..3809cc39331 100644 >--- a/installer/data/mysql/mandatory/sysprefs.sql >+++ b/installer/data/mysql/mandatory/sysprefs.sql >@@ -738,6 +738,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('SerialsDefaultEmailAddress', '', NULL, 'Default email address used as reply-to for notices sent by the serials module.', 'Free'), > ('SerialsDefaultReplyTo', '', NULL, 'Default email address that serials notices are sent from.', 'Free'), > ('SerialsSearchResultsLimit', '', NULL, 'Serials search results limit', 'integer'), >+('ServerSideCheckoutsTables','0',NULL,'If enabled, use the "server-side processing" feature for checkouts tables. Can improve performance when there is a big number of checkouts per patron.','YesNo'), > ('SessionRestrictionByIP','1','Check for change in remote IP address for session security. Disable only when remote IP address changes frequently.','','YesNo'), > ('SessionStorage','mysql','mysql|Pg|tmp','Use database or a temporary file for storing session data','Choice'), > ('ShelfBrowserUsesCcode','1','0','Use the item collection code when finding items for the shelf browser.','YesNo'), >@@ -889,4 +890,4 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` > ('z3950AuthorAuthFields','701,702,700',NULL,'Define the MARC biblio fields for Personal Name Authorities to fill biblio.author','free'), > ('z3950NormalizeAuthor','0','','If ON, Personal Name Authorities will replace authors in biblio.author','YesNo'), > ('z3950Status','','','This syspref allows to define custom YAML based rules for marking items unavailable in z3950 results.','Textarea') >-; >\ No newline at end of file >+; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table-footer.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table-footer.inc >index 498469ddc86..99edb4f70ae 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table-footer.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table-footer.inc >@@ -1,15 +1,10 @@ > <tfoot> > <tr> >- <td colspan="15" style="text-align: right; font-weight:bold;">Totals:</td> >+ <td colspan="11" style="text-align: right; font-weight:bold;">Totals:</td> > <td id="totaldue" style="text-align: right;"></td> > <td id="totalfine" style="text-align: right;"></td> > <td id="totalprice" style="text-align: right;"></td> >- [% IF Koha.Preference('ExportCircHistory') %] >- [% SET td_colspan = 4 %] >- [% ELSE %] >- [% SET td_colspan = 3 %] >- [% END %] >- <td colspan="[% td_colspan | html %]"> >+ <td colspan="4"> > <div class="date-select"> > <p><label for="newduedate">Renewal due date:</label> <input type="text" size="20" id="newduedate" name="newduedate" value="" class="flatpickr" data-flatpickr-enable-time="true" /></p> > <p id="newonholdduedate" >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc >index 316b28889b3..a303d9aa7b2 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc >@@ -20,9 +20,6 @@ > <table id="issues-table" style="width: 100% !Important; display: none;"> > <thead> > <tr> >- <th scope="col"> </th> >- <th scope="col"> </th> >- <th scope="col">Due date</th> > <th scope="col">Due date</th> > <th scope="col">Title</th> > <th scope="col">Record-level item type</th> >@@ -30,7 +27,6 @@ > <th scope="col">Collection</th> > <th scope="col">Location</th> > <th scope="col">Home library</th> >- <th scope="col"> </th> > <th scope="col">Checked out on</th> > <th scope="col">Checked out from</th> > <th scope="col">Call number</th> >@@ -39,14 +35,14 @@ > <th scope="col">Fine</th> > <th scope="col">Price</th> > <th scope="col" >- >Renew <p class="column-tool"><a href="#" id="CheckAllRenewals">select all</a> | <a href="#" id="UncheckAllRenewals">none</a></p></th >+ >Renew <p class="column-tool"><a href="#" id="CheckAllRenewals">select all visible rows</a> | <a href="#" id="UncheckAllRenewals">none</a></p></th > > > <th scope="col" >- >Check in <p class="column-tool"><a href="#" id="CheckAllCheckins">select all</a> | <a href="#" id="UncheckAllCheckins">none</a></p></th >+ >Check in <p class="column-tool"><a href="#" id="CheckAllCheckins">select all visible rows</a> | <a href="#" id="UncheckAllCheckins">none</a></p></th > > > <th scope="col">Return claims</th> > <th scope="col" >- >Export <p class="column-tool"><a href="#" id="CheckAllExports">select all</a> | <a href="#" id="UncheckAllExports">none</a></p></th >+ >Export <p class="column-tool"><a href="#" id="CheckAllExports">select all visible rows</a> | <a href="#" id="UncheckAllExports">none</a></p></th > > > </tr> > </thead> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/relatives-issues-table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/relatives-issues-table.inc >index f541cacb8aa..b2a3b99b78d 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/relatives-issues-table.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/relatives-issues-table.inc >@@ -3,14 +3,12 @@ > <table id="relatives-issues-table" style="width: 100% !Important;"> > <thead> > <tr> >- <th scope="col">Due date (unformatted, hidden)</th> > <th scope="col">Due date</th> > <th scope="col">Title</th> > <th scope="col">Record-level item type</th> > <th scope="col">Item type</th> > <th scope="col">Collection</th> > <th scope="col">Location</th> >- <th scope="col">Checked out on (hidden, unformatted)</th> > <th scope="col">Checked out on</th> > <th scope="col">Checked out from</th> > <th scope="col">Call number</th> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >index ef3c7acc011..451366c1d73 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/circulation.pref >@@ -57,6 +57,12 @@ Circulation: > asc: earliest to latest > desc: latest to earliest > - checkout time. >+ - >+ - pref: ServerSideCheckoutsTables >+ choices: >+ 1: Enable >+ 0: Do not enable >+ - server-side processing on checkouts tables. It can improve performance when there is a big number of checkouts to display. > - > - pref: SpecifyDueDate > choices: >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt >index 1ca767f7295..4823df7db16 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt >@@ -1107,6 +1107,7 @@ > relatives_borrowernumbers.push("[% b | html %]"); > [% END %] > var SuspendHoldsIntranet = [% ( Koha.Preference('SuspendHoldsIntranet') ) ? 1 : 0 | html %]; >+ var ServerSideCheckoutsTables = [% Koha.Preference('ServerSideCheckoutsTables') ? 'true' : 'false' | $raw %]; > </script> > [% Asset.js("js/pages/circulation.js") | $raw %] > [% IF Koha.Preference('ClaimReturnedLostValue') || Koha.Preference('BundleLostValue') %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >index cdb10f8e48b..0fdc6f79547 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/members/moremember.tt >@@ -783,6 +783,7 @@ > relatives_borrowernumbers.push("[% b | html %]"); > [% END %] > var SuspendHoldsIntranet = [% ( Koha.Preference('SuspendHoldsIntranet') ) ? 1 : 0 | html %]; >+ var ServerSideCheckoutsTables = [% Koha.Preference('ServerSideCheckoutsTables') ? 'true' : 'false' | $raw %]; > </script> > [% Asset.js("js/pages/circulation.js") | $raw %] > [% IF Koha.Preference('ClaimReturnedLostValue') || Koha.Preference('BundleLostValue') %] >diff --git a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js >index 185a5488db5..737d1157c65 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js >@@ -1,4 +1,4 @@ >-/* global __ */ >+/* global __, ServerSideCheckoutsTables: false */ > > function CheckRenewCheckinBoxes() { > $("#RenewChecked").prop("disabled", !$(".renew:checked").length); >@@ -61,37 +61,11 @@ function LoadIssuesTable() { > processing: msg_loading, > }, > autoWidth: false, >- dom: '<"table_controls"B>rt', >+ order: [], > columns: [ >- { >- data: function (oObj) { >- return oObj.sort_order; >- }, >- }, >- { >- data: function (oObj) { >- if (oObj.issued_today) { >- return ( >- "<strong>" + >- __("Today's checkouts") + >- "</strong>" >- ); >- } else { >- return ( >- "<strong>" + >- __("Previous checkouts") + >- "</strong>" >- ); >- } >- }, >- }, > { > data: "date_due", >- visible: false, >- }, >- { >- orderData: 2, // Sort on hidden unformatted date due column >- data: function (oObj) { >+ render: function (data, type, oObj) { > let date_due_formatted = $datetime(oObj.date_due, { > as_due_date: true, > no_tz_adjust: true, >@@ -142,7 +116,8 @@ function LoadIssuesTable() { > }, > }, > { >- data: function (oObj) { >+ data: "title", >+ render: function (data, type, oObj) { > let title = > "<span id='title_" + > oObj.itemnumber + >@@ -247,29 +222,34 @@ function LoadIssuesTable() { > type: "anti-the", > }, > { >- data: function (oObj) { >+ data: "recordtype", >+ render: function (data, type, oObj) { > return oObj.recordtype_description.escapeHtml(); > }, > }, > { >- data: function (oObj) { >+ data: "itemtype", >+ render: function (data, type, oObj) { > return oObj.itemtype_description.escapeHtml(); > }, > }, > { >- data: function (oObj) { >+ data: "collection", >+ render: function (data, type, oObj) { > return oObj.collection > ? oObj.collection.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "location", >+ render: function (data, type, oObj) { > return oObj.location ? oObj.location.escapeHtml() : ""; > }, > }, > { >- data: function (oObj) { >+ data: "homebranch", >+ render: function (data, type, oObj) { > return oObj.homebranch > ? oObj.homebranch.escapeHtml() > : ""; >@@ -277,39 +257,39 @@ function LoadIssuesTable() { > }, > { > data: "issuedate", >- visible: false, >- }, >- { >- orderData: 10, // Sort on hidden unformatted issuedate column >- data: function (oObj) { >+ render: function (data, type, oObj) { > return $datetime(oObj.issuedate, { > no_tz_adjust: true, > }); > }, > }, > { >- data: function (oObj) { >+ data: "branchcode", >+ render: function (data, type, oObj) { > return oObj.branchname > ? oObj.branchname.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "itemcallnumber", >+ render: function (data, type, oObj) { > return oObj.itemcallnumber > ? oObj.itemcallnumber.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "copynumber", >+ render: function (data, type, oObj) { > return oObj.copynumber > ? oObj.copynumber.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "charge", >+ render: function (data, type, oObj) { > if (!oObj.charge) oObj.charge = 0; > return ( > '<span style="text-align: right; display: block;">' + >@@ -320,7 +300,8 @@ function LoadIssuesTable() { > className: "nowrap", > }, > { >- data: function (oObj) { >+ data: "fine", >+ render: function (data, type, oObj) { > if (!oObj.fine) oObj.fine = 0; > return ( > '<span style="text-align: right; display: block;">' + >@@ -331,7 +312,8 @@ function LoadIssuesTable() { > className: "nowrap", > }, > { >- data: function (oObj) { >+ data: "price", >+ render: function (data, type, oObj) { > if (!oObj.price) oObj.price = 0; > return ( > '<span style="text-align: right; display: block;">' + >@@ -343,7 +325,7 @@ function LoadIssuesTable() { > }, > { > orderable: false, >- data: function (oObj) { >+ render: function (data, type, oObj) { > var content = ""; > var msg = ""; > var span_style = ""; >@@ -555,7 +537,7 @@ function LoadIssuesTable() { > }, > { > orderable: false, >- data: function (oObj) { >+ render: function (data, type, oObj) { > if (oObj.can_renew_error == "recalled") { > return ( > "<a href='/cgi-bin/koha/recalls/request.pl?biblionumber=" + >@@ -589,7 +571,7 @@ function LoadIssuesTable() { > }, > { > orderable: false, >- data: function (oObj) { >+ render: function (data, type, oObj) { > let content = ""; > > if (oObj.return_claim_id) { >@@ -615,7 +597,7 @@ function LoadIssuesTable() { > }, > { > orderable: false, >- data: function (oObj) { >+ render: function (data, type, oObj) { > var s = > "<input type='checkbox' name='itemnumbers' value='" + > oObj.itemnumber + >@@ -644,13 +626,27 @@ function LoadIssuesTable() { > $("#totalfine").html(total_fine.format_price()); > $("#totalprice").html(total_price.format_price()); > }, >- paging: false, >+ paging: ServerSideCheckoutsTables, > processing: true, >- serverSide: false, >+ serverSide: ServerSideCheckoutsTables, > ajax: { >- url: "/cgi-bin/koha/svc/checkouts?borrowernumber=%s".format( >- borrowernumber >- ), >+ url: "/cgi-bin/koha/svc/checkouts", >+ data(data) { >+ return Object.assign({}, data, { >+ borrowernumber, >+ date_due_sorton: "issues.date_due", >+ title_sorton: "biblio.title", >+ recordtype_sorton: "biblioitems.itemtype", >+ itemtype_sorton: "items.itype", >+ collection_sorton: "items.ccode", >+ location_sorton: "items.location", >+ homebranch_sorton: "items.homebranch", >+ issuedate_sorton: "issues.issuedate", >+ branchcode_sorton: "issues.branchcode", >+ itemcallnumber_sorton: "items.itemcallnumber", >+ copynumber_sorton: "items.copynumber", >+ }); >+ }, > }, > bKohaAjaxSVC: true, > rowGroup: { >@@ -667,7 +663,7 @@ function LoadIssuesTable() { > CheckRenewCheckinBoxes(); > > // Build a summary of checkouts grouped by itemtype >- var checkoutsByItype = json.aaData.reduce(function (obj, row) { >+ var checkoutsByItype = json.data.reduce(function (obj, row) { > obj[row.type_for_stat] = (obj[row.type_for_stat] || 0) + 1; > return obj; > }, {}); >@@ -696,6 +692,32 @@ function LoadIssuesTable() { > table_settings_issues_table > ); > >+ var groupingSwitch = $('<input type="checkbox">') >+ .attr("id", "issues-table-row-grouping-switch") >+ .attr("checked", "checked") >+ .css("vertical-align", "middle") >+ .change(function () { >+ if (this.checked) { >+ issuesTable.api().rowGroup().enable(); >+ issuesTable.api().draw(); >+ } else { >+ issuesTable.api().rowGroup().disable(); >+ issuesTable.api().draw(); >+ } >+ }); >+ $("<div></div>") >+ .css("float", "left") >+ .css("padding", "0.3em 0.5em") >+ .css("line-height", "1.9em") >+ .append(groupingSwitch) >+ .append(" ") >+ .append( >+ '<label for="issues-table-row-grouping-switch">' + >+ __("Group today's checkouts") + >+ "</label>" >+ ) >+ .appendTo("#issues-table_wrapper .top.pager"); >+ > if ($("#issues-table").length) { > $("#issues-table_processing").position({ > of: $("#issues-table"), >@@ -1091,16 +1113,11 @@ $(document).ready(function () { > relativesIssuesTable = $("#relatives-issues-table").kohaTable( > { > autoWidth: false, >- dom: '<"table_controls"B>rt', > order: [], > columns: [ > { > data: "date_due", >- visible: false, >- }, >- { >- orderData: 0, // Sort on hidden unformatted date due column >- data: function (oObj) { >+ render: function (data, type, oObj) { > var today = new Date(); > var due = new Date(oObj.date_due); > let date_due_formatted = $datetime( >@@ -1119,7 +1136,8 @@ $(document).ready(function () { > }, > }, > { >- data: function (oObj) { >+ data: "title", >+ render: function (data, type, oObj) { > let title = > "<span class='strong'><a href='/cgi-bin/koha/catalogue/detail.pl?biblionumber=" + > oObj.biblionumber + >@@ -1218,24 +1236,28 @@ $(document).ready(function () { > type: "anti-the", > }, > { >- data: function (oObj) { >+ data: "recordtype", >+ render: function (data, type, oObj) { > return oObj.recordtype_description.escapeHtml(); > }, > }, > { >- data: function (oObj) { >+ data: "itemtype", >+ render: function (data, type, oObj) { > return oObj.itemtype_description.escapeHtml(); > }, > }, > { >- data: function (oObj) { >+ data: "collection", >+ render: function (data, type, oObj) { > return oObj.collection > ? oObj.collection.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "location", >+ render: function (data, type, oObj) { > return oObj.location > ? oObj.location.escapeHtml() > : ""; >@@ -1243,56 +1265,59 @@ $(document).ready(function () { > }, > { > data: "issuedate", >- visible: false, >- }, >- { >- orderData: 7, // Sort on hidden unformatted issuedate column >- data: function (oObj) { >+ render: function (data, type, oObj) { > return $datetime(oObj.issuedate, { > no_tz_adjust: true, > }); > }, > }, > { >- data: function (oObj) { >+ data: "branchcode", >+ render: function (data, type, oObj) { > return oObj.branchname > ? oObj.branchname.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "itemcallnumber", >+ render: function (data, type, oObj) { > return oObj.itemcallnumber > ? oObj.itemcallnumber.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "copynumber", >+ render: function (data, type, oObj) { > return oObj.copynumber > ? oObj.copynumber.escapeHtml() > : ""; > }, > }, > { >- data: function (oObj) { >+ data: "charge", >+ render: function (data, type, oObj) { > if (!oObj.charge) oObj.charge = 0; > return parseFloat(oObj.charge).toFixed(2); > }, > }, > { >- data: function (oObj) { >+ data: "fine", >+ render: function (data, type, oObj) { > if (!oObj.fine) oObj.fine = 0; > return parseFloat(oObj.fine).toFixed(2); > }, > }, > { >- data: function (oObj) { >+ data: "price", >+ render: function (data, type, oObj) { > if (!oObj.price) oObj.price = 0; > return parseFloat(oObj.price).toFixed(2); > }, > }, > { >+ orderable: false, > data: function (oObj) { > return ( > "<a href='/cgi-bin/koha/members/moremember.pl?borrowernumber=" + >@@ -1314,15 +1339,27 @@ $(document).ready(function () { > }, > }, > ], >- paging: false, >+ paging: ServerSideCheckoutsTables, > processing: true, >- serverSide: false, >+ serverSide: ServerSideCheckoutsTables, > ajax: { >- url: "/cgi-bin/koha/svc/checkouts?%s".format( >- relatives_borrowernumbers >- .map(b => "borrowernumber=%s".format(b)) >- .join("&") >- ), >+ url: "/cgi-bin/koha/svc/checkouts", >+ data(data) { >+ return Object.assign({}, data, { >+ borrowernumber: relatives_borrowernumbers, >+ date_due_sorton: "issues.date_due", >+ title_sorton: "biblio.title", >+ recordtype_sorton: "biblioitems.itemtype", >+ itemtype_sorton: "items.itype", >+ collection_sorton: "items.ccode", >+ location_sorton: "items.location", >+ homebranch_sorton: "items.homebranch", >+ issuedate_sorton: "issues.issuedate", >+ branchcode_sorton: "issues.branchcode", >+ itemcallnumber_sorton: "items.itemcallnumber", >+ copynumber_sorton: "items.copynumber", >+ }); >+ }, > }, > bKohaAjaxSVC: true, > }, >diff --git a/svc/checkouts b/svc/checkouts >index a8c92354d7d..6134579168d 100755 >--- a/svc/checkouts >+++ b/svc/checkouts >@@ -48,26 +48,66 @@ unless ( haspermission( $userid, { circulate => 'circulate_remaining_permissions > exit 0; > } > >-my @sort_columns = qw/date_due title itype issuedate branchcode itemcallnumber/; >+my $dbh = C4::Context->dbh(); > >-my @borrowernumber = $input->multi_param('borrowernumber'); >-my $offset = $input->param('iDisplayStart'); >-my $results_per_page = $input->param('iDisplayLength') || -1; >+my @borrowernumbers; >+my $borrowernumber = $input->param('borrowernumber'); >+if ($borrowernumber) { >+ push @borrowernumbers, $borrowernumber; >+} else { >+ @borrowernumbers = $input->multi_param('borrowernumber[]'); >+} >+my $offset = $input->param('start'); >+my $count = $input->param('length'); > >-my $sorting_column = $input->param('iSortCol_0') || q{}; >-$sorting_column = ( $sorting_column && $sort_columns[$sorting_column] ) ? $sort_columns[$sorting_column] : 'issuedate'; >+my %vars = $input->Vars(); >+my $i = 0; >+my @orderbys; >+while (exists $vars{"order[$i][column]"}) { >+ my $iSortCol = $vars{"order[$i][column]"}; >+ my $sSortDir = $vars{"order[$i][dir]"}; >+ my $mDataProp = $vars{"columns[$iSortCol][data]"}; >+ my @sort_fields = $vars{$mDataProp.'_sorton'} >+ ? split(' ', $vars{$mDataProp.'_sorton'}) >+ : (); >+ if(@sort_fields > 0) { >+ push @orderbys, "$_ $sSortDir" foreach (@sort_fields); >+ } else { >+ push @orderbys, "$mDataProp $sSortDir"; >+ } >+ $i++; >+} >+@orderbys = grep { /^\w+\.\w+ (asc|desc)$/ } @orderbys; >+my @sanitized_orderbys; >+for my $orderby (@orderbys) { >+ my ($identifier, $direction) = split / /, $orderby, 2; >+ my ($table, $column) = split /\./, $identifier, 2; >+ my $sanitized_identifier = $dbh->quote_identifier(undef, $table, $column); >+ my $sanitized_direction = $direction eq 'asc' ? 'ASC' : 'DESC'; >+ push @sanitized_orderbys, "$sanitized_identifier $sanitized_direction"; >+} >+my $orderby = @sanitized_orderbys ? " ORDER BY " . join(',', @sanitized_orderbys) . " " : ''; > >-my $sorting_direction = $input->param('sSortDir_0') || q{}; >-$sorting_direction = $sorting_direction eq 'asc' ? 'asc' : 'desc'; >+my $todaysIssuesDefaultSortOrder = >+ C4::Context->preference('todaysIssuesDefaultSortOrder'); >+my $previousIssuesDefaultSortOrder = >+ C4::Context->preference('previousIssuesDefaultSortOrder'); > >-$results_per_page = undef if ( $results_per_page == -1 ); >- >-binmode STDOUT, ":encoding(UTF-8)"; >-print $input->header( -type => 'application/json', -charset => 'UTF-8' ); >+# Default sort when user hasn't clicked on a column header yet. >+# When row grouping is enabled, DataTables ask to sort on issued_today column, >+# so we need to treat this case the same as the "no sorting" case >+if (!$orderby || $orderby =~ m/ORDER BY issued_today desc\s*$/) { >+ $orderby = qq{ >+ ORDER BY >+ issued_today DESC, >+ CASE WHEN issued_today = 0 THEN date_due END $previousIssuesDefaultSortOrder, >+ CASE WHEN issued_today != 0 THEN date_due END $todaysIssuesDefaultSortOrder >+ }; >+} > > my @parameters; > my $sql = ' >- SELECT >+ SELECT SQL_CALC_FOUND_ROWS > issues.issue_id, > issues.issuedate, > issues.date_due, >@@ -114,13 +154,12 @@ my $sql = ' > items.enumchron, > items.materials, > >- DATEDIFF( issues.issuedate, CURRENT_DATE() ) AS not_issued_today, >+ DATEDIFF( issues.issuedate, CURRENT_DATE() ) = 0 AS issued_today, > > return_claims.id AS return_claim_id, > return_claims.notes AS return_claim_notes, > return_claims.created_on AS return_claim_created_on, > return_claims.updated_on AS return_claim_updated_on >- > FROM issues > LEFT JOIN items USING ( itemnumber ) > LEFT JOIN biblio USING ( biblionumber ) >@@ -129,21 +168,29 @@ my $sql = ' > LEFT JOIN branches ON ( issues.branchcode = branches.branchcode ) > LEFT JOIN branches branches2 ON ( items.homebranch = branches2.branchcode ) > LEFT JOIN return_claims USING ( issue_id ) >- WHERE issues.borrowernumber >+ WHERE > '; > >-if ( @borrowernumber == 1 ) { >- $sql .= '= ?'; >+my $borrowernumber_sql = 'issues.borrowernumber'; >+if ( @borrowernumbers == 1 ) { >+ $borrowernumber_sql .= ' = ?'; > } else { >- $sql .= ' IN (' . join( ',', ('?') x @borrowernumber ) . ') '; >+ $borrowernumber_sql .= ' IN (' . join( ',', ('?') x @borrowernumbers ) . ') '; > } >-push( @parameters, @borrowernumber ); >+$sql .= $borrowernumber_sql; >+push( @parameters, @borrowernumbers ); > >-$sql .= " ORDER BY $sorting_column $sorting_direction "; >+$sql .= " $orderby"; >+ >+if ($count && $count > 0) { >+ $sql .= " LIMIT ?,?"; >+ push @parameters, $offset, $count; >+} > >-my $dbh = C4::Context->dbh(); > my $sth = $dbh->prepare($sql); >+warn $sql; > $sth->execute(@parameters); >+my ($iTotalDisplayRecords) = $dbh->selectrow_array('SELECT FOUND_ROWS()'); > > my $item_level_itypes = C4::Context->preference('item-level_itypes'); > my $claims_returned_lost_value = C4::Context->preference('ClaimReturnedLostValue'); >@@ -152,8 +199,7 @@ my $confirm_parts_required = C4::Context->preference("CircConfirmItemParts") > my $itemtypes = { map { $_->{itemtype} => $_->{translated_description} } > @{ Koha::ItemTypes->search_with_localization->unblessed } }; > >-my @checkouts_today; >-my @checkouts_previous; >+my @checkouts; > while ( my $c = $sth->fetchrow_hashref() ) { > my ($charge) = GetIssuingCharges( $c->{itemnumber}, $c->{borrowernumber} ); > my $fine = GetFine( $c->{itemnumber}, $c->{borrowernumber} ); >@@ -251,7 +297,9 @@ while ( my $c = $sth->fetchrow_hashref() ) { > author => $c->{author}, > barcode => $c->{barcode}, > type_for_stat => $type_for_stat || q{}, >+ itemtype => $c->{itype} || q{}, > itemtype_description => $itemtype || q{}, >+ recordtype => $c->{itemtype} || q{}, > recordtype_description => $recordtype || q{}, > collection => $collection, > location => $location, >@@ -306,36 +354,26 @@ while ( my $c = $sth->fetchrow_hashref() ) { > firstname => $c->{firstname}, > cardnumber => $c->{cardnumber}, > }, >- issued_today => !$c->{not_issued_today}, >- recalled => $recalled, >+ issued_today => $c->{issued_today} ? JSON::true : JSON::false, >+ recalled => $recalled, > }; > >- if ( $c->{not_issued_today} ) { >- push( @checkouts_previous, $checkout ); >- } else { >- push( @checkouts_today, $checkout ); >- } >+ push @checkouts, $checkout; > } > >-@checkouts_today = sort { $a->{timestamp} cmp $b->{timestamp} } @checkouts_today; # latest to earliest >-@checkouts_today = reverse(@checkouts_today) >- if ( C4::Context->preference('todaysIssuesDefaultSortOrder') eq 'desc' ); # earliest to latest >+binmode STDOUT, ":encoding(UTF-8)"; >+print $input->header( -type => 'application/json', -charset => 'UTF-8' ); > >-@checkouts_previous = >- sort { $a->{date_due} cmp $b->{date_due} || $a->{timestamp} cmp $b->{timestamp} } >- @checkouts_previous; # latest to earliest >-@checkouts_previous = reverse(@checkouts_previous) >- if ( C4::Context->preference('previousIssuesDefaultSortOrder') eq 'desc' ); # earliest to latest >+my $draw = $input->param('draw'); >+my ($iTotalRecords) = $dbh->selectrow_array( >+ "SELECT COUNT(*) FROM issues WHERE $borrowernumber_sql", undef, >+ @borrowernumbers); > >-my @checkouts = ( @checkouts_today, @checkouts_previous ); >+my $response = { >+ recordsTotal => $iTotalRecords, >+ recordsFiltered => $iTotalDisplayRecords, >+ draw => $draw, >+ data => \@checkouts, >+}; > >-my $i = 1; >-map { $_->{sort_order} = $i++ } @checkouts; >- >-my $data; >-$data->{'iTotalRecords'} = scalar @checkouts; >-$data->{'iTotalDisplayRecords'} = scalar @checkouts; >-$data->{'sEcho'} = $input->param('sEcho') || undef; >-$data->{'aaData'} = \@checkouts; >- >-print to_json($data); >+print to_json($response); >-- >2.39.5
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 15219
:
45018
|
45036
|
45054
|
48107
|
50075
|
60071
|
60642
|
61158
|
61159
|
61545
|
61546
|
72905
|
72906
|
74224
|
74225
|
75911
|
75912
|
75913
|
78555
|
78556
|
78557
|
78558
|
78559
|
79195
|
79196
|
79197
|
79198
|
79199
|
92776
|
92777
|
92778
|
92779
|
92780
|
92781
|
93921
| 185742