Storing xml now
[koha.git] / C4 / Reports.pm
1 package C4::Reports;
2
3 # Copyright 2007 Liblime Ltd
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA  02111-1307 USA
19
20 use strict;
21 require Exporter;
22
23 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
24 use C4::Context;
25 use C4::Output;
26 use XML::Simple;
27 # use Smart::Comments;
28 # use Data::Dumper;
29
30 # set the version for version checking
31 $VERSION = 0.01;
32
33 @ISA = qw(Exporter);
34 @EXPORT =
35   qw(get_report_types get_report_areas get_columns build_query get_criteria
36   save_report get_saved_reports execute_query get_saved_report create_compound run_compound
37   get_column_type get_distinct_values save_dictionary get_from_dictionary
38   delete_definition delete_report);
39
40 our %table_areas;
41 $table_areas{'1'} =
42   [ 'borrowers', 'statistics','items', 'biblioitems' ];    # circulation
43 $table_areas{'2'} = [ 'items', 'biblioitems', 'biblio' ];   # catalogue
44 $table_areas{'3'} = [ 'borrowers', 'accountlines' ];        # patrons
45 $table_areas{'4'} = ['aqorders', 'biblio', 'items'];        # acquisitions
46
47 our %keys;
48 $keys{'1'} = [
49     'statistics.borrowernumber=borrowers.borrowernumber',
50     'items.itemnumber = statistics.itemnumber',
51     'biblioitems.biblioitemnumber = items.biblioitemnumber'
52 ];
53 $keys{'2'} = [
54     'items.biblioitemnumber=biblioitems.biblioitemnumber',
55     'biblioitems.biblionumber=biblio.biblionumber'
56 ];
57 $keys{'3'} = ['borrowers.borrowernumber=accountlines.borrowernumber'];
58 $keys{'4'} = [
59         'aqorders.biblionumber=biblio.biblionumber',
60         'biblio.biblionumber=items.biblionumber'
61 ];
62
63 # have to do someting here to know if its dropdown, free text, date etc
64
65 our %criteria;
66 $criteria{'1'} = [
67     'statistics.type',   'borrowers.categorycode',
68     'statistics.branch', 'biblioitems.itemtype',
69     'biblioitems.publicationyear|date',
70     'items.dateaccessioned|date'
71 ];
72 $criteria{'2'} =
73   [ 'biblioitems.itemtype', 'items.holdingbranch', 'items.homebranch' ,'items.itemlost'];
74 $criteria{'3'} = ['borrowers.branchcode'];
75 $criteria{'4'} = ['aqorders.datereceived|date'];
76
77
78 our %columns;
79 my $columns_def_file = "columns.def";
80 my $htdocs = C4::Context->config('intrahtdocs');                       
81 my $section='intranet';
82 my ($theme, $lang) = themelanguage($htdocs, $columns_def_file, $section);                                                                                 
83
84 my $columns_def_file="$htdocs/$theme/$lang/$columns_def_file";    
85 open (COLUMNS,$columns_def_file);
86 while (my $input = <COLUMNS>){
87         my @row =split(/\t/,$input);
88         $columns{$row[0]}=$row[1];
89 }
90
91 close COLUMNS;
92
93 =head1 NAME
94    
95 C4::Reports - Module for generating reports 
96
97 =head1 SYNOPSIS
98
99   use C4::Reports;
100
101 =head1 DESCRIPTION
102
103
104 =head1 METHODS
105
106 =over 2
107
108 =cut
109
110 =item get_report_types()
111
112 This will return a list of all the available report types
113
114 =cut
115
116 sub get_report_types {
117     my $dbh = C4::Context->dbh();
118
119     # FIXME these should be in the database perhaps
120     my @reports = ( 'Tabular', 'Summary', 'Matrix' );
121     my @reports2;
122     for ( my $i = 0 ; $i < 3 ; $i++ ) {
123         my %hashrep;
124         $hashrep{id}   = $i + 1;
125         $hashrep{name} = $reports[$i];
126         push @reports2, \%hashrep;
127     }
128     return ( \@reports2 );
129
130 }
131
132 =item get_report_areas()
133
134 This will return a list of all the available report areas
135
136 =cut
137
138 sub get_report_areas {
139     my $dbh = C4::Context->dbh();
140
141     # FIXME these should be in the database
142     my @reports = ( 'Circulation', 'Catalog', 'Patrons', 'Acquisitions' );
143     my @reports2;
144     for ( my $i = 0 ; $i < 4 ; $i++ ) {
145         my %hashrep;
146         $hashrep{id}   = $i + 1;
147         $hashrep{name} = $reports[$i];
148         push @reports2, \%hashrep;
149     }
150     return ( \@reports2 );
151
152 }
153
154 =item get_all_tables()
155
156 This will return a list of all tables in the database 
157
158 =cut
159
160 sub get_all_tables {
161     my $dbh   = C4::Context->dbh();
162     my $query = "SHOW TABLES";
163     my $sth   = $dbh->prepare($query);
164     $sth->execute();
165     my @tables;
166     while ( my $data = $sth->fetchrow_arrayref() ) {
167         push @tables, $data->[0];
168     }
169     $sth->finish();
170     return ( \@tables );
171
172 }
173
174 =item get_columns($area)
175
176 This will return a list of all columns for a report area
177
178 =cut
179
180 sub get_columns {
181
182     # this calls the internal fucntion _get_columns
183     my ($area) = @_;
184     my $tables = $table_areas{$area};
185     my @allcolumns;
186     foreach my $table (@$tables) {
187         my @columns = _get_columns($table);
188         push @allcolumns, @columns;
189     }
190     return ( \@allcolumns );
191 }
192
193 sub _get_columns {
194     my ($tablename) = @_;
195     my $dbh         = C4::Context->dbh();
196     my $sth         = $dbh->prepare("show columns from $tablename");
197     $sth->execute();
198     my @columns;
199         my %tablehash;
200         $tablehash{'table'}=$tablename;
201         push @columns, \%tablehash;
202     while ( my $data = $sth->fetchrow_arrayref() ) {
203         my %temphash;
204         $temphash{'name'}        = "$tablename.$data->[0]";
205         $temphash{'description'} = $columns{"$tablename.$data->[0]"};
206         push @columns, \%temphash;
207     }
208     $sth->finish();
209     return (@columns);
210 }
211
212 =item build_query($columns,$criteria,$orderby,$area)
213
214 This will build the sql needed to return the results asked for, 
215 $columns is expected to be of the format tablename.columnname.
216 This is what get_columns returns.
217
218 =cut
219
220 sub build_query {
221     my ( $columns, $criteria, $orderby, $area, $totals, $definition ) = @_;
222 ### $orderby
223     my $keys   = $keys{$area};
224     my $tables = $table_areas{$area};
225
226     my $sql =
227       _build_query( $tables, $columns, $criteria, $keys, $orderby, $totals, $definition );
228     return ($sql);
229 }
230
231 sub _build_query {
232     my ( $tables, $columns, $criteria, $keys, $orderby, $totals, $definition) = @_;
233 ### $orderby
234     # $keys is an array of joining constraints
235     my $dbh           = C4::Context->dbh();
236     my $joinedtables  = join( ',', @$tables );
237     my $joinedcolumns = join( ',', @$columns );
238     my $joinedkeys    = join( ' AND ', @$keys );
239     my $query =
240       "SELECT $totals $joinedcolumns FROM $tables->[0] ";
241         for (my $i=1;$i<@$tables;$i++){
242                 $query .= "LEFT JOIN $tables->[$i] on ($keys->[$i-1]) ";
243         }
244
245     if ($criteria) {
246                 $criteria =~ s/AND/WHERE/;
247         $query .= " $criteria";
248     }
249         if ($definition){
250                 my @definitions = split(',',$definition);
251                 my $deftext;
252                 foreach my $def (@definitions){
253                         my $defin=get_from_dictionary('',$def);
254                         $deftext .=" ".$defin->[0]->{'saved_sql'};
255                 }
256                 if ($query =~ /WHERE/i){
257                         $query .= $deftext;
258                 }
259                 else {
260                         $deftext  =~ s/AND/WHERE/;
261                         $query .= $deftext;                     
262                 }
263         }
264     if ($totals) {
265         my $groupby;
266         my @totcolumns = split( ',', $totals );
267         foreach my $total (@totcolumns) {
268             if ( $total =~ /\((.*)\)/ ) {
269                 if ( $groupby eq '' ) {
270                     $groupby = " GROUP BY $1";
271                 }
272                 else {
273                     $groupby .= ",$1";
274                 }
275             }
276         }
277         $query .= $groupby;
278     }
279     if ($orderby) {
280         $query .= $orderby;
281     }
282     return ($query);
283 }
284
285 =item get_criteria($area);
286
287 Returns an arraref to hashrefs suitable for using in a tmpl_loop. With the criteria and available values.
288
289 =cut
290
291 sub get_criteria {
292     my ($area) = @_;
293     my $dbh    = C4::Context->dbh();
294     my $crit   = $criteria{$area};
295     my @criteria_array;
296     foreach my $localcrit (@$crit) {
297         my ( $value, $type )   = split( /\|/, $localcrit );
298         my ( $table, $column ) = split( /\./, $value );
299         if ( $type eq 'date' ) {
300                         my %temp;
301             $temp{'name'}   = $value;
302             $temp{'date'}   = 1;
303                         $temp{'description'} = $columns{$value};
304             push @criteria_array, \%temp;
305         }
306         else {
307
308             my $query =
309               "SELECT distinct($column) as availablevalues FROM $table";
310             my $sth = $dbh->prepare($query);
311             $sth->execute();
312             my @values;
313             while ( my $row = $sth->fetchrow_hashref() ) {
314                 push @values, $row;
315                 ### $row;
316             }
317             $sth->finish();
318             my %temp;
319             $temp{'name'}   = $value;
320                         $temp{'description'} = $columns{$value};
321             $temp{'values'} = \@values;
322             push @criteria_array, \%temp;
323         }
324     }
325     return ( \@criteria_array );
326 }
327
328 sub execute_query {
329     my ( $sql, $type, $format, $id ) = @_;
330     my $dbh = C4::Context->dbh();
331
332     # take this line out when in production
333         if ($format eq 'url'){
334                 }
335         else {
336                 $sql .= " LIMIT 10";
337         }
338     my $sth = $dbh->prepare($sql);
339     $sth->execute();
340         my $colnames=$sth->{'NAME'};
341         my @results;
342         my $row;
343         my %temphash;
344         $row = join ('</th><th>',@$colnames);
345         $row = "<tr><th>$row</th></tr>";
346         $temphash{'row'} = $row;
347         push @results, \%temphash;
348     my $string;
349         my %xmlhash;
350         my $i=1;
351     while ( my @data = $sth->fetchrow_array() ) {
352
353             # tabular
354             my %temphash;
355             my $row = join( '</td><td>', @data );
356             $row = "<tr><td>$row</td></tr>";
357             $temphash{'row'} = $row;
358             if ( $format eq 'text' ) {
359                 $string .= "\n" . $row;
360             }
361                         if ($format eq 'tab' ){
362                                 $row = join("\t",@data);
363                                 $string .="\n" . $row;
364                         }
365                         if ($format eq 'csv' ){
366                                 $row = join(",",@data);
367                                 $string .="\n" . $row;
368                         }
369                 if ($format eq 'url'){
370                         my $temphash;
371                         @$temphash{@$colnames}=@data;
372                         $xmlhash{$i}=$temphash;
373                         $i++;
374                 }
375             push @results, \%temphash;
376 #        }
377     }
378     $sth->finish();
379     if ( $format eq 'text' || $format eq 'tab' || $format eq 'csv' ) {
380         return $string;
381     }
382         elsif ($format eq 'url') {
383                 my $url;
384                 my $xml = XMLout(\%xmlhash);
385                 store_results($id,$xml);
386                 return $url;
387         }
388     else {
389         return ( \@results );
390     }
391 }
392
393 =item save_report($sql,$name,$type,$notes)
394
395 Given some sql and a name this will saved it so that it can resued
396
397 =cut
398
399 sub save_report {
400     my ( $sql, $name, $type, $notes ) = @_;
401     my $dbh = C4::Context->dbh();
402     my $query =
403 "INSERT INTO saved_sql (borrowernumber,date_created,last_modified,savedsql,report_name,type,notes)  VALUES (?,now(),now(),?,?,?,?)";
404     my $sth = $dbh->prepare($query);
405     $sth->execute( 0, $sql, $name, $type, $notes );
406     $sth->finish();
407
408 }
409
410 sub store_results {
411         my ($id,$xml)=@_;
412         my $dbh = C4::Context->dbh();
413         my $query = "INSERT INTO saved_reports (report_id,report,date_run) VALUES (?,?,now())";
414         my $sth = $dbh->prepare($query);
415         $sth->execute($id,$xml);
416         $sth->finish();
417 }
418
419 sub delete_report {
420         my ( $id ) = @_;
421         my $dbh = C4::Context->dbh();
422         my $query = "DELETE FROM saved_sql WHERE id = ?";
423         my $sth = $dbh->prepare($query);
424         $sth->execute($id);
425         $sth->finish();
426 }       
427
428 sub get_saved_reports {
429     my $dbh   = C4::Context->dbh();
430     my $query = "SELECT * FROM saved_sql ORDER by date_created";
431     my $sth   = $dbh->prepare($query);
432     $sth->execute();
433     my @reports;
434     while ( my $data = $sth->fetchrow_hashref() ) {
435         push @reports, $data;
436     }
437     $sth->finish();
438     return ( \@reports );
439 }
440
441 sub get_saved_report {
442     my ($id)  = @_;
443     my $dbh   = C4::Context->dbh();
444     my $query = " SELECT * FROM saved_sql WHERE id = ?";
445     my $sth   = $dbh->prepare($query);
446     $sth->execute($id);
447     my $data = $sth->fetchrow_hashref();
448     $sth->finish();
449     return ( $data->{'savedsql'}, $data->{'type'} );
450 }
451
452 =item create_compound($masterID,$subreportID)
453
454 This will take 2 reports and create a compound report using both of them
455
456 =cut
457
458 sub create_compound {
459         my ($masterID,$subreportID) = @_;
460         my $dbh = C4::Context->dbh();
461         # get the reports
462         my ($mastersql,$mastertype) = get_saved_report($masterID);
463         my ($subsql,$subtype) = get_saved_report($subreportID);
464         
465         # now we have to do some checking to see how these two will fit together
466         # or if they will
467         my ($mastertables,$subtables);
468         if ($mastersql =~ / from (.*) where /i){ 
469                 $mastertables = $1;
470         }
471         if ($subsql =~ / from (.*) where /i){
472                 $subtables = $1;
473         }
474         return ($mastertables,$subtables);
475 }
476
477 =item get_column_type($column)
478
479 This takes a column name of the format table.column and will return what type it is
480 (free text, set values, date)
481
482 =cut
483
484 sub get_column_type {
485         my ($tablecolumn) = @_;
486         my ($table,$column) = split(/\./,$tablecolumn);
487         my $dbh = C4::Context->dbh();
488         my $catalog;
489         my $schema;
490
491         # mysql doesnt support a column selection, set column to %
492         my $tempcolumn='%';
493         my $sth = $dbh->column_info( $catalog, $schema, $table, $tempcolumn ) || die $dbh->errstr;
494         while (my $info = $sth->fetchrow_hashref()){
495                 if ($info->{'COLUMN_NAME'} eq $column){
496                         #column we want
497                         if ($info->{'TYPE_NAME'} eq 'CHAR' || $info->{'TYPE_NAME'} eq 'VARCHAR'){
498                                 $info->{'TYPE_NAME'} = 'distinct';
499                         }
500                         return $info->{'TYPE_NAME'};            
501                 }
502         }
503         $sth->finish();
504 }
505
506 =item get_distinct_values($column)
507
508 Given a column name, return an arrary ref of hashrefs suitable for use as a tmpl_loop 
509 with the distinct values of the column
510
511 =cut
512
513 sub get_distinct_values {
514         my ($tablecolumn) = @_;
515         my ($table,$column) = split(/\./,$tablecolumn);
516         my $dbh = C4::Context->dbh();
517         my $query =
518           "SELECT distinct($column) as availablevalues FROM $table";
519         my $sth = $dbh->prepare($query);
520         $sth->execute();
521         my @values;
522         while ( my $row = $sth->fetchrow_hashref() ) {
523                 push @values, $row;
524         }
525         $sth->finish();
526         return \@values;
527 }       
528
529 sub save_dictionary {
530         my ($name,$description,$sql,$area) = @_;
531         my $dbh = C4::Context->dbh();
532         my $query = "INSERT INTO reports_dictionary (name,description,saved_sql,area,date_created,date_modified)
533   VALUES (?,?,?,?,now(),now())";
534     my $sth = $dbh->prepare($query);
535     $sth->execute($name,$description,$sql,$area) || return 0;
536     $sth->finish();
537     return 1;
538 }
539
540 sub get_from_dictionary {
541         my ($area,$id) = @_;
542         my $dbh = C4::Context->dbh();
543         my $query = "SELECT * FROM reports_dictionary";
544         if ($area){
545                 $query.= " WHERE area = ?";
546         }
547         elsif ($id){
548                 $query.= " WHERE id = ?"
549         }
550         my $sth = $dbh->prepare($query);
551         if ($id){
552                 $sth->execute($id);
553         }
554         elsif ($area) {
555                 $sth->execute($area);
556         }
557         else {
558                 $sth->execute();
559         }
560         my @loop;
561         while (my $data = $sth->fetchrow_hashref()){
562                 push @loop,$data;
563                 
564                 }
565         $sth->finish();
566         return (\@loop);
567 }
568
569 sub delete_definition {
570         my ($id) = @_;
571         my $dbh = C4::Context->dbh();
572         my $query = "DELETE FROM reports_dictionary WHERE id = ?";
573         my $sth = $dbh->prepare($query);
574         $sth->execute($id);
575         $sth->finish();
576         }
577 =head1 AUTHOR
578
579 Chris Cormack <crc@liblime.com>
580
581 =cut
582
583 1;