Merge branch 'master' of git@lx-office.linet-services.de:lx-office-erp
[kivitendo-erp.git] / SL / DATEV.pm
index d6dd7c6..60eb97f 100644 (file)
 
 package DATEV;
 
+use utf8;
+use strict;
+
 use SL::DBUtils;
 use SL::DATEV::KNEFile;
+use SL::Taxkeys;
 
 use Data::Dumper;
+use File::Path;
+use List::Util qw(max);
+use Time::HiRes qw(gettimeofday);
+
+sub _get_export_path {
+  $main::lxdebug->enter_sub();
+
+  my ($a, $b) = gettimeofday();
+  my $path    = get_path_for_download_token("${a}-${b}-${$}");
+
+  mkpath($path) unless (-d $path);
+
+  $main::lxdebug->leave_sub();
+
+  return $path;
+}
+
+sub get_path_for_download_token {
+  $main::lxdebug->enter_sub();
+
+  my $token = shift;
+  my $path;
+
+  if ($token =~ m|^(\d+)-(\d+)-(\d+)$|) {
+    $path = $::lx_office_conf{paths}->{userspath} . "/datev-export-${1}-${2}-${3}";
+  }
+
+  $main::lxdebug->leave_sub();
+
+  return $path;
+}
+
+sub get_download_token_for_path {
+  $main::lxdebug->enter_sub();
+
+  my $path = shift;
+  my $token;
+
+  if ($path =~ m|.*datev-export-(\d+)-(\d+)-(\d+)/?$|) {
+    $token = "${1}-${2}-${3}";
+  }
+
+  $main::lxdebug->leave_sub();
+
+  return $token;
+}
+
+sub clean_temporary_directories {
+  $main::lxdebug->enter_sub();
+
+  foreach my $path (glob($::lx_office_conf{paths}->{userspath} . "/datev-export-*")) {
+    next unless (-d $path);
+
+    my $mtime = (stat($path))[9];
+    next if ((time() - $mtime) < 8 * 60 * 60);
+
+    rmtree $path;
+  }
+
+  $main::lxdebug->leave_sub();
+}
 
 sub _fill {
   $main::lxdebug->enter_sub();
@@ -62,11 +127,11 @@ sub get_datev_stamm {
   # connect to database
   my $dbh = $form->dbconnect($myconfig);
 
-  $query = qq|SELECT * FROM datev|;
-  $sth   = $dbh->prepare($query);
+  my $query = qq|SELECT * FROM datev|;
+  my $sth   = $dbh->prepare($query);
   $sth->execute || $form->dberror($query);
 
-  my $ref = $sth->fetchrow_hashref(NAME_lc);
+  my $ref = $sth->fetchrow_hashref("NAME_lc");
 
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
@@ -83,7 +148,7 @@ sub save_datev_stamm {
   # connect to database
   my $dbh = $form->dbconnect_noauto($myconfig);
 
-  $query = qq|DELETE FROM datev|;
+  my $query = qq|DELETE FROM datev|;
   $dbh->do($query) || $form->dberror($query);
 
   $query = qq|INSERT INTO datev
@@ -96,7 +161,7 @@ sub save_datev_stamm {
     . $dbh->quote($form->{mandantennr}) . qq|,|
     . $dbh->quote($form->{datentraegernr}) . qq|,|
     . $dbh->quote($form->{abrechnungsnr}) . qq|)|;
-  $sth = $dbh->prepare($query);
+  my $sth = $dbh->prepare($query);
   $sth->execute || $form->dberror($query);
   $sth->finish;
 
@@ -109,17 +174,17 @@ sub kne_export {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig, $form) = @_;
-  my @rc;
+  my $result;
 
   if ($form->{exporttype} == 0) {
-    @rc = &kne_buchungsexport($myconfig, $form);
+    $result = kne_buchungsexport($myconfig, $form);
   } else {
-    @rc = &kne_stammdatenexport($myconfig, $form);
+    $result = kne_stammdatenexport($myconfig, $form);
   }
 
   $main::lxdebug->leave_sub();
 
-  return @rc;
+  return $result;
 }
 
 sub obe_export {
@@ -138,6 +203,9 @@ sub get_dates {
   $main::lxdebug->enter_sub();
 
   my ($zeitraum, $monat, $quartal, $transdatefrom, $transdateto) = @_;
+  my ($fromto, $jahr, $leap);
+
+  my $form = $main::form;
 
   $fromto = "transdate >= ";
 
@@ -241,6 +309,14 @@ sub get_dates {
   return ($fromto, $jahr);
 }
 
+sub _sign {
+  my $value = shift;
+
+  return $value < 0 ? -1
+    :    $value > 0 ?  1
+    :                  0;
+}
+
 sub _get_transactions {
   $main::lxdebug->enter_sub();
 
@@ -251,60 +327,69 @@ sub _get_transactions {
 
   my $dbh      =  $form->get_standard_dbh($myconfig);
 
+  my ($notsplitindex);
   my @errors   = ();
 
+  $form->{net_gross_differences}     = [];
+  $form->{sum_net_gross_differences} = 0;
+
   $fromto      =~ s/transdate/ac\.transdate/g;
 
-  my %taxes    =  selectall_as_map($form, $dbh, qq|SELECT id, rate FROM tax|, 'id', 'rate');
+  my $taxkeys  = Taxkeys->new();
+  my $filter   = '';            # Useful for debugging purposes
+
+  my %all_taxchart_ids = selectall_as_map($form, $dbh, qq|SELECT DISTINCT chart_id, TRUE AS is_set FROM tax|, 'chart_id', 'is_set');
 
   my $query    =
-    qq|SELECT ac.oid, ac.transdate, ac.trans_id,ar.id, ac.amount, ac.taxkey,
+    qq|SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ar.id, ac.amount, ac.taxkey,
          ar.invnumber, ar.duedate, ar.amount as umsatz,
          ct.name,
-         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id,
-         t.chart_id, t.rate, t.id AS taxid, t.taxkey AS taxtaxkey
-       FROM acc_trans ac,ar ar, customer ct, chart c
-       LEFT JOIN tax t ON (t.chart_id = c.id)
-       WHERE $fromto
-         AND (ac.trans_id = ar.id)
-         AND (ac.trans_id = ar.id)
-         AND (ar.customer_id = ct.id)
-         AND (ac.chart_id = c.id)
+         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, c.link,
+         ar.invoice
+       FROM acc_trans ac
+       LEFT JOIN ar          ON (ac.trans_id    = ar.id)
+       LEFT JOIN customer ct ON (ar.customer_id = ct.id)
+       LEFT JOIN chart c     ON (ac.chart_id    = c.id)
+       WHERE (ar.id IS NOT NULL)
+         AND $fromto
+         $filter
 
        UNION ALL
 
-       SELECT ac.oid, ac.transdate, ac.trans_id,ap.id, ac.amount, ac.taxkey,
+       SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ap.id, ac.amount, ac.taxkey,
          ap.invnumber, ap.duedate, ap.amount as umsatz,
          ct.name,
-         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id,
-         t.chart_id, t.rate, t.id AS taxid, t.taxkey AS taxtaxkey
-       FROM acc_trans ac, ap ap, vendor ct, chart c
-       LEFT JOIN tax t ON (t.chart_id = c.id)
-       WHERE $fromto
-         AND (ac.trans_id = ap.id)
-         AND (ap.vendor_id = ct.id)
-         AND (ac.chart_id = c.id)
+         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, c.link,
+         ap.invoice
+       FROM acc_trans ac
+       LEFT JOIN ap        ON (ac.trans_id  = ap.id)
+       LEFT JOIN vendor ct ON (ap.vendor_id = ct.id)
+       LEFT JOIN chart c   ON (ac.chart_id  = c.id)
+       WHERE (ap.id IS NOT NULL)
+         AND $fromto
+         $filter
 
        UNION ALL
 
-       SELECT ac.oid, ac.transdate, ac.trans_id,gl.id, ac.amount, ac.taxkey,
+       SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,gl.id, ac.amount, ac.taxkey,
          gl.reference AS invnumber, gl.transdate AS duedate, ac.amount as umsatz,
          gl.description AS name,
-         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id,
-         t.chart_id, t.rate, t.id AS taxid, t.taxkey AS taxtaxkey
-       FROM acc_trans ac, gl gl, chart c
-       LEFT JOIN tax t ON (t.chart_id = c.id)
-       WHERE $fromto
-         AND (ac.trans_id = gl.id)
-         AND (ac.chart_id = c.id)
+         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, c.link,
+         FALSE AS invoice
+       FROM acc_trans ac
+       LEFT JOIN gl      ON (ac.trans_id  = gl.id)
+       LEFT JOIN chart c ON (ac.chart_id  = c.id)
+       WHERE (gl.id IS NOT NULL)
+         AND $fromto
+         $filter
 
-       ORDER BY trans_id, oid|;
+       ORDER BY trans_id, acc_trans_id|;
 
-  my $sth     = prepare_execute_query($form, $dbh, $query);
+  my $sth = prepare_execute_query($form, $dbh, $query);
+  $form->{DATEV} = [];
 
-  my @splits;
   my $counter = 0;
-  while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
+  while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
     $counter++;
     if (($counter % 500) == 0) {
       print("$counter ");
@@ -316,15 +401,38 @@ sub _get_transactions {
     my $firstrun = 1;
 
     while (abs($count) > 0.01 || $firstrun) {
-      my $ref2 = $sth->fetchrow_hashref(NAME_lc);
+      my $ref2 = $sth->fetchrow_hashref("NAME_lc");
       last unless ($ref2);
 
+      if ($ref2->{trans_id} != $trans->[0]->{trans_id}) {
+        $form->error("Unbalanced ledger! old trans_id " . $trans->[0]->{trans_id} . " new trans_id " . $ref2->{trans_id} . " count $count");
+        ::end_of_request();
+      }
+
       push @{ $trans }, $ref2;
 
       $count    += $ref2->{amount};
       $firstrun  = 0;
     }
 
+    foreach my $i (0 .. scalar(@{ $trans }) - 1) {
+      my $ref        = $trans->[$i];
+      my $prev_ref   = 0 < $i ? $trans->[$i - 1] : undef;
+      if (   $all_taxchart_ids{$ref->{id}}
+          && ($ref->{link} =~ m/(?:AP_tax|AR_tax)/)
+          && (   ($prev_ref && $prev_ref->{taxkey} && (_sign($ref->{amount}) == _sign($prev_ref->{amount})))
+              || $ref->{invoice})) {
+        $ref->{is_tax} = 1;
+      }
+
+      if (   !$ref->{invoice}
+          &&  $ref->{is_tax}
+          && !($prev_ref->{is_tax})
+          &&  (_sign($ref->{amount}) == _sign($prev_ref->{amount}))) {
+        $trans->[$i - 1]->{tax_amount} = $ref->{amount};
+      }
+    }
+
     my %taxid_taxkeys = ();
     my $absumsatz     = 0;
     if (scalar(@{$trans}) <= 2) {
@@ -337,15 +445,15 @@ sub _get_transactions {
         $absumsatz     = $trans->[$j]->{'amount'};
         $notsplitindex = $j;
       }
-      if (($trans->[$j]->{'taxtaxkey'}) && ($trans->[$j]->{'taxid'})) {
-        $taxid_taxkeys{$trans->[$j]->{'taxtaxkey'}}     = $trans->[$j]->{'taxid'};
-      }
     }
 
-    my $ml = ($trans->[0]->{'umsatz'} > 0) ? 1 : -1;
+    my $ml             = ($trans->[0]->{'umsatz'} > 0) ? 1 : -1;
+    my $rounding_error = 0;
+    my @taxed;
+
     for my $j (0 .. (scalar(@{$trans}) - 1)) {
       if (   ($j != $notsplitindex)
-          && ($trans->[$j]->{'chart_id'}  eq "")
+          && !$trans->[$j]->{is_tax}
           && (   $trans->[$j]->{'taxkey'} eq ""
               || $trans->[$j]->{'taxkey'} eq "0"
               || $trans->[$j]->{'taxkey'} eq "1"
@@ -359,27 +467,85 @@ sub _get_transactions {
         $new_trans{'umsatz'}      = abs($trans->[$j]->{'amount'}) * $ml;
         $trans->[$j]->{'umsatz'}  = abs($trans->[$j]->{'amount'}) * $ml;
 
-        push @splits, [ \%new_trans, $trans->[$j] ];
-        push @{ $form->{DATEV} }, $splits[-1];
+        push @{ $form->{DATEV} }, [ \%new_trans, $trans->[$j] ];
 
-      } elsif (($j != $notsplitindex) && ($trans->[$j]->{'chart_id'} eq "")) {
-        $absumsatz += ($trans->[$j]->{'amount'} * (1 + $taxes{ $taxid_taxkeys{$trans->[$j]->{'taxkey'}} }));
+      } elsif (($j != $notsplitindex) && !$trans->[$j]->{is_tax}) {
+        my %tax_info = $taxkeys->get_full_tax_info('transdate' => $trans->[$j]->{transdate});
 
         my %new_trans = ();
         map { $new_trans{$_} = $trans->[$notsplitindex]->{$_}; } keys %{ $trans->[$notsplitindex] };
 
-        my $tax_rate             = 1 + $taxes{ $taxid_taxkeys{$trans->[$j]->{'taxkey'}} };
-        $new_trans{'amount'}     = $form->round_amount(($trans->[$j]->{'amount'} * $tax_rate * -1), 2);
-        $new_trans{'umsatz'}     = abs($form->round_amount(($trans->[$j]->{'amount'} * $tax_rate), 2)) * $ml;
-        $trans->[$j]->{'umsatz'} = abs($form->round_amount(($trans->[$j]->{'amount'} * $tax_rate), 2)) * $ml;
+        my $tax_rate              = $tax_info{taxkeys}->{ $trans->[$j]->{'taxkey'} }->{taxrate};
+        $new_trans{'net_amount'}  = $trans->[$j]->{'amount'} * -1;
+        $new_trans{'tax_rate'}    = 1 + $tax_rate;
 
-        push @splits, [ \%new_trans, $trans->[$j] ];
-        push @{ $form->{DATEV} }, $splits[-1];
+        if (!$trans->[$j]->{'invoice'}) {
+          $new_trans{'amount'}      = $form->round_amount(-1 * ($trans->[$j]->{amount} + $trans->[$j]->{tax_amount}), 2);
+          $new_trans{'umsatz'}      = abs($new_trans{'amount'}) * $ml;
+          $trans->[$j]->{'umsatz'}  = $new_trans{'umsatz'};
+          $absumsatz               += -1 * $new_trans{'amount'};
+
+        } else {
+          my $unrounded             = $trans->[$j]->{'amount'} * (1 + $tax_rate) * -1 + $rounding_error;
+          my $rounded               = $form->round_amount($unrounded, 2);
+
+          $rounding_error           = $unrounded - $rounded;
+          $new_trans{'amount'}      = $rounded;
+          $new_trans{'umsatz'}      = abs($rounded) * $ml;
+          $trans->[$j]->{'umsatz'}  = $new_trans{umsatz};
+          $absumsatz               -= $rounded;
+        }
+
+        push @{ $form->{DATEV} }, [ \%new_trans, $trans->[$j] ];
+        push @taxed, $form->{DATEV}->[-1];
       }
     }
 
-    if (abs($absumsatz) > 0.01) {
+    my $idx        = 0;
+    my $correction = 0;
+    while ((abs($absumsatz) >= 0.01) && (abs($absumsatz) < 1.00)) {
+      if ($idx >= scalar @taxed) {
+        last if (!$correction);
+
+        $correction = 0;
+        $idx        = 0;
+      }
+
+      my $transaction = $taxed[$idx]->[0];
+
+      my $old_amount     = $transaction->{amount};
+      my $old_correction = $correction;
+      my @possible_diffs;
+
+      if (!$transaction->{diff}) {
+        @possible_diffs = (0.01, -0.01);
+      } else {
+        @possible_diffs = ($transaction->{diff});
+      }
+
+      foreach my $diff (@possible_diffs) {
+        my $net_amount = $form->round_amount(($transaction->{amount} + $diff) / $transaction->{tax_rate}, 2);
+        next if ($net_amount != $transaction->{net_amount});
+
+        $transaction->{diff}    = $diff;
+        $transaction->{amount} += $diff;
+        $transaction->{umsatz} += $diff;
+        $absumsatz             -= $diff;
+        $correction             = 1;
+
+        last;
+      }
+
+      $idx++;
+    }
+
+    $absumsatz = $form->round_amount($absumsatz, 2);
+    if (abs($absumsatz) >= (0.01 * (1 + scalar @taxed))) {
       push @errors, "Datev-Export fehlgeschlagen! Bei Transaktion $trans->[0]->{trans_id} ($absumsatz)\n";
+
+    } elsif (abs($absumsatz) >= 0.01) {
+      push @{ $form->{net_gross_differences} }, $absumsatz;
+      $form->{sum_net_gross_differences} += $absumsatz;
     }
   }
 
@@ -394,6 +560,7 @@ sub make_kne_data_header {
   $main::lxdebug->enter_sub();
 
   my ($myconfig, $form, $fromto, $start_jahr) = @_;
+  my ($primanota);
 
   my $jahr = $start_jahr;
   if (!$jahr) {
@@ -403,7 +570,7 @@ sub make_kne_data_header {
 
   #Header
   my $header  = "\x1D\x181";
-  $header    .= _fill($form->{datentraegernr}, 3, '0');
+  $header    .= _fill($form->{datentraegernr}, 3, ' ', 'left');
   $header    .= ($fromto) ? "11" : "13"; # Anwendungsnummer
   $header    .= _fill($form->{dfvkz}, 2, '0');
   $header    .= _fill($form->{beraternr}, 7, '0');
@@ -478,7 +645,7 @@ sub datetofour {
 
   my ($date, $six) = @_;
 
-  ($day, $month, $year) = split(/\./, $date);
+  my ($day, $month, $year) = split(/\./, $date);
 
   if ($day =~ /^0/) {
     $day = substr($day, 1, 1);
@@ -520,7 +687,7 @@ sub make_ed_versionset {
   if ($fromto ne "") {
     $versionset .= "0000" . substr($header, 28, 19);
   } else {
-    $datum = " " x 16;
+    my $datum = " " x 16;
     $versionset .= $datum . "001" . substr($header, 28, 4);
   }
 
@@ -560,10 +727,10 @@ sub kne_buchungsexport {
 
   my @filenames;
 
-  my $export_path = $main::userspath . "/";
+  my $export_path = _get_export_path() . "/";
   my $filename    = "ED00000";
   my $evfile      = "EV01";
-  my @ed_versionsets;
+  my @ed_versionset;
   my $fileno = 0;
 
   $form->header;
@@ -573,7 +740,7 @@ sub kne_buchungsexport {
   Buchungss&auml;tze verarbeitet:
 |;
 
-  ($fromto, $start_jahr) =
+  my ($fromto, $start_jahr) =
     &get_dates($form->{zeitraum}, $form->{monat},
                $form->{quartal},  $form->{transdatefrom},
                $form->{transdateto});
@@ -585,14 +752,14 @@ sub kne_buchungsexport {
     $filename++;
     my $ed_filename = $export_path . $filename;
     push(@filenames, $filename);
-    $header = &make_kne_data_header($myconfig, $form, $fromto, $start_jahr);
+    my $header = &make_kne_data_header($myconfig, $form, $fromto, $start_jahr);
 
     my $kne_file = SL::DATEV::KNEFile->new();
     $kne_file->add_block($header);
 
     while (scalar(@{ $form->{DATEV} }) > 0) {
-      $transaction = shift @{ $form->{DATEV} };
-      $trans_lines = scalar(@{$transaction});
+      my $transaction = shift @{ $form->{DATEV} };
+      my $trans_lines = scalar(@{$transaction});
       $counter++;
       if (($counter % 500) == 0) {
         print("$counter ");
@@ -609,13 +776,15 @@ sub kne_buchungsexport {
       my $datevautomatik = 0;
       my $taxkey         = 0;
       my $charttax       = 0;
-      my %umlaute = ('ä' => 'ae',
-                     'ö' => 'oe',
-                     'ü' => 'ue',
-                     'Ä' => 'Ae',
-                     'Ö' => 'Oe',
-                     'Ü' => 'Ue',
-                     'ß' => 'sz');
+      my ($haben, $soll);
+      my $iconv          = $::locale->{iconv_utf8};
+      my %umlaute = ($iconv->convert('ä') => 'ae',
+                     $iconv->convert('ö') => 'oe',
+                     $iconv->convert('ü') => 'ue',
+                     $iconv->convert('Ä') => 'Ae',
+                     $iconv->convert('Ö') => 'Oe',
+                     $iconv->convert('Ü') => 'Ue',
+                     $iconv->convert('ß') => 'sz');
       for (my $i = 0; $i < $trans_lines; $i++) {
         if ($trans_lines == 2) {
           if (abs($transaction->[$i]->{'amount'}) > abs($umsatz)) {
@@ -635,10 +804,7 @@ sub kne_buchungsexport {
         if ($transaction->[$i]->{'charttax'}) {
           $charttax = $transaction->[$i]->{'charttax'};
         }
-        if (   ($transaction->[$i]->{'id'} eq $transaction->[$i]->{'chart_id'})
-            && ($trans_lines > 2)) {
-          undef($transaction->[$i]);
-        } elsif ($transaction->[$i]->{'amount'} > 0) {
+        if ($transaction->[$i]->{'amount'} > 0) {
           $haben = $i;
         } else {
           $soll = $i;
@@ -646,7 +812,7 @@ sub kne_buchungsexport {
       }
 
       # Umwandlung von Umlauten und Sonderzeichen in erlaubte Zeichen bei Textfeldern
-      foreach $umlaut (keys(%umlaute)) {
+      foreach my $umlaut (keys(%umlaute)) {
         $transaction->[$haben]->{'invnumber'} =~ s/${umlaut}/${umlaute{$umlaut}}/g;
         $transaction->[$haben]->{'name'}      =~ s/${umlaut}/${umlaute{$umlaut}}/g;
       }
@@ -683,7 +849,8 @@ sub kne_buchungsexport {
 
       if (   ( $datevautomatik || $taxkey)
           && (!$datevautomatik || ($datevautomatik && ($charttax ne $taxkey)))) {
-        $kne_file->add_block("\x6C" . (!$datevautomatik ? $taxkey : "4"));
+#         $kne_file->add_block("\x6C" . (!$datevautomatik ? $taxkey : "4"));
+        $kne_file->add_block("\x6C${taxkey}");
       }
 
       $kne_file->add_block($gegenkonto);
@@ -709,13 +876,13 @@ sub kne_buchungsexport {
   }
 
   #Make EV Verwaltungsdatei
-  $ev_header = &make_ev_header($form, $fileno);
-  $ev_filename = $export_path . $evfile;
+  my $ev_header = &make_ev_header($form, $fileno);
+  my $ev_filename = $export_path . $evfile;
   push(@filenames, $evfile);
   open(EV, "> $ev_filename") or die "can't open outputfile: EV01\n";
   print(EV $ev_header);
 
-  foreach $file (@ed_versionset) {
+  foreach my $file (@ed_versionset) {
     print(EV $ed_versionset[$file]);
   }
   close(EV);
@@ -724,7 +891,7 @@ sub kne_buchungsexport {
   ###
   $main::lxdebug->leave_sub();
 
-  return @filenames;
+  return { 'download_token' => get_download_token_for_path($export_path), 'filenames' => \@filenames };
 }
 
 sub kne_stammdatenexport {
@@ -741,10 +908,10 @@ sub kne_stammdatenexport {
 
   my @filenames;
 
-  my $export_path = $main::userspath . "/";
+  my $export_path = _get_export_path() . "/";
   my $filename    = "ED00000";
   my $evfile      = "EV01";
-  my @ed_versionsets;
+  my @ed_versionset;
   my $fileno          = 1;
   my $i               = 0;
   my $blockcount      = 1;
@@ -755,9 +922,12 @@ sub kne_stammdatenexport {
   my $ed_filename = $export_path . $filename;
   push(@filenames, $filename);
   open(ED, "> $ed_filename") or die "can't open outputfile: $!\n";
-  $header = &make_kne_data_header($myconfig, $form, "");
+  my $header = &make_kne_data_header($myconfig, $form, "");
   $remaining_bytes -= length($header);
 
+  my $fuellzeichen;
+  our $fromto;
+
   # connect to database
   my $dbh = $form->dbconnect($myconfig);
 
@@ -781,7 +951,7 @@ sub kne_stammdatenexport {
   my $sth = $dbh->prepare($query);
   $sth->execute(@values) || $form->dberror($query);
 
-  while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
+  while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
     if (($remaining_bytes - length("t" . $ref->{'accno'})) <= 6) {
       $fuellzeichen = ($blockcount * 256 - length($buchungssatz . $header));
       $buchungssatz .= "\x00" x $fuellzeichen;
@@ -811,7 +981,7 @@ sub kne_stammdatenexport {
   print(ED $header);
   print(ED $buchungssatz);
   $fuellzeichen = 256 - (length($header . $buchungssatz . "z") % 256);
-  $dateiende = "\x00" x $fuellzeichen;
+  my $dateiende = "\x00" x $fuellzeichen;
   print(ED "z");
   print(ED $dateiende);
   close(ED);
@@ -820,13 +990,13 @@ sub kne_stammdatenexport {
   $ed_versionset[0] =
     &make_ed_versionset($header, $filename, $blockcount, $fromto);
 
-  $ev_header = &make_ev_header($form, $fileno);
-  $ev_filename = $export_path . $evfile;
+  my $ev_header = &make_ev_header($form, $fileno);
+  my $ev_filename = $export_path . $evfile;
   push(@filenames, $evfile);
   open(EV, "> $ev_filename") or die "can't open outputfile: EV01\n";
   print(EV $ev_header);
 
-  foreach $file (@ed_versionset) {
+  foreach my $file (@ed_versionset) {
     print(EV $ed_versionset[$file]);
   }
   close(EV);
@@ -839,7 +1009,7 @@ sub kne_stammdatenexport {
 
   $main::lxdebug->leave_sub();
 
-  return @filenames;
+  return { 'download_token' => get_download_token_for_path($export_path), 'filenames' => \@filenames };
 }
 
 1;