From 22bccf2c8282c469859bde04cfc0f21ab0cc61b4 Mon Sep 17 00:00:00 2001 From: Kyle M Hall Date: Tue, 4 Feb 2014 12:42:55 -0500 Subject: [PATCH] WIP - Bug 11703 - Convert checkouts table to ajax datatable --- Koha/Schema/Result/Issue.pm | 24 + api/checkin.pl | 75 ++++ api/checkouts.pl | 116 +++++ api/renew.pl | 63 +++ circ/circulation.pl | 94 ---- .../intranet-tmpl/prog/en/css/staff-global.css | 2 +- .../intranet-tmpl/prog/en/js/pages/circulation.js | 141 +++++-- .../prog/en/modules/circ/circulation.tt | 452 +++++++++----------- 8 files changed, 580 insertions(+), 387 deletions(-) create mode 100755 api/checkin.pl create mode 100755 api/checkouts.pl create mode 100755 api/renew.pl diff --git a/Koha/Schema/Result/Issue.pm b/Koha/Schema/Result/Issue.pm index 282c802..cdf9675 100644 --- a/Koha/Schema/Result/Issue.pm +++ b/Koha/Schema/Result/Issue.pm @@ -190,4 +190,28 @@ __PACKAGE__->belongs_to( { join_type => "LEFT", on_delete => "CASCADE", on_update => "CASCADE" }, ); +__PACKAGE__->belongs_to( + "item", + "Koha::Schema::Result::Item", + { itemnumber => "itemnumber" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "CASCADE", + on_update => "CASCADE", + }, +); + +__PACKAGE__->belongs_to( + "branch", + "Koha::Schema::Result::Branch", + { branchcode => "branchcode" }, + { + is_deferrable => 1, + join_type => "LEFT", + on_delete => "CASCADE", + on_update => "CASCADE", + }, +); + 1; diff --git a/api/checkin.pl b/api/checkin.pl new file mode 100755 index 0000000..808b5c4 --- /dev/null +++ b/api/checkin.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl + +# Copyright 2014 ByWater Solutions +# +# This file is part of Koha. +# +# 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 2 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use CGI; +use JSON qw(to_json); + +use C4::Circulation; +use C4::Items qw(GetBarcodeFromItemnumber); +use C4::Context; +use C4::Auth qw(check_cookie_auth); + +use Koha::DateUtils qw(output_pref_due); + +my $input = new CGI; + +my ( $auth_status, $sessionID ) = + check_cookie_auth( $input->cookie('CGISESSID'), + { circulate => 'circulate_remaining_permissions' } ); + +if ( $auth_status ne "ok" ) { + exit 0; +} + +binmode STDOUT, ":encoding(UTF-8)"; +print $input->header( -type => 'text/plain', -charset => 'UTF-8' ); + +my $itemnumber = $input->param('itemnumber'); +my $borrowernumber = $input->param('borrowernumber'); +my $override_limit = $input->param('override_limit'); +my $exempt_fine = $input->param('exempt_fine'); +my $branchcode = $input->param('branchcode') + || C4::Context->userenv->{'branch'}; + +my $barcode = GetBarcodeFromItemnumber($itemnumber); + +my $data; +$data->{itemnumber} = $itemnumber; +$data->{borrowernumber} = $borrowernumber; +$data->{branchcode} = $branchcode; + +if ( C4::Context->preference("InProcessingToShelvingCart") ) { + my $item = GetItem($itemnumber); + if ( $item->{'location'} eq 'PROC' ) { + $item->{'location'} = 'CART'; + ModItem( $item, $item->{'biblionumber'}, $item->{'itemnumber'} ); + } +} + +if ( C4::Context->preference("ReturnToShelvingCart") ) { + my $item = GetItem($itemnumber); + $item->{'location'} = 'CART'; + ModItem( $item, $item->{'biblionumber'}, $item->{'itemnumber'} ); +} + +( $data->{returned} ) = AddReturn( $barcode, $branchcode, $exempt_fine ); + +print to_json($data); diff --git a/api/checkouts.pl b/api/checkouts.pl new file mode 100755 index 0000000..4e1db3d --- /dev/null +++ b/api/checkouts.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl + +# This software is placed under the gnu General Public License, v2 (http://www.gnu.org/licenses/gpl.html) + +# Copyright 2014 ByWater Solutions +# +# This file is part of Koha. +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use CGI; +use JSON qw(to_json); + +use C4::Context; +use C4::Charset; +use C4::Auth qw(check_cookie_auth); +use C4::Circulation qw(GetIssuingCharges CanBookBeRenewed GetRenewCount); + +use Koha::Database; +use Koha::DateUtils; + +my $input = new CGI; + +my ( $auth_status, $sessionID ) = + check_cookie_auth( $input->cookie('CGISESSID'), + { circulate => 'circulate_remaining_permissions' } ); + +if ( $auth_status ne "ok" ) { + exit 0; +} + +my $schema = Koha::Database->new()->schema(); + +my @sort_columns = qw/date_due title itype issuedate branchcode itemcallnumber/; + +my $borrowernumber = $input->param('borrowernumber'); +my $offset = $input->param('iDisplayStart'); +my $results_per_page = $input->param('iDisplayLength'); +my $sorting_column = $sort_columns[ $input->param('iSortCol_0') ]; +my $sorting_direction = $input->param('sSortDir_0'); + +$results_per_page = undef if ( $results_per_page == -1 ); + +binmode STDOUT, ":encoding(UTF-8)"; +print $input->header( -type => 'text/plain', -charset => 'UTF-8' ); + +my $checkouts_rs = + $schema->resultset('Issue')->search( { borrowernumber => $borrowernumber }, + { prefetch => { 'item' => 'biblio' }, orderby => 'issuedate' } ); + +my @checkouts; +while ( my $c = $checkouts_rs->next() ) { + my $itemnumber = $c->itemnumber()->itemnumber(); + + my ($charge) = GetIssuingCharges( + $c->itemnumber()->itemnumber(), + $c->borrowernumber()->borrowernumber() + ); + + my ( $can_renew, $can_renew_error ) = + CanBookBeRenewed( $borrowernumber, $itemnumber ); + + my ( $renewals_count, $renewals_allowed, $renewals_remaining ) = + GetRenewCount( $borrowernumber, $itemnumber ); + + push( + @checkouts, + { + DT_RowId => "$itemnumber-$borrowernumber", + title => $c->item()->biblio()->title(), + author => $c->item()->biblio()->author(), + barcode => $c->item()->barcode(), + itemtype => $c->item()->effective_itemtype(), + branchcode => $c->branchcode(), + branchname => $c->branch->branchname(), + itemcallnumber => $c->item()->itemcallnumber() || q{}, + charge => $charge, + price => $c->item->replacementprice() || q{}, + can_renew => $can_renew, + can_renew_error => $can_renew_error, + itemnumber => $itemnumber, + borrowernumber => $borrowernumber, + biblionumber => $c->item()->biblio()->biblionumber, + issuedate => $c->issuedate(), + date_due => $c->date_due(), + renewals_count => $renewals_count, + renewals_allowed => $renewals_allowed, + renewals_remaining => $renewals_remaining, + issuedate_formatted => + output_pref( dt_from_string( $c->issuedate() ) ), + date_due_formatted => + output_pref_due( dt_from_string( $c->date_due() ) ), + } + ); +} + +my $data; +$data->{'iTotalRecords'} = scalar @checkouts; #FIXME +$data->{'iTotalDisplayRecords'} = scalar @checkouts; +$data->{'sEcho'} = $input->param('sEcho') || undef; +$data->{'aaData'} = \@checkouts; + +print to_json($data); diff --git a/api/renew.pl b/api/renew.pl new file mode 100755 index 0000000..e35b6b9 --- /dev/null +++ b/api/renew.pl @@ -0,0 +1,63 @@ +#!/usr/bin/perl + +# Copyright 2014 ByWater Solutions +# +# This file is part of Koha. +# +# 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 2 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use CGI; +use JSON qw(to_json); + +use C4::Circulation; +use C4::Context; +use C4::Auth qw(check_cookie_auth); + +use Koha::DateUtils qw(output_pref_due); + +my $input = new CGI; + +my ( $auth_status, $sessionID ) = + check_cookie_auth( $input->cookie('CGISESSID'), + { circulate => 'circulate_remaining_permissions' } ); + +if ( $auth_status ne "ok" ) { + exit 0; +} + +binmode STDOUT, ":encoding(UTF-8)"; +print $input->header( -type => 'text/plain', -charset => 'UTF-8' ); + +my $itemnumber = $input->param('itemnumber'); +my $borrowernumber = $input->param('borrowernumber'); +my $override_limit = $input->param('override_limit'); +my $branchcode = $input->param('branchcode') + || C4::Context->userenv->{'branch'}; + +my $data; +$data->{itemnumber} = $itemnumber; +$data->{borrowernumber} = $borrowernumber; +$data->{branchcode} = $branchcode; + +( $data->{renew_okay}, $data->{error} ) = + CanBookBeRenewed( $borrowernumber, $itemnumber, $override_limit ); + +if ( $data->{renew_okay} ) { + my $date_due = AddRenewal( $borrowernumber, $itemnumber, $branchcode ); + $data->{date_due} = output_pref_due( $date_due ); +} + +print to_json($data); diff --git a/circ/circulation.pl b/circ/circulation.pl index 7d214ea..1b38220 100755 --- a/circ/circulation.pl +++ b/circ/circulation.pl @@ -96,14 +96,6 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user ( my $branches = GetBranches(); -my @failedrenews = $query->param('failedrenew'); # expected to be itemnumbers -our %renew_failed = (); -for (@failedrenews) { $renew_failed{$_} = 1; } - -my @failedreturns = $query->param('failedreturn'); -our %return_failed = (); -for (@failedreturns) { $return_failed{$_} = 1; } - my $findborrower = $query->param('findborrower') || q{}; $findborrower =~ s|,| |g; my $borrowernumber = $query->param('borrowernumber'); @@ -466,91 +458,6 @@ our @relissues = (); our @relprevissues = (); my $displayrelissues; -our $totalprice = 0; - -sub build_issue_data { - my $issueslist = shift; - my $relatives = shift; - - # split in 2 arrays for today & previous - foreach my $it ( @$issueslist ) { - my $itemtypeinfo = getitemtypeinfo( (C4::Context->preference('item-level_itypes')) ? $it->{'itype'} : $it->{'itemtype'} ); - - # set itemtype per item-level_itype syspref - FIXME this is an ugly hack - $it->{'itemtype'} = ( C4::Context->preference( 'item-level_itypes' ) ) ? $it->{'itype'} : $it->{'itemtype'}; - - ($it->{'charge'}, $it->{'itemtype_charge'}) = GetIssuingCharges( - $it->{'itemnumber'}, $it->{'borrowernumber'} - ); - $it->{'charge'} = sprintf("%.2f", $it->{'charge'}); - my ($can_renew, $can_renew_error) = CanBookBeRenewed( - $it->{'borrowernumber'},$it->{'itemnumber'} - ); - $it->{"renew_error_${can_renew_error}"} = 1 if defined $can_renew_error; - my $restype = C4::Reserves::GetReserveStatus( $it->{'itemnumber'} ); - $it->{'can_renew'} = $can_renew; - $it->{'can_confirm'} = !$can_renew && !$restype; - $it->{'renew_error'} = ( $restype eq "Waiting" or $restype eq "Reserved" ) ? 1 : 0; - $it->{'checkoutdate'} = C4::Dates->new($it->{'issuedate'},'iso')->output('syspref'); - $it->{'issuingbranchname'} = GetBranchName($it->{'branchcode'}); - - $totalprice += $it->{'replacementprice'} || 0; - $it->{'itemtype'} = $itemtypeinfo->{'description'}; - $it->{'itemtype_image'} = $itemtypeinfo->{'imageurl'}; - $it->{'dd_sort'} = $it->{'date_due'}; - $it->{'dd'} = output_pref($it->{'date_due'}); - $it->{'displaydate_sort'} = $it->{'issuedate'}; - $it->{'displaydate'} = output_pref($it->{'issuedate'}); - #$it->{'od'} = ( $it->{'date_due'} lt $todaysdate ) ? 1 : 0 ; - $it->{'od'} = $it->{'overdue'}; - $it->{'subtitle'} = GetRecordValue('subtitle', GetMarcBiblio($it->{biblionumber}), GetFrameworkCode($it->{biblionumber})); - $it->{'renew_failed'} = $renew_failed{$it->{'itemnumber'}}; - $it->{'return_failed'} = $return_failed{$it->{'barcode'}}; - - if ( ( $it->{'issuedate'} && $it->{'issuedate'} gt $todaysdate ) - || ( $it->{'lastreneweddate'} && $it->{'lastreneweddate'} gt $todaysdate ) ) { - (!$relatives) ? push @todaysissues, $it : push @relissues, $it; - } else { - (!$relatives) ? push @previousissues, $it : push @relprevissues, $it; - } - ($it->{'renewcount'},$it->{'renewsallowed'},$it->{'renewsleft'}) = C4::Circulation::GetRenewCount($it->{'borrowernumber'},$it->{'itemnumber'}); #Add renewal count to item data display - } -} - -if ($borrower) { - - # Getting borrower relatives - my @relborrowernumbers = GetMemberRelatives($borrower->{'borrowernumber'}); - #push @borrowernumbers, $borrower->{'borrowernumber'}; - - # get each issue of the borrower & separate them in todayissues & previous issues - my $issueslist = GetPendingIssues($borrower->{'borrowernumber'}); - my $relissueslist = []; - if ( @relborrowernumbers ) { - $relissueslist = GetPendingIssues(@relborrowernumbers); - } - - build_issue_data($issueslist, 0); - build_issue_data($relissueslist, 1); - - $displayrelissues = scalar($relissueslist); - - if ( C4::Context->preference( "todaysIssuesDefaultSortOrder" ) eq 'asc' ) { - @todaysissues = sort { $a->{'timestamp'} cmp $b->{'timestamp'} } @todaysissues; - } - else { - @todaysissues = sort { $b->{'timestamp'} cmp $a->{'timestamp'} } @todaysissues; - } - - if ( C4::Context->preference( "previousIssuesDefaultSortOrder" ) eq 'asc' ){ - @previousissues = sort { $a->{'date_due'} cmp $b->{'date_due'} } @previousissues; - } - else { - @previousissues = sort { $b->{'date_due'} cmp $a->{'date_due'} } @previousissues; - } -} - - my @values; my %labels; my $CGIselectborrower; @@ -739,7 +646,6 @@ $template->param( duedatespec => $duedatespec, message => $message, CGIselectborrower => $CGIselectborrower, - totalprice => sprintf('%.2f', $totalprice), totaldue => sprintf('%.2f', $total), todayissues => \@todaysissues, previssues => \@previousissues, diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css index 2ea7b4a..67bb50b 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -270,7 +270,7 @@ tr.even td, tr.even.highlight td { border-right : 1px solid #BCBCBC; } -td.od { +.overdue td.od { color : #cc0000; font-weight : bold; } diff --git a/koha-tmpl/intranet-tmpl/prog/en/js/pages/circulation.js b/koha-tmpl/intranet-tmpl/prog/en/js/pages/circulation.js index fe307bf..b8aac3d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/js/pages/circulation.js +++ b/koha-tmpl/intranet-tmpl/prog/en/js/pages/circulation.js @@ -1,57 +1,128 @@ $(document).ready(function() { - $('#patronlists').tabs(); - var allcheckboxes = $(".checkboxed"); - $("#renew_all").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=items]"); - allcheckboxes.unCheckCheckboxes(":input[name*=barcodes]"); - }); - $("#CheckAllitems").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=items]"); - allcheckboxes.unCheckCheckboxes(":input[name*=barcodes]"); return false; + // Handle the select all/none links for checkouts table columns + $("#CheckAllRenewals").on("click",function(){ + $("#UncheckAllCheckins").click(); + $(".renew:visible").attr("checked", "checked" ); + return false; }); - $("#CheckNoitems").on("click",function(){ - allcheckboxes.unCheckCheckboxes(":input[name*=items]"); return false; + $("#UncheckAllRenewals").on("click",function(){ + $(".renew:visible").removeAttr("checked"); + return false; }); - $("#CheckAllreturns").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=barcodes]"); - allcheckboxes.unCheckCheckboxes(":input[name*=items]"); return false; + + $("#CheckAllCheckins").on("click",function(){ + $("#UncheckAllRenewals").click(); + $(".checkin:visible").attr("checked", "checked" ); + return false; }); - $("#CheckNoreturns" ).on("click",function(){ - allcheckboxes.unCheckCheckboxes(":input[name*=barcodes]"); return false; + $("#UncheckAllCheckins").on("click",function(){ + $(".checkin:visible").removeAttr("checked"); + return false; }); - $("#CheckAllexports").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=biblionumbers]"); + $("#CheckAllExports").on("click",function(){ + $(".export:visible").attr("checked", "checked" ); return false; }); - $("#CheckNoexports").on("click",function(){ - allcheckboxes.unCheckCheckboxes(":input[name*=biblionumbers]"); + $("#UncheckAllExports").on("click",function(){ + $(".export:visible").removeAttr("checked"); return false; }); - $("#relrenew_all").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=items]"); - allcheckboxes.unCheckCheckboxes(":input[name*=barcodes]"); - }); - $("#relCheckAllitems").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=items]"); - allcheckboxes.unCheckCheckboxes(":input[name*=barcodes]"); return false; + // Don't allow both return and renew checkboxes to be checked + $(document).on("change", '.renew', function(){ + if ( $(this).is(":checked") ) { + $( "#checkin_" + $(this).val() ).removeAttr("checked"); + } }); - $("#relCheckNoitems").on("click",function(){ - allcheckboxes.unCheckCheckboxes(":input[name*=items]"); return false; + $(document).on("change", '.checkin', function(){ + if ( $(this).is(":checked") ) { + $( "#renew_" + $(this).val() ).removeAttr("checked"); + } }); - $("#relCheckAllreturns").on("click",function(){ - allcheckboxes.checkCheckboxes(":input[name*=barcodes]"); - allcheckboxes.unCheckCheckboxes(":input[name*=items]"); return false; + + // Handle renewals + $("#RenewCheckinChecked").on("click",function(){ + $(".checkin:checked:visible").each(function() { + itemnumber = $(this).val(); + + $(this).replaceWith(""); + + params = { + itemnumber: itemnumber, + borrowernumber: borrowernumber, + branchcode: branchcode + }; + + $.post( "/cgi-bin/koha/api/checkin.pl", params, function( data ) { + id = "#checkin_" + data.itemnumber; + + content = ""; + if ( data.returned ) { + content = _("Returned"); + } else { + content = _("Unable to return"); + } + + $(id).replaceWith( content ); + }, "json") + }); + + $(".renew:checked:visible").each(function() { + var override_limit = $("#override_limit").is(':checked') ? 1 : 0; + + var itemnumber = $(this).val(); + + $(this).replaceWith(""); + + var params = { + itemnumber: itemnumber, + borrowernumber: borrowernumber, + branchcode: branchcode, + override_limit: override_limit + }; + + $.post( "/cgi-bin/koha/api/renew.pl", params, function( data ) { + var id = "#renew_" + data.itemnumber; + + var content = ""; + if ( data.renew_okay ) { + content = _("Renewed, due: ") + data.date_due; + } else { + content = _("Renew failed: "); + if ( data.error == "no_checkout" ) { + content += _("not checked out"); + } else if ( data.error == "too_many" ) { + content += _("too many renewals"); + } else if ( data.error == "on_reserve" ) { + content += _("on reserve"); + } else if ( data.error ) { + content += data.error; + } else { + content += _("reason unknown"); + } + } + + $(id).replaceWith( content ); + }, "json") + }); }); - $("#relCheckNoreturns").on("click",function(){ - allcheckboxes.unCheckCheckboxes(":input[name*=barcodes]"); return false; + + $("#RenewAll").on("click",function(){ + $("#CheckAllRenewals").click(); + $("#UncheckAllCheckins").click(); + $("#RenewCheckinChecked").click(); }); + + $('#patronlists').tabs(); + $("#messages ul").after(""+MSG_ADD_MESSAGE+""); + $("#borrower_messages .cancel").on("click",function(){ $("#add_message_form").hide(); $("#addmessage").show(); }); + $("#addmessage").on("click",function(){ $(this).hide(); $("#add_message_form").show(); @@ -77,7 +148,7 @@ $(document).ready(function() { return false; }); // Clicking the table cell checks the checkbox inside it - $("td").on("click",function(e){ + $(document).on("click", 'td', function(e){ if(e.target.tagName.toLowerCase() == 'td'){ $(this).find("input:checkbox:visible").each( function() { $(this).click(); 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 5a6a82d..4c0e9c4 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/circulation.tt @@ -13,15 +13,21 @@ [% INCLUDE 'doc-head-close.inc' %] [% INCLUDE 'calendar.inc' %] -[% IF ( UseTablesortForCirc ) %] + [% INCLUDE 'datatables-strings.inc' %] -[% END %] +