Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
[kivitendo-erp.git] / SL / DATEV.pm
index 4079439..bfb48d0 100644 (file)
@@ -26,7 +26,8 @@
 
 package DATEV;
 
-use List::Util qw(max);
+use utf8;
+use strict;
 
 use SL::DBUtils;
 use SL::DATEV::KNEFile;
@@ -34,6 +35,7 @@ use SL::Taxkeys;
 
 use Data::Dumper;
 use File::Path;
+use List::Util qw(max);
 use Time::HiRes qw(gettimeofday);
 
 sub _get_export_path {
@@ -56,7 +58,7 @@ sub get_path_for_download_token {
   my $path;
 
   if ($token =~ m|^(\d+)-(\d+)-(\d+)$|) {
-    $path = "${main::userspath}/datev-export-${1}-${2}-${3}";
+    $path = $::lx_office_conf{paths}->{userspath} . "/datev-export-${1}-${2}-${3}";
   }
 
   $main::lxdebug->leave_sub();
@@ -82,7 +84,7 @@ sub get_download_token_for_path {
 sub clean_temporary_directories {
   $main::lxdebug->enter_sub();
 
-  foreach my $path (glob "${main::userspath}/datev-export-*") {
+  foreach my $path (glob($::lx_office_conf{paths}->{userspath} . "/datev-export-*")) {
     next unless (-d $path);
 
     my $mtime = (stat($path))[9];
@@ -125,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;
 
@@ -146,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
@@ -159,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;
 
@@ -201,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 >= ";
 
@@ -322,8 +327,12 @@ 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 $taxkeys  = Taxkeys->new();
@@ -377,9 +386,10 @@ sub _get_transactions {
        ORDER BY trans_id, acc_trans_id|;
 
   my $sth = prepare_execute_query($form, $dbh, $query);
+  $form->{DATEV} = [];
 
   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 ");
@@ -389,14 +399,15 @@ sub _get_transactions {
 
     my $count    = $ref->{amount};
     my $firstrun = 1;
+    my $subcent  = abs($count) < 0.02;
 
-    while (abs($count) > 0.01 || $firstrun) {
-      my $ref2 = $sth->fetchrow_hashref(NAME_lc);
+    while (abs($count) > 0.01 || $firstrun || ($subcent && abs($count) > 0.005)) {
+      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");
-        exit 1;
+        ::end_of_request();
       }
 
       push @{ $trans }, $ref2;
@@ -415,10 +426,10 @@ sub _get_transactions {
         $ref->{is_tax} = 1;
       }
 
-      if (   !$ref->{invoice}
-          &&  $ref->{is_tax}
-          && !($prev_ref->{is_tax})
-          &&  (_sign($ref->{amount}) == _sign($prev_ref->{amount}))) {
+      if (   !$ref->{invoice}   # we have a non-invoice booking (=gl)
+          &&  $ref->{is_tax}    # that has "is_tax" set
+          && !($prev_ref->{is_tax})  # previous line wasn't is_tax
+          &&  (_sign($ref->{amount}) == _sign($prev_ref->{amount}))) {  # and sign same as previous sign
         $trans->[$i - 1]->{tax_amount} = $ref->{amount};
       }
     }
@@ -431,7 +442,10 @@ sub _get_transactions {
     }
 
     for my $j (0 .. (scalar(@{$trans}) - 1)) {
-      if (abs($trans->[$j]->{'amount'}) > abs($absumsatz)) {
+      # for gl-bookings no split is allowed and there is no AR/AP account, so we always use the maximum value as a reference
+      # for ap/ar bookings we can always search for AR/AP in link and use that
+      if ( ( not $trans->[$j]->{'invoice'} and abs($trans->[$j]->{'amount'}) > abs($absumsatz) )
+         or ($trans->[$j]->{'invoice'} and ($trans->[$j]->{'link'} eq 'AR' or $trans->[$j]->{'link'} eq 'AP'))) {
         $absumsatz     = $trans->[$j]->{'amount'};
         $notsplitindex = $j;
       }
@@ -439,6 +453,7 @@ sub _get_transactions {
 
     my $ml             = ($trans->[0]->{'umsatz'} > 0) ? 1 : -1;
     my $rounding_error = 0;
+    my @taxed;
 
     for my $j (0 .. (scalar(@{$trans}) - 1)) {
       if (   ($j != $notsplitindex)
@@ -475,22 +490,24 @@ sub _get_transactions {
           $absumsatz               += -1 * $new_trans{'amount'};
 
         } else {
-          my $unrounded             = $trans->[$j]->{'amount'} * (1 + $tax_rate) * -1; # + $rounding_error;
+          my $unrounded             = $trans->[$j]->{'amount'} * (1 + $tax_rate) * -1 + $rounding_error;
           my $rounded               = $form->round_amount($unrounded, 2);
-          $rounding_error          += $unrounded - $rounded;
+
+          $rounding_error           = $unrounded - $rounded;
           $new_trans{'amount'}      = $rounded;
-          $new_trans{'umsatz'}      = abs($form->round_amount(($trans->[$j]->{'amount'} * (1 + $tax_rate)), 2)) * $ml;
-          $trans->[$j]->{'umsatz'}  = abs($form->round_amount(($trans->[$j]->{'amount'} * (1 + $tax_rate)), 2)) * $ml;
-          $absumsatz               += $form->round_amount($trans->[$j]->{'amount'} + $trans->[$j]->{'amount'} * $tax_rate, 2);
+          $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];
       }
     }
 
     my $idx        = 0;
     my $correction = 0;
-    while (abs($absumsatz) >= 0.01) {
+    while ((abs($absumsatz) >= 0.01) && (abs($absumsatz) < 1.00)) {
       if ($idx >= scalar @taxed) {
         last if (!$correction);
 
@@ -526,8 +543,13 @@ sub _get_transactions {
       $idx++;
     }
 
-    if (abs($absumsatz) >= 0.01) {
-      push @errors, "Datev-Export fehlgeschlagen! Bei Transaktion $trans->[0]->{trans_id} ($absumsatz, Rundungsfehler $rounding_error)\n";
+    $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;
     }
   }
 
@@ -542,6 +564,7 @@ sub make_kne_data_header {
   $main::lxdebug->enter_sub();
 
   my ($myconfig, $form, $fromto, $start_jahr) = @_;
+  my ($primanota);
 
   my $jahr = $start_jahr;
   if (!$jahr) {
@@ -626,7 +649,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);
@@ -668,7 +691,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);
   }
 
@@ -711,7 +734,7 @@ sub kne_buchungsexport {
   my $export_path = _get_export_path() . "/";
   my $filename    = "ED00000";
   my $evfile      = "EV01";
-  my @ed_versionsets;
+  my @ed_versionset;
   my $fileno = 0;
 
   $form->header;
@@ -721,7 +744,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});
@@ -733,14 +756,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 ");
@@ -757,14 +780,15 @@ sub kne_buchungsexport {
       my $datevautomatik = 0;
       my $taxkey         = 0;
       my $charttax       = 0;
-      my $iconv          = $main::locale->{iconv_iso8859};
-      my %umlaute = ($iconv->convert('ä') => 'ae',
-                     $iconv->convert('ö') => 'oe',
-                     $iconv->convert('ü') => 'ue',
-                     $iconv->convert('Ä') => 'Ae',
-                     $iconv->convert('Ö') => 'Oe',
-                     $iconv->convert('Ü') => 'Ue',
-                     $iconv->convert('ß') => '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)) {
@@ -792,7 +816,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;
       }
@@ -827,6 +851,16 @@ sub kne_buchungsexport {
       $umsatzsumme += $umsatz;
       $kne_file->add_block("+" . $umsatz);
 
+      # Dies ist die einzige Stelle die datevautomatik auswertet. Was soll gesagt werden?
+      # Im Prinzip hat jeder acc_trans Eintrag einen Steuerschlüssel, außer, bei gewissen Fällen
+      # wie: Kreditorenbuchung mit negativen Vorzeichen, SEPA-Export oder Rechnungen die per
+      # Skript angelegt werden.
+      # Also falls ein Steuerschlüssel da ist und NICHT datevautomatik diesen Block hinzufügen.
+      # Oder aber datevautomatik ist WAHR, aber der Steuerschlüssel in der acc_trans weicht
+      # von dem in der Chart ab: Also wahrscheinlich Programmfehler (NULL übergeben, statt
+      # DATEV-Steuerschlüssel) oder der Steuerschlüssel des Kontos weicht WIRKLICH von dem Eintrag in der
+      # acc_trans ab. Gibt es für diesen Fall eine plausiblen Grund?
+      #
       if (   ( $datevautomatik || $taxkey)
           && (!$datevautomatik || ($datevautomatik && ($charttax ne $taxkey)))) {
 #         $kne_file->add_block("\x6C" . (!$datevautomatik ? $taxkey : "4"));
@@ -847,7 +881,7 @@ sub kne_buchungsexport {
     $kne_file->add_block($mandantenendsumme);
     $kne_file->flush();
 
-    open(ED, "> $ed_filename") or die "can't open outputfile: $!\n";
+    open(ED, ">", $ed_filename) or die "can't open outputfile: $!\n";
     print(ED $kne_file->get_data());
     close(ED);
 
@@ -856,13 +890,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";
+  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);
@@ -891,7 +925,7 @@ sub kne_stammdatenexport {
   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;
@@ -901,10 +935,13 @@ sub kne_stammdatenexport {
   $filename++;
   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, "");
+  open(ED, ">", $ed_filename) or die "can't open outputfile: $!\n";
+  my $header = &make_kne_data_header($myconfig, $form, "");
   $remaining_bytes -= length($header);
 
+  my $fuellzeichen;
+  our $fromto;
+
   # connect to database
   my $dbh = $form->dbconnect($myconfig);
 
@@ -918,7 +955,7 @@ sub kne_stammdatenexport {
     push @values, $form->{accnoto};
   }
 
-  my $where_str = ' WHERE ' . join(' AND ', map { "($_)" } @where) if (scalar @where);
+  my $where_str = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : '';
 
   my $query     = qq|SELECT c.accno, c.description
                      FROM chart c
@@ -928,7 +965,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;
@@ -958,7 +995,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);
@@ -967,13 +1004,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";
+  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);