Bug 7955: Statistics tab for Patron checkouts
This patch adds a new 'statistics' tab in the Patron module. For a borrower, this tab contains a table with: - number of checkout for today - number of checkin for today - "precedent state", the number of checkouts the patron had yesterday on its library card - "actual state", the number of checkouts on the borrower card at the current date A new syspref (StatisticsFields) contains a list of fields (separated by pipe (|) on which the table results is based. The default value is location|itype|ccode Signed-off-by: Mathilde Formery <mathilde.formery@ville-nimes.fr>
This commit is contained in:
parent
1a2e242704
commit
5e2001a6f8
|
|
@ -0,0 +1,116 @@
|
|||
package C4::Members::Statistics;
|
||||
|
||||
# Copyright 2012 BibLibre
|
||||
# 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.
|
||||
|
||||
=head1 NAME
|
||||
|
||||
C4::Members::Statistics - Get statistics for patron checkouts
|
||||
|
||||
=cut
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use C4::Context;
|
||||
|
||||
our ( @ISA, @EXPORT, @EXPORT_OK, $debug );
|
||||
|
||||
BEGIN {
|
||||
$debug = $ENV{DEBUG} || 0;
|
||||
require Exporter;
|
||||
@ISA = qw(Exporter);
|
||||
|
||||
push @EXPORT, qw(
|
||||
&GetTotalIssuesTodayByBorrower
|
||||
&GetTotalIssuesReturnedTodayByBorrower
|
||||
&GetPrecedentStateByBorrower
|
||||
);
|
||||
}
|
||||
|
||||
=head2 construct_query
|
||||
Build a sql query from a subquery
|
||||
Adds statistics fields to the select and the group by clause
|
||||
=cut
|
||||
sub construct_query {
|
||||
my $count = shift;
|
||||
my $subquery = shift;
|
||||
my $fields = C4::Context->preference('StatisticsFields') || 'location|itype|ccode';
|
||||
my @select_fields = split '\|', $fields;
|
||||
my $query = "SELECT COUNT(*) as count_$count";
|
||||
$query .= ", " . C4::Context->dbh->quote( $_ ) for @select_fields;
|
||||
|
||||
$query .= " " . $subquery;
|
||||
|
||||
$fields =~ s/\|/,/g;
|
||||
$query .= " GROUP BY $fields;";
|
||||
|
||||
return $query;
|
||||
|
||||
}
|
||||
|
||||
=head2 GetTotalIssuesTodayByBorrower
|
||||
Return total issues for a borrower at this current day
|
||||
=cut
|
||||
sub GetTotalIssuesTodayByBorrower {
|
||||
my ($borrowernumber) = @_;
|
||||
my $dbh = C4::Context->dbh;
|
||||
|
||||
my $query = construct_query "total_issues_today",
|
||||
"FROM (
|
||||
SELECT it.* FROM issues i, items it WHERE i.itemnumber = it.itemnumber AND i.borrowernumber = ? AND DATE(i.issuedate) = CAST(now() AS date)
|
||||
UNION
|
||||
SELECT it.* FROM old_issues oi, items it WHERE oi.itemnumber = it.itemnumber AND oi.borrowernumber = ? AND DATE(oi.issuedate) = CAST(now() AS date)
|
||||
) tmp"; # alias is required by MySQL
|
||||
|
||||
my $sth = $dbh->prepare($query);
|
||||
$sth->execute($borrowernumber, $borrowernumber);
|
||||
return $sth->fetchall_arrayref( {} );
|
||||
}
|
||||
|
||||
=head2 GetTotalIssuesReturnedTodayByBorrower
|
||||
Return total issues returned by a borrower at this current day
|
||||
=cut
|
||||
sub GetTotalIssuesReturnedTodayByBorrower {
|
||||
my ($borrowernumber) = @_;
|
||||
my $dbh = C4::Context->dbh;
|
||||
|
||||
my $query = construct_query "total_issues_returned_today", "FROM old_issues i, items it WHERE i.itemnumber = it.itemnumber AND i.borrowernumber = ? AND DATE(i.returndate) = CAST(now() AS date) ";
|
||||
|
||||
my $sth = $dbh->prepare($query);
|
||||
$sth->execute($borrowernumber);
|
||||
return $sth->fetchall_arrayref( {} );
|
||||
}
|
||||
|
||||
=head2 GetPrecedentStateByBorrower
|
||||
Return the precedent state (before today) for a borrower of his checkins and checkouts
|
||||
=cut
|
||||
sub GetPrecedentStateByBorrower {
|
||||
my ($borrowernumber) = @_;
|
||||
my $dbh = C4::Context->dbh;
|
||||
|
||||
my $query = construct_query "precedent_state",
|
||||
"FROM (
|
||||
SELECT it.* FROM issues i, items it WHERE i.borrowernumber = ? AND i.itemnumber = it.itemnumber AND DATE(i.issuedate) < CAST(now() AS date)
|
||||
UNION
|
||||
SELECT it.* FROM old_issues oi, items it WHERE oi.borrowernumber = ? AND oi.itemnumber = it.itemnumber AND DATE(oi.issuedate) < CAST(now() AS date) AND DATE(oi.returndate) = CAST(now() AS date)
|
||||
) tmp"; # alias is required by MySQL
|
||||
|
||||
my $sth = $dbh->prepare($query);
|
||||
$sth->execute($borrowernumber, $borrowernumber);
|
||||
return $sth->fetchall_arrayref( {});
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -5369,6 +5369,14 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
|
|||
SetVersion($DBversion);
|
||||
}
|
||||
|
||||
$DBversion = "3.09.00.015";
|
||||
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
|
||||
$dbh->do(qq{
|
||||
INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('StatisticsFields','location|itype|ccode','Define Fields (from the items table) used for statistics members','location|itype|ccode','free')
|
||||
});
|
||||
print "Upgrade to $DBversion done (Add System preference StatisticsFields)\n";
|
||||
SetVersion($DBversion);
|
||||
}
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@
|
|||
[% IF ( EnhancedMessagingPreferences ) %]
|
||||
[% END %]
|
||||
[% IF ( sentnotices ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/notices.pl?borrowernumber=[% borrowernumber %]">Notices</a></li>
|
||||
[% IF ( statisticsview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/statistics.pl?borrowernumber=[% borrowernumber %]">Statistics</a></li>
|
||||
</ul></div>
|
||||
[% END %]
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ in the global namespace %]
|
|||
[% IF ( EnhancedMessagingPreferences ) %]
|
||||
[% END %]
|
||||
[% IF ( sentnotices ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/notices.pl?borrowernumber=[% borrower.borrowernumber %]">Notices</a></li>
|
||||
[% IF ( statisticsview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/statistics.pl?borrowernumber=[% borrowernumber %]">Statistics</a></li>
|
||||
</ul></div>
|
||||
[% END %]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@
|
|||
[% IF ( EnhancedMessagingPreferences ) %]
|
||||
[% END %]
|
||||
[% IF ( sentnotices ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/notices.pl?borrowernumber=[% borrowernumber %]">Notices</a></li>
|
||||
[% IF ( statisticsview ) %]<li class="active">[% ELSE %]<li>[% END %]<a href="/cgi-bin/koha/members/statistics.pl?borrowernumber=[% borrowernumber %]">Statistics</a></li>
|
||||
</ul></div>
|
||||
[% END %]
|
||||
|
|
|
|||
|
|
@ -127,3 +127,7 @@ Patrons:
|
|||
yes: Enable
|
||||
no: Disable
|
||||
- patron phone notifications using Talking Tech i-tiva (overdues, predues and holds notices currently supported).
|
||||
-
|
||||
- pref: StatisticsFields
|
||||
class: multi
|
||||
- Define Fields (from the items table) used for statistics members (separate fields with |, for example:"location|itype|ccode").
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
<title>Koha › Patrons ›
|
||||
[% IF ( unknowuser ) %]
|
||||
Patron does not exist
|
||||
[% ELSE %]
|
||||
Patron details for [% INCLUDE 'patron-title.inc' %]
|
||||
[% END %]
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
|
||||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.min.js"></script>
|
||||
[% INCLUDE 'datatables-strings.inc' %]
|
||||
<script type="text/javascript" src="[% themelang %]/js/datatables.js"></script>
|
||||
[% INCLUDE 'calendar.inc' %]
|
||||
<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.tablesorter.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("#statistics").dataTable($.extend(true, {}, dataTablesDefaults, {
|
||||
'bPaginate': false,
|
||||
'bFilter': false,
|
||||
'bInfo': false,
|
||||
} ));
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
[% INCLUDE 'header.inc' %]
|
||||
[% INCLUDE 'patron-search.inc' %]
|
||||
|
||||
<div id="breadcrumbs">
|
||||
<a href="/cgi-bin/koha/mainpage.pl">Home</a>
|
||||
› <a href="/cgi-bin/koha/members/members-home.pl">Patrons</a>
|
||||
› [% IF ( unknowuser ) %]Patron does not exist[% ELSE %]Patron statistics for [% firstname %] [% surname %] ([% cardnumber %])[% END %]
|
||||
</div>
|
||||
|
||||
<div id="doc3" class="yui-t1">
|
||||
|
||||
<div id="bd">
|
||||
<div id="yui-main">
|
||||
<div class="yui-b">
|
||||
<div class="yui-g">
|
||||
<h2>Statistics</h2>
|
||||
<table id="statistics">
|
||||
<thead>
|
||||
<tr>
|
||||
[% FOREACH cn IN column_names %]
|
||||
<th>[% cn %]</th>
|
||||
[% END %]
|
||||
<th>Precedent State</th>
|
||||
<th>Issues</th>
|
||||
<th>Issues returned</th>
|
||||
<th>Actual State</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
[% FOREACH r IN datas %]
|
||||
<tr>
|
||||
[% FOREACH c IN r %]
|
||||
<td>[% c %]</td>
|
||||
[% END %]
|
||||
</tr>
|
||||
[% END %]
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="[% length_keys %]">TOTAL</td>
|
||||
<td>[% count_total_precedent_state %]</td>
|
||||
<td>[% count_total_issues %]</td>
|
||||
<td>[% count_total_issues_returned %]</td>
|
||||
<td>[% count_total_actual_state %]</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yui-b">
|
||||
[% INCLUDE 'circ-menu.inc' %]
|
||||
</div>
|
||||
</div>
|
||||
[% INCLUDE 'intranet-bottom.inc' %]
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# Copyright 2012 BibLibre
|
||||
# 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., 59 Temple Place,
|
||||
# Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
=head1 members/statistics.pl
|
||||
Generate statistic issues for a member
|
||||
=cut
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use CGI;
|
||||
use C4::Auth;
|
||||
use C4::Branch;
|
||||
use C4::Context;
|
||||
use C4::Members;
|
||||
use C4::Members::Statistics;
|
||||
use C4::Output;
|
||||
|
||||
my $input = new CGI;
|
||||
|
||||
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
|
||||
{ template_name => "members/statistics.tmpl",
|
||||
query => $input,
|
||||
type => "intranet",
|
||||
authnotrequired => 0,
|
||||
flagsrequired => { borrowers => 1 },
|
||||
debug => 1,
|
||||
}
|
||||
);
|
||||
|
||||
my $borrowernumber = $input->param('borrowernumber');
|
||||
|
||||
# Set informations for the patron
|
||||
my $borrower = GetMemberDetails( $borrowernumber, 0 );
|
||||
if ( not defined $borrower ) {
|
||||
$template->param (unknowuser => 1);
|
||||
output_html_with_http_headers $input, $cookie, $template->output;
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach my $key ( keys %$borrower ) {
|
||||
$template->param( $key => $borrower->{$key} );
|
||||
}
|
||||
|
||||
# Construct column names
|
||||
my $fields = C4::Context->preference('StatisticsFields') || 'location|itype|ccode';
|
||||
our @statistic_column_names = split '\|', $fields;
|
||||
our @value_column_names = ( 'count_precedent_state', 'count_total_issues_today', 'count_total_issues_returned_today' );
|
||||
our @column_names = ( @statistic_column_names, @value_column_names );
|
||||
|
||||
# Get statistics
|
||||
my $precedent_state = GetPrecedentStateByBorrower( $borrowernumber );
|
||||
my $total_issues_today = GetTotalIssuesTodayByBorrower( $borrowernumber );
|
||||
my $total_issues_returned_today = GetTotalIssuesReturnedTodayByBorrower( $borrowernumber );
|
||||
my $r = merge (
|
||||
@$precedent_state, @$total_issues_today, @$total_issues_returned_today
|
||||
);
|
||||
add_actual_state( $r );
|
||||
my ( $total, $datas ) = build_array( $r );
|
||||
|
||||
# Gettings sums
|
||||
my $count_total_precedent_state = $total->{count_precedent_state} || 0;
|
||||
my $count_total_issues = $total->{count_total_issues_today} || 0;
|
||||
my $count_total_issues_returned = $total->{count_total_issues_returned_today} || 0;
|
||||
my $count_total_actual_state = ($count_total_precedent_state - $count_total_issues_returned + $count_total_issues);
|
||||
|
||||
$template->param(
|
||||
statisticsview => 1,
|
||||
datas => $datas,
|
||||
column_names => \@statistic_column_names,
|
||||
length_keys => scalar( @statistic_column_names),
|
||||
count_total_issues => $count_total_issues,
|
||||
count_total_issues_returned => $count_total_issues_returned,
|
||||
count_total_precedent_state => $count_total_precedent_state,
|
||||
count_total_actual_state => $count_total_actual_state,
|
||||
);
|
||||
|
||||
output_html_with_http_headers $input, $cookie, $template->output;
|
||||
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=head2 add_actual_state
|
||||
Add a 'count_actual_state' key in all hashes
|
||||
count_actual_state = count_precedent_state - count_total_issues_returned_today + count_total_issues_today
|
||||
=cut
|
||||
sub add_actual_state {
|
||||
my ( $array ) = @_;
|
||||
for my $hash ( @$array ) {
|
||||
$hash->{count_actual_state} = ( $hash->{count_precedent_state} // 0 ) - ( $hash->{count_total_issues_returned_today} // 0 ) + ( $hash->{count_total_issues_today} // 0 );
|
||||
}
|
||||
}
|
||||
|
||||
=head2 build_array
|
||||
Build a new array containing values of hashes.
|
||||
It used by template whitch display silly values.
|
||||
ex:
|
||||
$array = [
|
||||
{
|
||||
'count_total_issues_returned_today' => 1,
|
||||
'ccode' => 'ccode',
|
||||
'count_actual_state' => 1,
|
||||
'count_precedent_state' => 1,
|
||||
'homebranch' => 'homebranch',
|
||||
'count_total_issues_today' => 1,
|
||||
'itype' => 'itype'
|
||||
}
|
||||
];
|
||||
and returns:
|
||||
[
|
||||
[
|
||||
'homebranch',
|
||||
'itype',
|
||||
'ccode',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
]
|
||||
];
|
||||
|
||||
=cut
|
||||
sub build_array {
|
||||
my ( $array ) = @_;
|
||||
my ( @r, $total );
|
||||
for my $hash ( @$array) {
|
||||
my @line;
|
||||
for my $cn ( ( @column_names, 'count_actual_state') ) {
|
||||
if ( grep /$cn/, ( @value_column_names, 'count_actual_state') ) {
|
||||
$hash->{$cn} //= 0;
|
||||
if ( exists $total->{$cn} ) {
|
||||
$total->{$cn} += $hash->{$cn} if $hash->{$cn};
|
||||
} else {
|
||||
$total->{$cn} = $hash->{$cn};
|
||||
}
|
||||
}
|
||||
push @line, $hash->{$cn};
|
||||
}
|
||||
push @r, \@line;
|
||||
}
|
||||
return ( $total, \@r );
|
||||
}
|
||||
|
||||
=head2 merge
|
||||
Merge hashes with the same statistic column names into one
|
||||
param: array, a arrayref of arrayrefs
|
||||
ex:
|
||||
@array = (
|
||||
{
|
||||
'ccode' => 'ccode',
|
||||
'count_precedent_state' => '1',
|
||||
'homebranch' => 'homebranch',
|
||||
'itype' => 'itype'
|
||||
},
|
||||
{
|
||||
'count_total_issues_returned_today' => '1',
|
||||
'ccode' => 'ccode',
|
||||
'homebranch' => 'homebranch',
|
||||
'itype' => 'itype'
|
||||
}
|
||||
);
|
||||
and returns:
|
||||
[
|
||||
{
|
||||
'count_total_issues_returned_today' => '1',
|
||||
'ccode' => 'ccode',
|
||||
'count_precedent_state' => '1',
|
||||
'homebranch' => 'homebranch',
|
||||
'itype' => 'itype'
|
||||
}
|
||||
];
|
||||
|
||||
=cut
|
||||
sub merge {
|
||||
my @array = @_;
|
||||
my @r;
|
||||
for my $h ( @array ) {
|
||||
my $exists = 0;
|
||||
for my $ch ( @r ) {
|
||||
$exists = 1;
|
||||
for my $cn ( @statistic_column_names ) {
|
||||
if ( not $ch->{$cn} eq $h->{$cn} ) {
|
||||
$exists = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($exists){
|
||||
for my $cn ( @value_column_names ) {
|
||||
next if not exists $h->{$cn};
|
||||
$ch->{$cn} = $h->{$cn} ? $h->{$cn} : 0;
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if ( not $exists ) {push @r, $h;}
|
||||
}
|
||||
return \@r;
|
||||
}
|
||||
Loading…
Reference in New Issue