Improved guided reports (#3929)

added runtime parameters for guided reports. Online help:

Can I have run-time defined parameters?

Yes, you can: there is a specific syntax that Koha will understand as 'ask for values when running the report'. The syntax is <<Question to ask|authorised_value>>.

The << and >> are just delimiters. You must put << at the beginning and >> at the end of your parameter
The Question to ask will be displayed on the left of the string to enter.
The authorised_value can be omitted if not applicable. If it contains an authorised value category, or <strong>branches</strong> or <strong>itemtype</strong> or <strong>categorycode</strong>, a list with the Koha authorised values will be displayed instead of a free field

Note that you can have more than one parameter in a given SQL
Note that entering nothing at run time won't probably work as you expect. It will be considered as "value empty" not as "ignore this parameter". For example entering nothing for : "title=&lt;&lt;Enter title&gt;&gt;" will display results with title='' (no title). If you want to have to have something not mandatory, use "title like &lt;&lt;Enter title&gt;&gt;" and enter a % at run time instead of nothing
Sample :
SELECT surname,firstname FROM borrowers WHERE branchcode=&lt;&lt;Enter patrons library|branches&gt;&gt; AND surname like &lt;&lt;Enter filter for patron surname (% if none)&gt;&gt;
This commit is contained in:
Paul Poulain 2009-12-15 19:55:58 +01:00 committed by Henri-Damien LAURENT
parent 94cca6a22b
commit 7d8b369ba4
4 changed files with 181 additions and 37 deletions

View file

@ -75,17 +75,27 @@ $keys{'5'} = ['borrowers.borrowernumber=accountlines.borrowernumber'];
# have to do someting here to know if its dropdown, free text, date etc
our %criteria;
# reports on circulation
$criteria{'1'} = [
'statistics.type', 'borrowers.categorycode',
'statistics.branch',
'biblioitems.publicationyear|date',
'items.dateaccessioned|date'
];
# reports on catalogue
$criteria{'2'} =
[ 'items.itemnumber|textrange', 'items.biblionumber|textrange', 'items.barcode|textrange', 'biblio.frameworkcode', 'items.holdingbranch', 'items.homebranch', 'biblio.datecreated|daterange', 'biblio.timestamp|daterange', 'items.onloan|daterange', 'items.ccode', 'items.itemcallnumber|textrange', 'items.itemlost', 'items.location' ];
$criteria{'3'} = ['borrowers.branchcode'];
[ 'items.itemnumber|textrange', 'items.biblionumber|textrange', 'items.barcode|textrange',
'biblio.frameworkcode', 'items.holdingbranch', 'items.homebranch',
'biblio.datecreated|daterange', 'biblio.timestamp|daterange', 'items.onloan|daterange',
'items.ccode', 'items.itemcallnumber|textrange', 'items.itype',
'items.itemlost', 'items.location' ];
# reports on borrowers
$criteria{'3'} = ['borrowers.branchcode', 'borrowers.categorycode'];
# reports on acquisition
$criteria{'4'} = ['aqorders.datereceived|date'];
$criteria{'5'} = ['borrowers.branchcode'];
# reports on accounting
$criteria{'5'} = ['borrowers.branchcode', 'borrowers.categorycode'];
# Adds itemtypes to criteria, according to the syspref
if (C4::Context->preference('item-level_itypes')) {
@ -245,7 +255,6 @@ sub _build_query {
my $dbh = C4::Context->dbh();
my $joinedtables = join( ',', @$tables );
my $joinedcolumns = join( ',', @$columns );
my $joinedkeys = join( ' AND ', @$keys );
my $query =
"SELECT $totals $joinedcolumns FROM $tables->[0] ";
for (my $i=1;$i<@$tables;$i++){
@ -342,6 +351,14 @@ sub get_criteria {
my $sth = $dbh->prepare($query);
$sth->execute();
my @values;
# push the runtime choosing option
my $list;
$list='branches' if $column eq 'branchcode' or $column eq 'holdingbranch' or $column eq 'homebranch';
$list='categorycode' if $column eq 'categorycode';
$list='itemtype' if $column eq 'itype';
$list='ccode' if $column eq 'ccode';
# TODO : improve to let the librarian choose the description at runtime
push @values, { availablevalues => "<<$column".($list?"|$list":'').">>" };
while ( my $row = $sth->fetchrow_hashref() ) {
push @values, $row;
if ($row->{'availablevalues'} eq '') { $row->{'default'} = 1 };

View file

@ -52,5 +52,17 @@
<p>NEKLS also has a great page (<a href="http://www.nexpresslibrary.org/training/reports-training/" target="_new">http://www.nexpresslibrary.org/training/reports-training/</a>) with a list of reports they have written.</p>
<p><strong>Can I have run-time defined parameters?</strong></p>
<!-- TMPL_INCLUDE NAME="help-bottom.inc" -->
<p>Yes, you can: there is a specific syntax that Koha will understand as 'ask for values when running the report'. The syntax is &lt;&lt;Question to ask|authorised_value&gt;&gt;.</p>
<ul>
<li>The &lt;&lt; and &gt;&gt; are just delimiters. You must put &lt;&lt; at the beginning and &gt;&gt; at the end of your parameter</li>
<li>The <strong>Question to ask</strong> will be displayed on the left of the string to enter.
<li>The <strong>authorised_value</strong> can be omitted if not applicable. If it contains an authorised value category, or <strong>branches</strong> or <strong>itemtype</strong> or <strong>categorycode</strong>, a list with the Koha authorised values will be displayed instead of a free field</li>
</ul>
<p>Note that you can have more than one parameter in a given SQL</p>
<p>Note that entering nothing at run time won't probably work as you expect. It will be considered as "value empty" not as "ignore this parameter". For example entering nothing for : "title=&lt;&lt;Enter title&gt;&gt;" will display results with title='' (no title). If you want to have to have something not mandatory, use "title like &lt;&lt;Enter title&gt;&gt;" and enter a % at run time instead of nothing</p>
<p>Sample :</p>
<ul><li>SELECT surname,firstname FROM borrowers WHERE branchcode=&lt;&lt;Enter patrons library|branches&gt;&gt; AND surname like &lt;&lt;Enter filter for patron surname (% if none)&gt;&gt;</li></ul>
<!-- TMPL_INCLUDE NAME="help-bottom.inc" -->

View file

@ -294,7 +294,7 @@ canned reports and writing custom SQL reports.</p>
<td>
<select name="<!-- TMPL_VAR NAME="name" -->_value">
<!-- TMPL_LOOP NAME="values" -->
<option value="<!-- TMPL_VAR NAME="availablevalues"-->"><!-- TMPL_IF name="default" -->Default<!-- TMPL_ELSE --><!-- TMPL_VAR NAME="availablevalues" --><!-- /TMPL_IF --></option>
<option value="<!-- TMPL_VAR NAME="availablevalues"-->"><!-- TMPL_IF name="default" -->Default<!-- TMPL_ELSE --><!-- TMPL_VAR NAME="availablevalues" ESCAPE="HTML"--><!-- /TMPL_IF --></option>
<!-- /TMPL_LOOP -->
</select>
</td>
@ -390,11 +390,11 @@ NAME="name" -->"><!-- TMPL_VAR NAME="name"--></label></td><td>
<h1>Confirm Custom Report</h1>
<p>Your report will be generated with the following SQL statement.</p>
<p>
<!-- TMPL_VAR NAME="sql" -->
<!-- TMPL_VAR NAME="sql" ESCAPE="HTML" -->
</p>
<form action="/cgi-bin/koha/reports/guided_reports.pl" method="post">
<input type="hidden" name="sql" value="<!-- TMPL_VAR NAME="sql" ESCAPE="html" -->" />
<input type="hidden" name="sql" value="<!-- TMPL_VAR NAME="sql" -->" />
<input type="hidden" name="type" value="<!-- TMPL_VAR NAME="type" -->" />
<p>You will need to save the report before you can execute it</p>
<fieldset class="action"><input type="hidden" name="phase" value="Save" />
@ -417,10 +417,24 @@ NAME="name" -->"><!-- TMPL_VAR NAME="name"--></label></td><td>
</form>
<!-- /TMPL_IF -->
<!-- TMPL_IF name="enter_params" -->
<form action='/cgi-bin/koha/reports/guided_reports.pl'>
<input type='hidden' name='phase' value='Run this report' />
<input type='hidden' name='reports' value="<!--TMPL_VAR name='reports' -->" />
<h1>Enter parameters for report <!-- TMPL_VAR name="name" -->:</h1>
<!-- TMPL_IF NAME="notes" --><p><!-- TMPL_VAR NAME="notes" --></p><!-- /TMPL_IF -->
<!-- TMPL_LOOP name="sql_params" -->
<p><!-- TMPL_VAR name="entry" -->: <!-- TMPL_VAR name='input' --></p>
<!-- /TMPL_LOOP -->
<input type='submit' value='Run the report'/>
</form>
<!-- /TMPL_IF -->
<!-- TMPL_IF NAME="execute" -->
<h1><!-- TMPL_VAR NAME="name" --></h1>
<!-- TMPL_IF NAME="notes" --><p><!-- TMPL_VAR NAME="notes" --></p><!-- /TMPL_IF -->
<!-- TMPL_IF NAME="unlimited_total" --><p>Total number of rows matching the (unlimited) query is <!-- TMPL_VAR NAME="unlimited_total" -->.</p><!-- /TMPL_IF -->
<!-- TMPL_VAR name="sql" -->
<!-- TMPL_VAR NAME='pagination_bar' DEFAULT="" -->
<!-- TMPL_UNLESS name="errors" -->
<table>

View file

@ -26,6 +26,7 @@ use C4::Auth;
use C4::Output;
use C4::Dates;
use C4::Debug;
use C4::Branch; # XXX subfield_is_koha_internal_p
=head1 NAME
@ -226,7 +227,12 @@ elsif ( $phase eq 'Choose these criteria' ) {
if ($value =~ C4::Dates->regexp('syspref')) {
$value = C4::Dates->new($value)->output("iso");
}
$query_criteria .= " AND $crit='$value'";
# don't escape runtime parameters, they'll be at runtime
if ($value =~ /<<.*>>/) {
$query_criteria .= " AND $crit=$value";
} else {
$query_criteria .= " AND $crit='$value'";
}
}
warn $query_criteria;
}
@ -304,8 +310,7 @@ elsif ( $phase eq 'Build Report' ) {
my $column = $input->param('column');
my $crit = $input->param('criteria');
my $totals = $input->param('totals');
my $definition = $input->param('definition');
# my @criteria = split( ',', $crit );
my $definition = $input->param('definition');
my $query_criteria=$crit;
# split the columns up by ,
my @columns = split( ',', $column );
@ -377,6 +382,7 @@ elsif ($phase eq 'Run this report'){
my $limit = 20; # page size. # TODO: move to DB or syspref?
my $offset = 0;
my $report = $input->param('reports');
my @sql_params = $input->param('sql_params');
# offset algorithm
if ($input->param('page')) {
$offset = ($input->param('page') - 1) * $limit;
@ -386,33 +392,128 @@ elsif ($phase eq 'Run this report'){
push @errors, {no_sql_for_id=>$report};
}
my @rows = ();
my ($sth, $errors) = execute_query($sql, $offset, $limit);
my $total = select_2_select_count_value($sql) || 0;
unless ($sth) {
die "execute_query failed to return sth for report $report: $sql";
} else {
my $headref = $sth->{NAME} || [];
my @headers = map { +{ cell => $_ } } @$headref;
$template->param(header_row => \@headers);
while (my $row = $sth->fetchrow_arrayref()) {
my @cells = map { +{ cell => $_ } } @$row;
push @rows, { cells => \@cells };
}
}
# if we have at least 1 parameter, and it's not filled, then don't execute but ask for parameters
if ($sql =~ /<</ && !@sql_params) {
# split on ??. Each odd (2,4,6,...) entry should be a parameter to fill
my @split = split /<<|>>/,$sql;
my @tmpl_parameters;
for(my $i=0;$i<($#split/2);$i++) {
my ($text,$authorised_value) = split /\|/,$split[$i*2+1];
my $input;
if ($authorised_value) {
my $dbh=C4::Context->dbh;
my @authorised_values;
my %authorised_lib;
# builds list, depending on authorised value...
if ( $authorised_value eq "branches" ) {
my $branches = GetBranchesLoop();
foreach my $thisbranch (@$branches) {
push @authorised_values, $thisbranch->{value};
$authorised_lib{$thisbranch->{value}} = $thisbranch->{branchname};
}
}
elsif ( $authorised_value eq "itemtypes" ) {
my $sth = $dbh->prepare("SELECT itemtype,description FROM itemtypes ORDER BY description");
$sth->execute;
while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
push @authorised_values, $itemtype;
$authorised_lib{$itemtype} = $description;
}
}
elsif ( $authorised_value eq "cn_source" ) {
my $class_sources = GetClassSources();
my $default_source = C4::Context->preference("DefaultClassificationSource");
foreach my $class_source (sort keys %$class_sources) {
next unless $class_sources->{$class_source}->{'used'} or
($class_source eq $default_source);
push @authorised_values, $class_source;
$authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
}
}
elsif ( $authorised_value eq "categorycode" ) {
my $sth = $dbh->prepare("SELECT categorycode, description FROM categories ORDER BY description");
$sth->execute;
while ( my ( $categorycode, $description ) = $sth->fetchrow_array ) {
push @authorised_values, $categorycode;
$authorised_lib{$categorycode} = $description;
}
my $totpages = int($total/$limit) + (($total % $limit) > 0 ? 1 : 0);
my $url = "/cgi-bin/koha/reports/guided_reports.pl?reports=$report&phase=Run%20this%20report";
$template->param(
'results' => \@rows,
'sql' => $sql,
'execute' => 1,
'name' => $name,
'notes' => $notes,
'errors' => $errors,
'pagination_bar' => pagination_bar($url, $totpages, $input->param('page')),
'unlimited_total' => $total,
);
}
#---- "true" authorised value
}
else {
my $authorised_values_sth = $dbh->prepare("SELECT authorised_value,lib FROM authorised_values WHERE category=? ORDER BY lib");
$authorised_values_sth->execute( $authorised_value);
while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
push @authorised_values, $value;
$authorised_lib{$value} = $lib;
# For item location, we show the code and the libelle
$authorised_lib{$value} = $lib;
}
}
$input =CGI::scrolling_list( # FIXME: factor out scrolling_list
-name => "sql_params",
-values => \@authorised_values,
# -default => $value,
-labels => \%authorised_lib,
-override => 1,
-size => 1,
-multiple => 0,
-tabindex => 1,
);
} else {
$input = "<input type='text' name='sql_params'/>";
}
push @tmpl_parameters, {'entry' => $text, 'input' => $input };
}
$template->param('sql' => $sql,
'name' => $name,
'sql_params' => \@tmpl_parameters,
'enter_params' => 1,
'reports' => $report,
);
} else {
# OK, we have parameters, or there are none, we run the report
# if there were parameters, replace before running
# split on ??. Each odd (2,4,6,...) entry should be a parameter to fill
my @split = split /<<|>>/,$sql;
my @tmpl_parameters;
for(my $i=0;$i<$#split/2;$i++) {
my $quoted = C4::Context->dbh->quote($sql_params[$i]);
# if there are special regexp chars, we must \ them
$split[$i*2+1] =~ s/(\||\?|\.|\*|\(|\)|\%)/\\$1/g;
$sql =~ s/<<$split[$i*2+1]>>/$quoted/;
}
my ($sth, $errors) = execute_query($sql, $offset, $limit);
my $total = select_2_select_count_value($sql) || 0;
unless ($sth) {
die "execute_query failed to return sth for report $report: $sql";
} else {
my $headref = $sth->{NAME} || [];
my @headers = map { +{ cell => $_ } } @$headref;
$template->param(header_row => \@headers);
while (my $row = $sth->fetchrow_arrayref()) {
my @cells = map { +{ cell => $_ } } @$row;
push @rows, { cells => \@cells };
}
}
my $totpages = int($total/$limit) + (($total % $limit) > 0 ? 1 : 0);
my $url = "/cgi-bin/koha/reports/guided_reports.pl?reports=$report&phase=Run%20this%20report";
$template->param(
'results' => \@rows,
'sql' => $sql,
'execute' => 1,
'name' => $name,
'notes' => $notes,
'errors' => $errors,
'pagination_bar' => pagination_bar($url, $totpages, $input->param('page')),
'unlimited_total' => $total,
);
}
}
elsif ($phase eq 'Export'){
binmode STDOUT, ':utf8';