From eed2123b978b00bfd8c2c79a1294aab420afdb81 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Wed, 18 Nov 2015 15:52:14 +0100 Subject: [PATCH] Bug 15219: Server-side processing and 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 server-side processing and 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 server-side processing can be useless and cumbersome with small sets of data, a new system preference is introduced to control this behaviour (server-side processing 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) - adds "column settings" for relatives' checkouts tables - factorize some code that was duplicated across the two DataTables configurations (mDataProp) 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 Signed-off-by: Eric Gosselin --- admin/columns_settings.yml | 60 +- installer/data/mysql/atomicupdate/bug_15219.sql | 2 + installer/data/mysql/sysprefs.sql | 1 + .../plugins/jquery.dataTables.rowGrouping.js | 10 +- .../prog/en/includes/checkouts-table-footer.inc | 2 +- .../prog/en/includes/checkouts-table.inc | 1 - .../prog/en/includes/checkouts.js.inc | 24 + .../prog/en/includes/columns_settings.inc | 2 +- .../intranet-tmpl/prog/en/includes/strings.inc | 1 + .../en/modules/admin/preferences/circulation.pref | 6 + .../prog/en/modules/circ/circulation.tt | 13 +- .../prog/en/modules/members/moremember.tt | 12 +- koha-tmpl/intranet-tmpl/prog/js/checkouts.js | 748 ++++++++++----------- svc/checkouts | 105 +-- 14 files changed, 519 insertions(+), 468 deletions(-) create mode 100644 installer/data/mysql/atomicupdate/bug_15219.sql create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/checkouts.js.inc diff --git a/admin/columns_settings.yml b/admin/columns_settings.yml index f56a745eb5..73935302e0 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -209,7 +209,7 @@ modules: moremember: issues-table: - - columnname: sort_order + columnname: group_order cannot_be_toggled: 1 cannot_be_modified: 1 is_hidden: 1 @@ -218,11 +218,6 @@ modules: 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 - @@ -255,6 +250,29 @@ modules: columnname: export cannot_be_toggled: 1 cannot_be_modified: 1 + relatives-issues-table: + - + columnname: due_date + - + columnname: title + - + columnname: item_type + - + columnname: location + - + columnname: checkout_on + - + columnname: checkout_from + - + columnname: callno + - + columnname: charge + - + columnname: fine + - + columnname: price + - + columnname: patron holdshistory: holdshistory-table: @@ -321,7 +339,7 @@ modules: circulation: issues-table: - - columnname: sort_order + columnname: group_order cannot_be_toggled: 1 cannot_be_modified: 1 is_hidden: 1 @@ -330,11 +348,6 @@ modules: 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 - @@ -367,6 +380,29 @@ modules: columnname: export cannot_be_toggled: 1 cannot_be_modified: 1 + relatives-issues-table: + - + columnname: due_date + - + columnname: title + - + columnname: item_type + - + columnname: location + - + columnname: checkout_on + - + columnname: checkout_from + - + columnname: callno + - + columnname: charge + - + columnname: fine + - + columnname: price + - + columnname: patron returns: checkedintable: diff --git a/installer/data/mysql/atomicupdate/bug_15219.sql b/installer/data/mysql/atomicupdate/bug_15219.sql new file mode 100644 index 0000000000..a186d45abe --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_15219.sql @@ -0,0 +1,2 @@ +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/sysprefs.sql b/installer/data/mysql/sysprefs.sql index 76bb2cc03a..cb503c1908 100644 --- a/installer/data/mysql/sysprefs.sql +++ b/installer/data/mysql/sysprefs.sql @@ -488,6 +488,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('SelfCheckTimeout','120','','Define the number of seconds before the Web-based Self Checkout times out a patron','Integer'), ('SeparateHoldings','0',NULL,'Separate current branch holdings from other holdings','YesNo'), ('SeparateHoldingsBranch','homebranch','homebranch|holdingbranch','Branch used to separate holdings','Choice'), +('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'), diff --git a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jquery.dataTables.rowGrouping.js b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jquery.dataTables.rowGrouping.js index 126ff63357..12a88e4dba 100644 --- a/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jquery.dataTables.rowGrouping.js +++ b/koha-tmpl/intranet-tmpl/lib/jquery/plugins/jquery.dataTables.rowGrouping.js @@ -608,15 +608,15 @@ } - oTable.fnSetColumnVis(properties.iGroupingColumnIndex, !properties.bHideGroupingColumn); + oTable.fnSetColumnVis(properties.iGroupingColumnIndex, !properties.bHideGroupingColumn, false); if (properties.bCustomColumnOrdering) { - oTable.fnSetColumnVis(properties.iGroupingOrderByColumnIndex, !properties.bHideGroupingOrderByColumn); + oTable.fnSetColumnVis(properties.iGroupingOrderByColumnIndex, !properties.bHideGroupingOrderByColumn, false); } if (properties.iGroupingColumnIndex2 != -1) { - oTable.fnSetColumnVis(properties.iGroupingColumnIndex2, !properties.bHideGroupingColumn2); + oTable.fnSetColumnVis(properties.iGroupingColumnIndex2, !properties.bHideGroupingColumn2, false); } if (properties.bCustomColumnOrdering2) { - oTable.fnSetColumnVis(properties.iGroupingOrderByColumnIndex2, !properties.bHideGroupingOrderByColumn2); + oTable.fnSetColumnVis(properties.iGroupingOrderByColumnIndex2, !properties.bHideGroupingOrderByColumn2, false); } oTable.fnSettings().aoDrawCallback.push({ "fn": _fnDrawCallBackWithGrouping, @@ -687,4 +687,4 @@ }); }; -})(jQuery); \ No newline at end of file +})(jQuery); 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 3adbfa326a..a8b906b7c2 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,6 +1,6 @@ - Totals: + Totals: [% totaldue %] [% finetotal %] [% totalprice %] 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 b34089d3c7..b723d62a66 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts-table.inc @@ -13,7 +13,6 @@     Due date - Due date Title Item type Location diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts.js.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts.js.inc new file mode 100644 index 0000000000..c048ab883c --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/checkouts.js.inc @@ -0,0 +1,24 @@ +[% USE Koha %] + + + + diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc index 1ee31b0739..1d53b51c5d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc @@ -40,7 +40,7 @@ function KohaTable(id_selector, dt_parameters, columns_settings, add_filters) { table.dataTable($.extend(true, {}, dataTablesDefaults, dt_parameters)); $(hidden_ids).each(function(index, value) { - table.fnSetColumnVis( value, false ); + table.fnSetColumnVis( value, false, false ); }); if ( add_filters ) { diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/strings.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/strings.inc index e116204cdc..ff87e4b0ad 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/strings.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/strings.inc @@ -43,5 +43,6 @@ var SUSPEND_HOLD_ERROR_NOT_FOUND = _("Unable to suspend hold, hold not found"); var RESUME_HOLD_ERROR_NOT_FOUND = _("Unable to resume, hold not found"); var CURRENT = _(" (current) "); + var GROUP_TODAYS_CHECKOUTS = _("Group today's checkouts"); //]]> 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 178a048e91..75ee02636a 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 @@ -52,6 +52,12 @@ Circulation: asc: earliest to latest desc: latest to earliest - due date. + - + - pref: ServerSideCheckoutsTables + choices: + yes: Enable + no: 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 4c66febb6b..ba48183ab1 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -847,7 +847,6 @@ No patron matched [% message | html %] - @@ -981,24 +980,16 @@ No patron matched [% message | html %] [% INCLUDE 'timepicker.inc' %] - + [% INCLUDE 'checkouts.js.inc' %] - + [% INCLUDE 'checkouts.js.inc' %] @@ -559,22 +558,13 @@ /* Set some variable needed in circulation.js */ var interface = "[% interface %]"; var theme = "[% theme %]"; - var borrowernumber = "[% patron.borrowernumber %]"; var branchcode = "[% Branches.GetLoggedInBranchcode() %]"; var exports_enabled = "[% Koha.Preference('ExportCircHistory') %]"; - var AllowCirculate = [% (CAN_user_circulate_circulate_remaining_permissions)? 1 : 0 %] - var AllowRenewalLimitOverride = [% (CAN_user_circulate_override_renewals && Koha.Preference('AllowRenewalLimitOverride') )? 1: 0 %]; var script = "moremember"; - var relatives_borrowernumbers = new Array(); - [% FOREACH b IN relatives_borrowernumbers %] - relatives_borrowernumbers.push("[% b %]"); - [% END %] var MSG_EXPORT_SELECT_CHECKOUTS = _("You must select checkout(s) to export"); var MSG_CONFIRM_DELETE_MESSAGE = _("Are you sure you want to delete this message? This cannot be undone."); - columns_settings = [% ColumnsSettings.GetColumns( 'members', 'moremember', 'issues-table', 'json' ) %] - $(document).ready(function() { if ( $('#clubs-tab').length ) { $('#clubs-tab-link').on('click', function() { diff --git a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js index 5167ee12ae..a8e94e0625 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/checkouts.js +++ b/koha-tmpl/intranet-tmpl/prog/js/checkouts.js @@ -1,3 +1,267 @@ +(function($) { + +var ymd = $.datepicker.formatDate('yy-mm-dd', new Date()); + +function enableGrouping(datatable) { + datatable.rowGrouping({ + iGroupingColumnIndex: 1, + iGroupingOrderByColumnIndex: 0, + sGroupingColumnSortDirection: "desc" + }); +} + +function disableGrouping(datatable) { + var oSettings = datatable.fnSettings(); + for (f = 0; f < oSettings.aoDrawCallback.length; f++) { + if (oSettings.aoDrawCallback[f].sName == 'fnRowGrouping') { + oSettings.aoDrawCallback.splice(f, 1); + oSettings.aaSortingFixed = null; + datatable.fnDraw(); + break; + } + } +} + +var mRenders = { + groupOrder: function (oData, sType, oObj) { + if ( oObj.issued_today ) { + return "1"; + } else { + return "0"; + } + }, + groupName: function (oData, sType, oObj) { + if (oObj.issued_today) { + return "" + TODAYS_CHECKOUTS + ""; + } else { + return "" + PREVIOUS_CHECKOUTS + ""; + } + }, + dateDueFormatted: function (oData, sType, oObj) { + var due = oObj.date_due_formatted; + + if ( oObj.date_due_overdue ) { + due = "" + due + ""; + } + + due = "" + due + ""; + + if ( oObj.lost ) { + due += "" + oObj.lost + ""; + } + + if ( oObj.damaged ) { + due += "" + oObj.damaged + ""; + } + + return due; + }, + title: function (oData, sType, oObj) { + var title = "" + + oObj.title; + + $.each(oObj.subtitle, function( index, value ) { + title += " " + value.subfield; + }); + + if ( oObj.enumchron ) { + title += " (" + oObj.enumchron + ")"; + } + + title += ""; + + if ( oObj.author ) { + title += " " + BY.replace( "_AUTHOR_", " " + oObj.author ); + } + + if ( oObj.itemnotes ) { + var span_class = "text-muted"; + if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { + span_class = "circ-hlt"; + } + title += " - " + oObj.itemnotes + ""; + } + + if ( oObj.itemnotes_nonpublic ) { + var span_class = "text-danger"; + if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { + span_class = "circ-hlt"; + } + title += " - " + oObj.itemnotes_nonpublic + ""; + } + + var onsite_checkout = ''; + if ( oObj.onsite_checkout == 1 ) { + onsite_checkout += " (" + INHOUSE_USE + ")"; + } + + title += " " + + "" + + oObj.barcode + + "" + + onsite_checkout + + return title; + }, + itemtype: function (oData, sType, oObj) { + return oObj.itemtype_description; + }, + issuedate: function (oData, sType, oObj) { + return oObj.issuedate_formatted; + }, + branch: function (oData, sType, oObj) { + return oObj.branchname; + }, + charge: function (oData, sType, oObj) { + if (!oObj.charge) oObj.charge = 0; + return '' + parseFloat(oObj.charge).toFixed(2) + ''; + }, + fine: function (oData, sType, oObj) { + if (!oObj.fine) oObj.fine = 0; + return '' + parseFloat(oObj.fine).toFixed(2) + ''; + }, + price: function (oData, sType, oObj) { + if (!oObj.price) oObj.price = 0; + return parseFloat(oObj.price).toFixed(2); + }, + renew: function (oData, sType, oObj) { + var content = ""; + var span_style = ""; + var span_class = ""; + + content += ""; + content += "" + oObj.renewals_count + ""; + + if ( oObj.can_renew ) { + // Do nothing + } else if ( oObj.can_renew_error == "on_reserve" ) { + content += "" + + "" + ON_HOLD + "" + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "too_many" ) { + content += "" + + NOT_RENEWABLE + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "restriction" ) { + content += "" + + NOT_RENEWABLE_RESTRICTION + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "overdue" ) { + content += "" + + NOT_RENEWABLE_OVERDUE + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "too_soon" ) { + content += "" + + NOT_RENEWABLE_TOO_SOON.format( oObj.can_renew_date ) + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "auto_too_soon" ) { + content += "" + + NOT_RENEWABLE_AUTO_TOO_SOON + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "auto_too_late" ) { + content += "" + + NOT_RENEWABLE_AUTO_TOO_LATE + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "auto_too_much_oweing" ) { + content += "" + + NOT_RENEWABLE_AUTO_TOO_MUCH_OWEING + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "auto_account_expired" ) { + content += "" + + NOT_RENEWABLE_AUTO_ACCOUNT_EXPIRED + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "auto_renew" ) { + content += "" + + NOT_RENEWABLE_AUTO_RENEW + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } else if ( oObj.can_renew_error == "onsite_checkout" ) { + // Don't display something if it's an onsite checkout + } else { + content += "" + + oObj.can_renew_error + + ""; + + span_style = "display: none"; + span_class = "renewals-allowed"; + } + + var can_force_renew = ( oObj.onsite_checkout == 0 ) && ( oObj.can_renew_error != "on_reserve" ); + var can_renew = ( oObj.renewals_remaining > 0 && !oObj.can_renew_error ); + if ( can_renew || can_force_renew ) { + content += "" + + "" + + ""; + + content += "(" + + RENEWALS_REMAINING.format( oObj.renewals_remaining, oObj.renewals_allowed ) + + ")"; + } + + content += ""; + + return content; + }, + checkin: function (oData, sType, oObj) { + if (oObj.can_renew_error == "on_reserve") { + return "" + ON_HOLD + ""; + } else { + return ""; + } + }, + export: function (oData, sType, oObj) { + var s = ""; + + s += ""; + return s; + }, + patron: function (oData, sType, oObj) { + return "" + + oObj.borrower.firstname + " " + oObj.borrower.surname + " (" + oObj.borrower.cardnumber + ")" + } +}; + $(document).ready(function() { $.ajaxSetup ({ cache: false }); @@ -152,8 +416,6 @@ $(document).ready(function() { return false; }); - var ymd = $.datepicker.formatDate('yy-mm-dd', new Date()); - $('#issues-table').hide(); $('#issues-table-actions').hide(); $('#issues-table-load-immediately').change(function(){ @@ -187,16 +449,24 @@ $(document).ready(function() { "sProcessing": MSG_DT_LOADING_RECORDS, }, "bAutoWidth": false, - "dom": 'B<"clearfix">rt', + "bProcessing": true, + "bServerSide": ServerSideCheckoutsTables, + "sAjaxSource": "/cgi-bin/koha/svc/checkouts", + "fnServerParams": function (aoData) { + aoData.push({ "name": "borrowernumber", "value": borrowernumber }); + }, + "bPaginate": true, + "sPaginationType": "four_button", + "dom": 'B<"clearfix"><"top pager"ilp>rt', + "aaSorting": [], "aoColumns": [ { - "mDataProp": function( oObj ) { - return oObj.sort_order; - } + "mData": "issued_today", + "mRender": mRenders.groupOrder }, { - "mDataProp": function( oObj ) { - if ( oObj.issued_today ) { + "mData": function (oObj) { + if (oObj.issued_today) { return "" + TODAYS_CHECKOUTS + ""; } else { return "" + PREVIOUS_CHECKOUTS + ""; @@ -204,251 +474,57 @@ $(document).ready(function() { } }, { - "mDataProp": "date_due", - "bVisible": false, + "mData": "date_due", + "mRender": mRenders.dateDueFormatted }, { - "iDataSort": 2, // Sort on hidden unformatted date due column - "mDataProp": function( oObj ) { - var due = oObj.date_due_formatted; - - if ( oObj.date_due_overdue ) { - due = "" + due + ""; - } - - due = "" + due + ""; - - if ( oObj.lost ) { - due += "" + oObj.lost + ""; - } - - if ( oObj.damaged ) { - due += "" + oObj.damaged + ""; - } - - var patron_note = " "; - due +="
" + patron_note; - - return due; - } + "mData": "title", + "mRender": mRenders.title }, { - "mDataProp": function ( oObj ) { - title = "" - + oObj.title; - - $.each(oObj.subtitle, function( index, value ) { - title += " " + value.subfield; - }); - - if ( oObj.enumchron ) { - title += " (" + oObj.enumchron + ")"; - } - - title += ""; - - if ( oObj.author ) { - title += " " + BY.replace( "_AUTHOR_", " " + oObj.author ); - } - - if ( oObj.itemnotes ) { - var span_class = "text-muted"; - if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { - span_class = "circ-hlt"; - } - title += " - " + oObj.itemnotes + ""; - } - - if ( oObj.itemnotes_nonpublic ) { - var span_class = "text-danger"; - if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { - span_class = "circ-hlt"; - } - title += " - " + oObj.itemnotes_nonpublic + ""; - } - - var onsite_checkout = ''; - if ( oObj.onsite_checkout == 1 ) { - onsite_checkout += " (" + INHOUSE_USE + ")"; - } - - title += " " - + "" - + oObj.barcode - + "" - + onsite_checkout - - return title; - }, - "sType": "anti-the" + "mData": "itemtype", + "mRender": mRenders.itemtype }, - { "mDataProp": "itemtype_description" }, - { "mDataProp": "location" }, - { "mDataProp": "homebranch" }, - { "mDataProp": "issuedate_formatted" }, - { "mDataProp": "branchname" }, - { "mDataProp": "itemcallnumber" }, + { "mData": "location" }, + { "mData": "homebranch" }, { - "mDataProp": function ( oObj ) { - if ( ! oObj.charge ) oObj.charge = 0; - return '' + parseFloat(oObj.charge).toFixed(2) + ''; - } + "mData": "issuedate", + "mRender": mRenders.issuedate }, { - "mDataProp": function ( oObj ) { - if ( ! oObj.fine ) oObj.fine = 0; - return '' + parseFloat(oObj.fine).toFixed(2) + ''; - } + "mData": "branchcode", + "mRender": mRenders.branch }, + { "mData": "itemcallnumber" }, { - "mDataProp": function ( oObj ) { - if ( ! oObj.price ) oObj.price = 0; - return '' + parseFloat(oObj.price).toFixed(2) + ''; - } + "mData": "charge", + "mRender": mRenders.charge, + "bSortable": ServerSideCheckoutsTables ? false : true + }, + { + "mData": "fine", + "mRender": mRenders.fine, + "bSortable": ServerSideCheckoutsTables ? false : true + }, + { + "mData": "replacementprice", + "mRender": mRenders.price, + "bSortable": ServerSideCheckoutsTables ? false : true }, { - "bSortable": false, "bVisible": AllowCirculate ? true : false, - "mDataProp": function ( oObj ) { - var content = ""; - var span_style = ""; - var span_class = ""; - - content += ""; - content += "" + oObj.renewals_count + ""; - - if ( oObj.can_renew ) { - // Do nothing - } else if ( oObj.can_renew_error == "on_reserve" ) { - content += "" - + "" + ON_HOLD + "" - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "too_many" ) { - content += "" - + NOT_RENEWABLE - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "restriction" ) { - content += "" - + NOT_RENEWABLE_RESTRICTION - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "overdue" ) { - content += "" - + NOT_RENEWABLE_OVERDUE - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "too_soon" ) { - content += "" - + NOT_RENEWABLE_TOO_SOON.format( oObj.can_renew_date ) - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "auto_too_soon" ) { - content += "" - + NOT_RENEWABLE_AUTO_TOO_SOON - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "auto_too_late" ) { - content += "" - + NOT_RENEWABLE_AUTO_TOO_LATE - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "auto_too_much_oweing" ) { - content += "" - + NOT_RENEWABLE_AUTO_TOO_MUCH_OWEING - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "auto_account_expired" ) { - content += "" - + NOT_RENEWABLE_AUTO_ACCOUNT_EXPIRED - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "auto_renew" ) { - content += "" - + NOT_RENEWABLE_AUTO_RENEW - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } else if ( oObj.can_renew_error == "onsite_checkout" ) { - // Don't display something if it's an onsite checkout - } else { - content += "" - + oObj.can_renew_error - + ""; - - span_style = "display: none"; - span_class = "renewals-allowed"; - } - - var can_force_renew = ( oObj.onsite_checkout == 0 ) && ( oObj.can_renew_error != "on_reserve" ); - var can_renew = ( oObj.renewals_remaining > 0 && !oObj.can_renew_error ); - if ( can_renew || can_force_renew ) { - content += "" - + "" - + ""; - - content += "(" - + RENEWALS_REMAINING.format( oObj.renewals_remaining, oObj.renewals_allowed ) - + ")"; - } - - content += ""; - - return content; - } + "mRender": mRenders.renew, + "bSortable": false }, { - "bSortable": false, "bVisible": AllowCirculate ? true : false, - "mDataProp": function ( oObj ) { - if ( oObj.can_renew_error == "on_reserve" ) { - return "" + ON_HOLD + ""; - } else { - return ""; - } - } + "mRender": mRenders.checkin, + "bSortable": false }, { "bVisible": exports_enabled == 1 ? true : false, "bSortable": false, - "mDataProp": function ( oObj ) { - var s = ""; - - s += ""; - return s; - } + "mRender": mRenders.export } ], "fnFooterCallback": function ( nRow, aaData, iStart, iEnd, aiDisplay ) { @@ -463,39 +539,30 @@ $(document).ready(function() { $("#totaldue").html(total_charge.toFixed(2)); $("#totalfine").html(total_fine.toFixed(2)); $("#totalprice").html(total_price.toFixed(2)); - }, - "bPaginate": false, - "bProcessing": true, - "bServerSide": false, - "sAjaxSource": '/cgi-bin/koha/svc/checkouts', - "fnServerData": function ( sSource, aoData, fnCallback ) { - aoData.push( { "name": "borrowernumber", "value": borrowernumber } ); - - $.getJSON( sSource, aoData, function (json) { - fnCallback(json) - } ); - }, - "fnInitComplete": function(oSettings) { - // Disable rowGrouping plugin after first use - // so any sorting on the table doesn't use it - var oSettings = issuesTable.fnSettings(); - - for (f = 0; f < oSettings.aoDrawCallback.length; f++) { - if (oSettings.aoDrawCallback[f].sName == 'fnRowGrouping') { - oSettings.aoDrawCallback.splice(f, 1); - break; - } - } - - oSettings.aaSortingFixed = null; - }, - }, columns_settings).rowGrouping( - { - iGroupingColumnIndex: 1, - iGroupingOrderByColumnIndex: 0, - sGroupingColumnSortDirection: "asc" } - ); + }, columns_settings); + + enableGrouping(issuesTable); + + var groupingSwitch = $('') + .attr('id', 'issues-table-row-grouping-switch') + .attr('checked', 'checked') + .css('vertical-align', 'middle') + .change(function() { + if ($(this).is(':checked')) { + enableGrouping(issuesTable); + } else { + disableGrouping(issuesTable); + } + }); + $('
') + .css('float', 'left') + .css('padding', '0.3em 0.5em') + .css('line-height', '1.9em') + .append(groupingSwitch) + .append(' ') + .append('') + .appendTo('#issues-table_wrapper .top.pager'); if ( $("#issues-table").length ) { $("#issues-table_processing").position({ @@ -509,129 +576,60 @@ $(document).ready(function() { var relativesIssuesTable; $("#relatives-issues-tab").click( function() { if ( ! relativesIssuesTable ) { - relativesIssuesTable = $("#relatives-issues-table").dataTable({ + relativesIssuesTable = KohaTable("#relatives-issues-table", { "bAutoWidth": false, - "sDom": "rt", - "aaSorting": [], + "sDom": 'C<"top pager"ilp>rt', + "aaSorting": [[4, "desc"]], + "bProcessing": true, + "bServerSide": ServerSideCheckoutsTables, + "sAjaxSource": "/cgi-bin/koha/svc/checkouts", + "fnServerParams": function (aoData) { + for (i in relatives_borrowernumbers) { + var borrowernumber = relatives_borrowernumbers[i]; + aoData.push({ "name": "borrowernumber", "value": borrowernumber }); + } + }, + "bPaginate": true, + "sPaginationType": "four_button", "aoColumns": [ { - "mDataProp": "date_due", - "bVisible": false, + "mData": "date_due", + "mRender": mRenders.dateDueFormatted }, { - "iDataSort": 1, // Sort on hidden unformatted date due column - "mDataProp": function( oObj ) { - var today = new Date(); - var due = new Date( oObj.date_due ); - if ( today > due ) { - return "" + oObj.date_due_formatted + ""; - } else { - return oObj.date_due_formatted; - } - } + "mData": "title", + "mRender": mRenders.title, }, + { "mData": "itemtype" }, + { "mData": "location" }, { - "mDataProp": function ( oObj ) { - title = "" - + oObj.title; - - $.each(oObj.subtitle, function( index, value ) { - title += " " + value.subfield; - }); - - if ( oObj.enumchron ) { - title += " (" + oObj.enumchron + ")"; - } - - title += ""; - - if ( oObj.author ) { - title += " " + BY.replace( "_AUTHOR_", " " + oObj.author ); - } - - if ( oObj.itemnotes ) { - var span_class = ""; - if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { - span_class = "circ-hlt"; - } - title += " - " + oObj.itemnotes + "" - } - - if ( oObj.itemnotes_nonpublic ) { - var span_class = ""; - if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) { - span_class = "circ-hlt"; - } - title += " - " + oObj.itemnotes_nonpublic + "" - } - - var onsite_checkout = ''; - if ( oObj.onsite_checkout == 1 ) { - onsite_checkout += " (" + INHOUSE_USE + ")"; - } - - title += " " - + "" - + oObj.barcode - + "" - + onsite_checkout; - - return title; - }, - "sType": "anti-the" + "mData": "issuedate", + "mRender": mRenders.issuedate }, - { "mDataProp": "itemtype" }, - { "mDataProp": "location" }, - { "mDataProp": "issuedate_formatted" }, - { "mDataProp": "branchname" }, - { "mDataProp": "itemcallnumber" }, + { "mData": "branchname" }, + { "mData": "itemcallnumber" }, { - "mDataProp": function ( oObj ) { - if ( ! oObj.charge ) oObj.charge = 0; - return parseFloat(oObj.charge).toFixed(2); - } + "mData": "charge", + "mRender": mRenders.charge, + "bSortable": ServerSideCheckoutsTables ? false : true }, { - "mDataProp": function ( oObj ) { - if ( ! oObj.fine ) oObj.fine = 0; - return parseFloat(oObj.fine).toFixed(2); - } + "mData": "fine", + "mRender": mRenders.fine, + "bSortable": ServerSideCheckoutsTables ? false : true }, { - "mDataProp": function ( oObj ) { - if ( ! oObj.price ) oObj.price = 0; - return parseFloat(oObj.price).toFixed(2); - } + "mData": "replacementprice", + "mRender": mRenders.price, + "bSortable": ServerSideCheckoutsTables ? false : true }, { - "mDataProp": function( oObj ) { - return "" - + oObj.borrower.firstname + " " + oObj.borrower.surname + " (" + oObj.borrower.cardnumber + ")" - } - }, - ], - "bPaginate": false, - "bProcessing": true, - "bServerSide": false, - "sAjaxSource": '/cgi-bin/koha/svc/checkouts', - "fnServerData": function ( sSource, aoData, fnCallback ) { - $.each(relatives_borrowernumbers, function( index, value ) { - aoData.push( { "name": "borrowernumber", "value": value } ); - }); - - $.getJSON( sSource, aoData, function (json) { - fnCallback(json) - } ); - }, - }); + "mData": "borrowernumber", + "mRender": mRenders.patron, + "bSortable": ServerSideCheckoutsTables ? false : true + } + ] + }, relatives_columns_settings); } }); @@ -651,4 +649,6 @@ $(document).ready(function() { } } ).prop('checked', false); } - }); +}); + +})(jQuery); diff --git a/svc/checkouts b/svc/checkouts index fefadeeeea..34a8562e6c 100755 --- a/svc/checkouts +++ b/svc/checkouts @@ -27,6 +27,7 @@ use C4::Biblio qw(GetMarcBiblio GetFrameworkCode GetRecordValue ); use C4::Circulation qw(GetIssuingCharges CanBookBeRenewed GetRenewCount GetSoonestRenewDate); use C4::Overdues qw(GetFine); use C4::Context; +use C4::Utils::DataTables; use Koha::AuthorisedValues; use Koha::DateUtils; @@ -45,26 +46,33 @@ unless (haspermission($userid, { circulate => 'circulate_remaining_permissions' exit 0; } -my @sort_columns = qw/date_due title itype issuedate branchcode itemcallnumber/; - my @borrowernumber = $input->multi_param('borrowernumber'); -my $offset = $input->param('iDisplayStart'); -my $results_per_page = $input->param('iDisplayLength') || -1; - -my $sorting_column = $input->param('iSortCol_0') || q{}; -$sorting_column = ( $sorting_column && $sort_columns[$sorting_column] ) ? $sort_columns[$sorting_column] : 'issuedate'; - -my $sorting_direction = $input->param('sSortDir_0') || q{}; -$sorting_direction = $sorting_direction eq 'asc' ? 'asc' : 'desc'; - -$results_per_page = undef if ( $results_per_page == -1 ); - -binmode STDOUT, ":encoding(UTF-8)"; -print $input->header( -type => 'text/plain', -charset => 'UTF-8' ); +my $offset = $input->param('iDisplayStart'); +my $count = $input->param('iDisplayLength'); + +my %params = dt_get_params($input); +my $orderby = dt_build_orderby(\%params); + +my $todaysIssuesDefaultSortOrder = + C4::Context->preference('todaysIssuesDefaultSortOrder'); +my $previousIssuesDefaultSortOrder = + C4::Context->preference('previousIssuesDefaultSortOrder'); + +# 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 issuedate, date_due, date_due < now() as date_due_overdue, @@ -100,7 +108,7 @@ my $sql = ' location, items.enumchron, - DATEDIFF( issuedate, CURRENT_DATE() ) AS not_issued_today + DATEDIFF( issuedate, CURRENT_DATE() ) = 0 AS issued_today FROM issues LEFT JOIN items USING ( itemnumber ) LEFT JOIN biblio USING ( biblionumber ) @@ -108,27 +116,33 @@ my $sql = ' LEFT JOIN borrowers USING ( borrowernumber ) LEFT JOIN branches ON ( issues.branchcode = branches.branchcode ) LEFT JOIN branches branches2 ON ( items.homebranch = branches2.branchcode ) - WHERE borrowernumber + WHERE '; +my $borrowernumber_sql = 'borrowernumber'; if ( @borrowernumber == 1 ) { - $sql .= '= ?'; -} -else { - $sql .= ' IN (' . join( ',', ('?') x @borrowernumber ) . ') '; + $borrowernumber_sql .= ' = ?'; +} else { + $borrowernumber_sql .= ' IN (' . join( ',', ('?') x @borrowernumber ) . ') '; } +$sql .= $borrowernumber_sql; push( @parameters, @borrowernumber ); -$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); $sth->execute(@parameters); +my ($iTotalDisplayRecords) = $dbh->selectrow_array('SELECT FOUND_ROWS()'); my $item_level_itypes = C4::Context->preference('item-level_itypes'); -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} ); @@ -219,36 +233,25 @@ while ( my $c = $sth->fetchrow_hashref() ) { firstname => $c->{firstname}, cardnumber => $c->{cardnumber}, }, - issued_today => !$c->{not_issued_today}, + issued_today => $c->{issued_today} ? JSON::true : JSON::false, }; - if ( $c->{not_issued_today} ) { - push( @checkouts_previous, $checkout ); - } - else { - push( @checkouts_today, $checkout ); - } + push @checkouts, $checkout; } +binmode STDOUT, ":encoding(UTF-8)"; +print $input->header( -type => 'application/json', -charset => 'UTF-8' ); -@checkouts_today = sort { $a->{timestamp} cmp $b->{timestamp} } @checkouts_today; -@checkouts_today = reverse(@checkouts_today) - unless ( C4::Context->preference('todaysIssuesDefaultSortOrder') eq 'desc' ); - -@checkouts_previous = sort { $a->{date_due} cmp $b->{date_due} } @checkouts_previous; -@checkouts_previous = reverse(@checkouts_previous) - if ( C4::Context->preference('previousIssuesDefaultSortOrder') eq 'desc' ); - -my @checkouts = ( @checkouts_today, @checkouts_previous ); - -my $i = 1; -map { $_->{sort_order} = $i++ } @checkouts; - +my $sEcho = $input->param('sEcho'); +my ($iTotalRecords) = $dbh->selectrow_array( + "SELECT COUNT(*) FROM issues WHERE $borrowernumber_sql", undef, + @borrowernumber); -my $data; -$data->{'iTotalRecords'} = scalar @checkouts; -$data->{'iTotalDisplayRecords'} = scalar @checkouts; -$data->{'sEcho'} = $input->param('sEcho') || undef; -$data->{'aaData'} = \@checkouts; +my $response = { + iTotalRecords => $iTotalRecords, + iTotalDisplayRecords => $iTotalDisplayRecords, + sEcho => $sEcho, + aaData => \@checkouts, +}; -print to_json($data); +print to_json($response); -- 2.14.2
Due date (unformatted, hidden) Due date Title Item type