Bug 5549 : overdues display should be hours mins aware
[koha.git] / C4 / Reports / Guided.pm
index 5df6c2a..75c6c37 100644 (file)
@@ -13,16 +13,20 @@ package C4::Reports::Guided;
 # 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
+# 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 strict;
+#use warnings; FIXME - Bug 2505 this module needs a lot of repair to run clean under warnings
 use CGI;
+use Carp;
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 use C4::Context;
-use C4::Output;
+use C4::Dates qw/format_date format_date_in_iso/;
+use C4::Templates qw/themelanguage/;
+use C4::Dates;
 use XML::Simple;
 use XML::Dumper;
 use C4::Debug;
@@ -36,9 +40,10 @@ BEGIN {
        @ISA = qw(Exporter);
        @EXPORT = qw(
                get_report_types get_report_areas get_columns build_query get_criteria
-               save_report get_saved_reports execute_query get_saved_report create_compound run_compound
+           save_report get_saved_reports execute_query get_saved_report create_compound run_compound
                get_column_type get_distinct_values save_dictionary get_from_dictionary
                delete_definition delete_report format_results get_sql
+        nb_rows update_sql
        );
 }
 
@@ -69,18 +74,29 @@ $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.holdingbranch', 'items.homebranch' ,'items.itemlost', 'items.location', 'items.ccode'];
-$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')) {
     unshift @{ $criteria{'1'} }, 'items.itype';
     unshift @{ $criteria{'2'} }, 'items.itype';
@@ -90,7 +106,7 @@ if (C4::Context->preference('item-level_itypes')) {
 }
 
 =head1 NAME
-   
+
 C4::Reports::Guided - Module for generating guided reports 
 
 =head1 SYNOPSIS
@@ -99,6 +115,7 @@ C4::Reports::Guided - Module for generating guided reports
 
 =head1 DESCRIPTION
 
+=cut
 
 =head1 METHODS
 
@@ -238,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++){
@@ -295,201 +311,189 @@ sub get_criteria {
     my ($area,$cgi) = @_;
     my $dbh    = C4::Context->dbh();
     my $crit   = $criteria{$area};
-       my $column_defs = _get_column_defs($cgi);
+    my $column_defs = _get_column_defs($cgi);
     my @criteria_array;
     foreach my $localcrit (@$crit) {
         my ( $value, $type )   = split( /\|/, $localcrit );
         my ( $table, $column ) = split( /\./, $value );
-        if ( $type eq 'date' ) {
-                       my %temp;
-            $temp{'name'}   = $value;
-            $temp{'date'}   = 1;
-                       $temp{'description'} = $column_defs->{$value};
+        if ($type eq 'textrange') {
+            my %temp;
+            $temp{'name'}        = $value;
+            $temp{'from'}        = "from_" . $value;
+            $temp{'to'}          = "to_" . $value;
+            $temp{'textrange'}   = 1;
+            $temp{'description'} = $column_defs->{$value};
+            push @criteria_array, \%temp;
+        }
+        elsif ($type eq 'date') {
+            my %temp;
+            $temp{'name'}        = $value;
+            $temp{'date'}        = 1;
+            $temp{'description'} = $column_defs->{$value};
+            push @criteria_array, \%temp;
+        }
+        elsif ($type eq 'daterange') {
+            my %temp;
+            $temp{'name'}        = $value;
+            $temp{'from'}        = "from_" . $value;
+            $temp{'to'}          = "to_" . $value;
+            $temp{'daterange'}   = 1;
+            $temp{'description'} = $column_defs->{$value};
             push @criteria_array, \%temp;
         }
         else {
-
             my $query =
-              "SELECT distinct($column) as availablevalues FROM $table";
+            "SELECT distinct($column) as availablevalues FROM $table";
             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;
-                ### $row;
+                if ($row->{'availablevalues'} eq '') { $row->{'default'} = 1 };
             }
             $sth->finish();
+
             my %temp;
-            $temp{'name'}   = $value;
-                       $temp{'description'} = $column_defs->{$value};
-            $temp{'values'} = \@values;
+            $temp{'name'}        = $value;
+            $temp{'description'} = $column_defs->{$value};
+            $temp{'values'}      = \@values;
+
             push @criteria_array, \%temp;
         }
+
     }
     return ( \@criteria_array );
 }
 
+sub nb_rows($) {
+    my $sql = shift or return;
+    my $sth = C4::Context->dbh->prepare($sql);
+    $sth->execute();
+    my $rows = $sth->fetchall_arrayref();
+    return scalar (@$rows);
+}
+
 =item execute_query
 
-=over
+  ($results, $total, $error) = execute_query($sql, $offset, $limit)
 
-($results, $total) = execute_query($sql, $type, $offset, $limit, $format, $id)
 
-=back
+When passed C<$sql>, this function returns an array ref containing a result set
+suitably formatted for display in html or for output as a flat file when passed in
+C<$format> and C<$id>. It also returns the C<$total> records available for the
+supplied query. If passed any query other than a SELECT, or if there is a db error,
+C<$errors> an array ref is returned containing the error after this manner:
 
-    When passed C<$sql>, this function returns an array ref containing a result set
-    suitably formatted for display in html or for output as a flat file when passed in
-    C<$format> and C<$id>. It also returns the C<$total> records available for the
-    supplied query. If passed any query other than a SELECT, or if there is a db error,
-    C<$errors> an array ref is returned containing the error after this manner:
+C<$error->{'sqlerr'}> contains the offending SQL keyword.
+C<$error->{'queryerr'}> contains the native db engine error returned for the query.
 
-    C<$error->{'sqlerr'}> contains the offending SQL keyword.
-    C<$error->{'queryerr'}> contains the native db engine error returned for the query.
-    
-    Valid values for C<$format> are 'text,' 'tab,' 'csv,' or 'url. C<$sql>, C<$type>,
-    C<$offset>, and C<$limit> are required parameters. If a valid C<$format> is passed
-    in, C<$offset> and C<$limit> are ignored for obvious reasons. A LIMIT specified by
-    the user in a user-supplied SQL query WILL apply in any case.
+Valid values for C<$format> are 'text,' 'tab,' 'csv,' or 'url. C<$sql>, C<$type>,
+C<$offset>, and C<$limit> are required parameters. If a valid C<$format> is passed
+in, C<$offset> and C<$limit> are ignored for obvious reasons. A LIMIT specified by
+the user in a user-supplied SQL query WILL apply in any case.
 
 =cut
 
-# FIXME: This needs to be generalized to reports in general
+# returns $sql, $offset, $limit
+# $sql returned will be transformed to:
+#  ~ remove any LIMIT clause
+#  ~ repace SELECT clause w/ SELECT count(*)
 
-sub execute_query ($$$$;$$) {
-    my ( $sql, $type, $offset, $limit, $format, $id ) = @_;
-    my @params;
-    my $total = 0;
-    my ($useroffset, $userlimit);
-    my @errors = ();
-    my $error = {};
-    my $sqlerr = 0;
+sub select_2_select_count ($) {
+    # Modify the query passed in to create a count query... (I think this covers all cases -crn)
+    my ($sql) = strip_limit(shift) or return;
+    $sql =~ s/\bSELECT\W+(?:\w+\W+){1,}?FROM\b|\bSELECT\W\*\WFROM\b/SELECT count(*) FROM /ig;
+    return $sql;
+}
+sub strip_limit ($) {
+    my $sql = shift or return;
+    ($sql =~ /\bLIMIT\b/i) or return ($sql, 0, undef);
+    $sql =~ s/\bLIMIT\b\s*(\d+)(\s*\,\s*(\d+))?\s*/ /ig;
+    return ($sql, (defined $2 ? $1 : 0), (defined $3 ? $3 : $1));   # offset can default to 0, LIMIT cannot!
+}
+
+sub execute_query ($;$$$) {
+
+    my ( $sql, $offset, $limit, $no_count ) = @_;
+
+    # check parameters
+    unless ($sql) {
+        carp "execute_query() called without SQL argument";
+        return;
+    }
+    $offset = 0    unless $offset;
+    $limit  = 999999 unless $limit;
+    $debug and print STDERR "execute_query($sql, $offset, $limit)\n";
     if ($sql =~ /;?\W?(UPDATE|DELETE|DROP|INSERT|SHOW|CREATE)\W/i) {
-        $sqlerr = 1;
-        $error->{'sqlerr'} = $1;
-        push @errors, $error;
-    } elsif ($sql !~ /^(SELECT)/i) {
-        $sqlerr = 1;
-        $error->{'queryerr'} = 'Missing SELECT';
-        push @errors, $error;
+        return (undef, {  sqlerr => $1} );
+    } elsif ($sql !~ /^\s*SELECT\b\s*/i) {
+        return (undef, { queryerr => 'Missing SELECT'} );
     }
-    if ($sqlerr == 0) {
-        my $dbh = C4::Context->dbh();
-        unless ($format eq 'text' || $format eq 'tab' || $format eq 'csv' || $format eq 'url'){
-            # Grab offset/limit from user supplied LIMIT and drop the LIMIT so we can control pagination
-            if ($sql =~ /LIMIT/i) {
-                $sql =~ s/LIMIT\W?(\d+)?\,?\W+?(\d+)//ig;
-                $debug and warn "User has supplied LIMIT\n";
-                $useroffset = $1;
-                $userlimit = $2;
-                $debug and warn "User supplied offset = $useroffset, limit = $userlimit\n";
-                $offset += $useroffset if $useroffset;
-                # keep track of where we are if there is a user supplied LIMIT
-                if ( $offset + $limit > $userlimit ) {
-                    $limit = $userlimit - $offset;
-                }
-            }
-            my $countsql = $sql;
-            $sql .= " LIMIT ?, ?";
-            $debug and warn "Passing query with params offset = $offset, limit = $limit\n";
-            @params = ($offset, $limit);
-            # Modify the query passed in to create a count query... (I think this covers all cases -crn)
-            $countsql =~ s/\bSELECT\W+(?:\w+\W+){1,}?FROM\b|\bSELECT\W\*\WFROM\b/SELECT count(*) FROM /ig;
-            $debug and warn "original query: $sql\n";
-            $debug and warn "count query: $countsql\n";
-            my $sth1 = $dbh->prepare($countsql);
-            $sth1->execute();
-            $total = $sth1->fetchrow();
-            $debug and warn "total records for this query: $total\n";
-            $total = $userlimit if defined($userlimit) and $userlimit < $total;     # we will never exceed a user defined LIMIT and...
-            $userlimit = $total if defined($userlimit) and $userlimit > $total;     # we will never exceed the total number of records available to satisfy the query
-        }
-        my $sth = $dbh->prepare($sql);
-        $sth->execute(@params);
-        my $colnames=$sth->{'NAME'};
-        my @results;
-        my $row;
-        my %temphash;
-        $row = join ('</th><th>',@$colnames);
-        $row = "<tr><th>$row</th></tr>";
-        $temphash{'row'} = $row;
-        push @results, \%temphash;
-        my $string;
-        if ($format eq 'tab') {
-            $string = join("\t",@$colnames);
-        }
-        if ($format eq 'csv') {
-            $string = join(",",@$colnames);
-        }
-        my @xmlarray;
-        while ( my @data = $sth->fetchrow_array() ) {
-            # if the field is a date field, it needs formatting
-            foreach my $data (@data) {
-                next unless $data =~ C4::Dates->regexp("iso");
-                my $date = C4::Dates->new($data, "iso");
-                $data = $date->output();
-            }
-            # tabular
-            my %temphash;
-            my $row = join( '</td><td>', @data );
-            $row = "<tr><td>$row</td></tr>";
-            $temphash{'row'} = $row;
-            if ( $format eq 'text' ) {
-                $string .= "\n" . $row;
-            }
-            if ($format eq 'tab' ){
-                $row = join("\t",@data);
-                $string .="\n" . $row;
-            }
-            if ($format eq 'csv' ){
-                $row = join(",",@data);
-                $string .="\n" . $row;
-            }
-            if ($format eq 'url'){
-                my $temphash;
-                @$temphash{@$colnames}=@data;
-                push @xmlarray,$temphash;
-            }
-            push @results, \%temphash;
-        }
-        if (defined($sth->errstr)) {
-            $error->{'queryerr'} = $sth->errstr;
-            push @errors, $error;
-            warn "Database returned: $sth->errstr";
-        }
-        if ( $format eq 'text' || $format eq 'tab' || $format eq 'csv' ) {
-            return $string, $total, \@errors;
-        }
-        elsif ($format eq 'url') {
-            my $url = "/cgi-bin/koha/reports/guided_reports.pl?phase=retrieve%20results&id=$id";
-            my $dump = new XML::Dumper;
-            my $xml = $dump->pl2xml( \@xmlarray );
-            store_results($id,$xml);
-            return $url, $total, \@errors;
-        }
-        else {
-            return \@results, $total, \@errors;
+
+    my ($useroffset, $userlimit);
+
+    # Grab offset/limit from user supplied LIMIT and drop the LIMIT so we can control pagination
+    ($sql, $useroffset, $userlimit) = strip_limit($sql);
+    $debug and warn sprintf "User has supplied (OFFSET,) LIMIT = %s, %s",
+        $useroffset,
+        (defined($userlimit ) ? $userlimit  : 'UNDEF');
+    $offset += $useroffset;
+    if (defined($userlimit)) {
+        if ($offset + $limit > $userlimit ) {
+            $limit = $userlimit - $offset;
+        } elsif ( ! $offset && $limit < $userlimit ) {
+            $limit = $userlimit;
         }
-    } else {
-        return undef, undef, \@errors;
     }
+    $sql .= " LIMIT ?, ?";
+
+    my $sth = C4::Context->dbh->prepare($sql);
+    $sth->execute($offset, $limit);
+    return ( $sth );
+    # my @xmlarray = ... ;
+    # my $url = "/cgi-bin/koha/reports/guided_reports.pl?phase=retrieve%20results&id=$id";
+    # my $xml = XML::Dumper->new()->pl2xml( \@xmlarray );
+    # store_results($id,$xml);
 }
 
 =item save_report($sql,$name,$type,$notes)
 
-Given some sql and a name this will saved it so that it can resued
+Given some sql and a name this will saved it so that it can reused
+Returns id of the newly created report
 
 =cut
 
 sub save_report {
-    my ( $sql, $name, $type, $notes ) = @_;
+    my ( $borrowernumber, $sql, $name, $type, $notes ) = @_;
     my $dbh = C4::Context->dbh();
-    $sql =~ s/(\W*)$//;
+    $sql =~ s/(\s*\;\s*)$//; # removes trailing whitespace and /;/
     my $query =
 "INSERT INTO saved_sql (borrowernumber,date_created,last_modified,savedsql,report_name,type,notes)  VALUES (?,now(),now(),?,?,?,?)";
+    $dbh->do( $query, undef, $borrowernumber, $sql, $name, $type, $notes );
+    my $id = $dbh->selectrow_array("SELECT max(id) FROM saved_sql WHERE borrowernumber=? AND report_name=?", undef,
+                                   $borrowernumber, $name);
+    return $id;
+}
+
+sub update_sql {
+    my $id = shift || croak "No Id given";
+    my $sql = shift;
+    my $reportname = shift;
+    my $notes = shift;
+    my $dbh = C4::Context->dbh();
+    $sql =~ s/(\s*\;\s*)$//; # removes trailing whitespace and /;/
+    my $query = "UPDATE saved_sql SET savedsql = ?, last_modified = now(), report_name = ?, notes = ? WHERE id = ? ";
     my $sth = $dbh->prepare($query);
-    $sth->execute( 0, $sql, $name, $type, $notes );
+    $sth->execute( $sql, $reportname, $notes, $id );
     $sth->finish();
-
 }
 
 sub store_results {
@@ -502,15 +506,12 @@ sub store_results {
                my $query2 = "UPDATE saved_reports SET report=?,date_run=now() WHERE report_id=?";
                my $sth2 = $dbh->prepare($query2);
            $sth2->execute($xml,$id);
-               $sth2->finish();
        }
        else {
                my $query2 = "INSERT INTO saved_reports (report_id,report,date_run) VALUES (?,?,now())";
                my $sth2 = $dbh->prepare($query2);
                $sth2->execute($id,$xml);
-               $sth2->finish();
        }
-       $sth->finish();
 }
 
 sub format_results {
@@ -535,7 +536,6 @@ sub format_results {
        $sth = $dbh->prepare($query);
        $sth->execute($id);
        $data = $sth->fetchrow_hashref();
-    $sth->finish();
        return ($perl,$data->{'report_name'},$data->{'notes'}); 
 }      
 
@@ -545,22 +545,55 @@ sub delete_report {
        my $query = "DELETE FROM saved_sql WHERE id = ?";
        my $sth = $dbh->prepare($query);
        $sth->execute($id);
-       $sth->finish();
 }      
 
+# $filter is either { date => $d, author => $a, keyword => $kw }
+# or $keyword. Optional.
+my $DATE_FORMAT = "%d/%m/%Y";
 sub get_saved_reports {
+    my ($filter) = @_;
+    $filter = { keyword => $filter } if $filter && !ref( $filter );
+
     my $dbh   = C4::Context->dbh();
-    my $query = "SELECT *,saved_sql.id AS id FROM saved_sql 
-    LEFT JOIN saved_reports ON saved_reports.report_id = saved_sql.id
-    ORDER by date_created";
-    my $sth   = $dbh->prepare($query);
-    $sth->execute();
-    my @reports;
-    while ( my $data = $sth->fetchrow_hashref() ) {
-        push @reports, $data;
+    my (@cond,@args);
+    my $query = "SELECT saved_sql.id, report_id, report,
+                        date_run, date_created, last_modified, savedsql, last_run,
+                        report_name, type, notes,
+                        borrowernumber, surname as borrowersurname, firstname as borrowerfirstname
+                 FROM saved_sql 
+                 LEFT JOIN saved_reports ON saved_reports.report_id = saved_sql.id
+                 LEFT OUTER JOIN borrowers USING (borrowernumber)";
+    if ($filter) {
+        if (my $date = $filter->{date}) {
+            $date = format_date_in_iso($date);
+            push @cond, "DATE(date_run) = ? OR
+                         DATE(date_created) = ? OR
+                         DATE(last_modified) = ? OR
+                         DATE(last_run) = ?";
+            push @args, $date, $date, $date, $date;
+        }
+        if (my $author = $filter->{author}) {
+            $author = "%$author%";
+            push @cond, "surname LIKE ? OR
+                         firstname LIKE ?";
+            push @args, $author, $author;
+        }
+        if (my $keyword = $filter->{keyword}) {
+            $keyword = "%$keyword%";
+            push @cond, "report LIKE ? OR
+                         report_name LIKE ? OR
+                         notes LIKE ? OR
+                         savedsql LIKE ?";
+            push @args, $keyword, $keyword, $keyword, $keyword;
+        }
     }
-    $sth->finish();
-    return ( \@reports );
+    $query .= " WHERE ".join( " AND ", map "($_)", @cond ) if @cond;
+    $query .= " ORDER by date_created";
+    
+    my $result = $dbh->selectall_arrayref($query, {Slice => {}}, @args);
+    $_->{date_created} = format_date($_->{date_created}) foreach @$result;
+
+    return $result;
 }
 
 sub get_saved_report {
@@ -570,7 +603,6 @@ sub get_saved_report {
     my $sth   = $dbh->prepare($query);
     $sth->execute($id);
     my $data = $sth->fetchrow_hashref();
-    $sth->finish();
     return ( $data->{'savedsql'}, $data->{'type'}, $data->{'report_name'}, $data->{'notes'} );
 }
 
@@ -625,7 +657,6 @@ sub get_column_type {
                        return $info->{'TYPE_NAME'};            
                }
        }
-       $sth->finish();
 }
 
 =item get_distinct_values($column)
@@ -643,12 +674,7 @@ sub get_distinct_values {
          "SELECT distinct($column) as availablevalues FROM $table";
        my $sth = $dbh->prepare($query);
        $sth->execute();
-       my @values;
-       while ( my $row = $sth->fetchrow_hashref() ) {
-               push @values, $row;
-       }
-       $sth->finish();
-       return \@values;
+    return $sth->fetchall_arrayref({});
 }      
 
 sub save_dictionary {
@@ -658,7 +684,6 @@ sub save_dictionary {
   VALUES (?,?,?,?,now(),now())";
     my $sth = $dbh->prepare($query);
     $sth->execute($name,$description,$sql,$area) || return 0;
-    $sth->finish();
     return 1;
 }
 
@@ -689,27 +714,24 @@ sub get_from_dictionary {
                push @loop,$data;
                
        }
-       $sth->finish();
        return (\@loop);
 }
 
 sub delete_definition {
-       my ($id) = @_;
+       my ($id) = @_ or return;
        my $dbh = C4::Context->dbh();
        my $query = "DELETE FROM reports_dictionary WHERE id = ?";
        my $sth = $dbh->prepare($query);
        $sth->execute($id);
-       $sth->finish();
 }
 
 sub get_sql {
-       my ($id) = @_;
+       my ($id) = @_ or return;
        my $dbh = C4::Context->dbh();
        my $query = "SELECT * FROM saved_sql WHERE id = ?";
        my $sth = $dbh->prepare($query);
        $sth->execute($id);
        my $data=$sth->fetchrow_hashref();
-       $sth->finish(); 
        return $data->{'savedsql'};
 }
 
@@ -719,13 +741,14 @@ sub _get_column_defs {
        my $columns_def_file = "columns.def";
        my $htdocs = C4::Context->config('intrahtdocs');                       
        my $section='intranet';
-       my ($theme, $lang) = themelanguage($htdocs, $columns_def_file, $section,$cgi);
+       my ($theme, $lang) = C4::Templates::themelanguage($htdocs, $columns_def_file, $section,$cgi);
 
        my $full_path_to_columns_def_file="$htdocs/$theme/$lang/$columns_def_file";    
        open (COLUMNS,$full_path_to_columns_def_file);
        while (my $input = <COLUMNS>){
+               chomp $input;
                my @row =split(/\t/,$input);
-               $columns{$row[0]}=$row[1];
+               $columns{$row[0]}= $row[1];
        }
 
        close COLUMNS;