kivi.Validator: Tests auf strikteres parse_amount geƤndert
[kivitendo-erp.git] / SL / DATEV.pm
index 6f222b5..b0e9936 100644 (file)
@@ -36,7 +36,6 @@ use SL::DATEV::CSV;
 use SL::DB;
 use SL::HTML::Util ();
 use SL::Locale::String qw(t8);
-use SL::Iconv qw(convert);
 
 use Data::Dumper;
 use DateTime;
@@ -216,6 +215,26 @@ sub trans_id {
   return $self->{trans_id};
 }
 
+sub warnings {
+  my $self = shift;
+
+  if (@_) {
+    $self->{warnings} = [@_];
+  } else {
+   return $self->{warnings};
+  }
+}
+
+sub use_pk {
+ my $self = shift;
+
+ if (@_) {
+   $self->{use_pk} = $_[0];
+ }
+
+ return $self->{use_pk};
+}
+
 sub accnofrom {
  my $self = shift;
 
@@ -431,11 +450,18 @@ sub generate_datev_data {
 
   my %all_taxchart_ids = selectall_as_map($form, $self->dbh, qq|SELECT DISTINCT chart_id, TRUE AS is_set FROM tax|, 'chart_id', 'is_set');
 
+  my $ar_accno = "c.accno";
+  my $ap_accno = "c.accno";
+  if ( $self->use_pk ) {
+    $ar_accno = "CASE WHEN ac.chart_link = 'AR' THEN ct.customernumber ELSE c.accno END as accno";
+    $ap_accno = "CASE WHEN ac.chart_link = 'AP' THEN ct.vendornumber   ELSE c.accno END as accno";
+  }
+
   my $query    =
     qq|SELECT ac.acc_trans_id, ac.transdate, ac.gldate, ac.trans_id,ar.id, ac.amount, ac.taxkey, ac.memo,
          ar.invnumber, ar.duedate, ar.amount as umsatz, ar.deliverydate, ar.itime::date,
          ct.name, ct.ustid, ct.customernumber AS vcnumber, ct.id AS customer_id, NULL AS vendor_id,
-         c.accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
+         $ar_accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
          ar.invoice,
          t.rate AS taxrate, t.taxdescription,
          'ar' as table,
@@ -464,7 +490,7 @@ sub generate_datev_data {
        SELECT ac.acc_trans_id, ac.transdate, ac.gldate, ac.trans_id,ap.id, ac.amount, ac.taxkey, ac.memo,
          ap.invnumber, ap.duedate, ap.amount as umsatz, ap.deliverydate, ap.itime::date,
          ct.name, ct.ustid, ct.vendornumber AS vcnumber, NULL AS customer_id, ct.id AS vendor_id,
-         c.accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
+         $ap_accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
          ap.invoice,
          t.rate AS taxrate, t.taxdescription,
          'ap' as table,
@@ -947,6 +973,7 @@ sub generate_datev_lines {
 
     if ($trans_lines >= 2) {
 
+      # Personenkontenerweiterung: accno has already been replaced if use_pk was set
       $datev_data{'gegenkonto'} = $transaction->[$haben]->{'accno'};
       $datev_data{'konto'}      = $transaction->[$soll]->{'accno'};
       if ($transaction->[$haben]->{'invnumber'} ne "") {
@@ -1366,13 +1393,10 @@ sub csv_buchungsexport {
   push @array_of_datev, \@csv_headers;
   push @array_of_datev, [ map { $_->{csv_header_name} } @csv_columns ];
 
+  my @warnings;
   foreach my $row ( @datev_lines ) {
     my @current_datev_row;
 
-    # format transformation
-    foreach (qw(belegfeld1 kost1 kost2)) {
-      $row->{$_} = SL::Iconv::convert("UTF-8", "CP1252", $row->{$_}) if $row->{$_};
-    }
     # shorten strings
     if ($row->{belegfeld1}) {
       $row->{buchungsbes} = $row->{belegfeld1} if $row->{belegfeld1};
@@ -1391,18 +1415,20 @@ sub csv_buchungsexport {
     foreach my $column (@csv_columns) {
       if (exists $column->{max_length} && $column->{kivi_datev_name} ne 'not yet implemented') {
         # check max length
-        die "Incorrect lenght of field" if length($row->{ $column->{kivi_datev_name} }) > $column->{max_length};
+        die "Incorrect length of field" if length($row->{ $column->{kivi_datev_name} }) > $column->{max_length};
       }
       if (exists $column->{valid_check} && $column->{kivi_datev_name} ne 'not yet implemented') {
-        # more checks
-        die "Not a valid value: '$row->{ $column->{kivi_datev_name} }'" .
-            " for '$column->{kivi_datev_name}' with amount '$row->{umsatz}'"
-              unless ($column->{valid_check}->($row->{ $column->{kivi_datev_name} }));
+        # more checks, listed as user warnings
+        push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
+                            " with amount '#3'",$row->{ $column->{kivi_datev_name} },
+                            $column->{kivi_datev_name},$row->{umsatz})
+          unless ($column->{valid_check}->($row->{ $column->{kivi_datev_name} }));
       }
       push @current_datev_row, $row->{ $column->{kivi_datev_name} };
     }
     push @array_of_datev, \@current_datev_row;
   }
+  $self->warnings(@warnings) if @warnings;
   return \@array_of_datev;
 }
 
@@ -1416,7 +1442,6 @@ sub _csv_buchungsexport_to_file {
   my $filename = "EXTF_DATEV_kivitendo" . $self->from->ymd() . '-' . $self->to->ymd() . ".csv";
   my @data = \$params{data};
 
-  # EXTF_Buchungsstapel.csv: ISO-8859 text, with very long lines, with CRLF line terminators
   my $csv = Text::CSV_XS->new({
               binary       => 1,
               sep_char     => ";",
@@ -1429,12 +1454,28 @@ sub _csv_buchungsexport_to_file {
     $csv->quote_empty(1);
   }
 
-  my $csv_file = IO::File->new($self->export_path . '/' . $filename, '>:encoding(iso-8859-1)') or die "Can't open: $!";
+  my $csv_file = IO::File->new($self->export_path . '/' . $filename, '>:encoding(cp1252)') or die "Can't open: $!";
   $csv->print($csv_file, $_) for @{ $params{data} };
   $csv_file->close;
 
   return { download_token => $self->download_token, filenames => $params{filename} };
 }
+
+sub check_vcnumbers_are_valid_pk_numbers {
+  my ($self) = @_;
+
+  my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
+  my $pk_length = $length_of_accounts + 1;
+  my $query = <<"SQL";
+   SELECT customernumber AS vcnumber FROM customer WHERE customernumber !~ '^[[:digit:]]{$pk_length}\$'
+   UNION
+   SELECT vendornumber   AS vcnumber FROM vendor   WHERE vendornumber   !~ '^[[:digit:]]{$pk_length}\$'
+   LIMIT 1;
+SQL
+  my ($has_non_pk_accounts)  = selectrow_query($::form, SL::DB->client->dbh, $query);
+  return defined $has_non_pk_accounts ? 0 : 1;
+}
+
 sub DESTROY {
   clean_temporary_directories();
 }
@@ -1660,6 +1701,19 @@ Generates a CSV-file with the same encodings as defined in DATEV Format CSV 2015
 
 Usage: _csv_buchungsexport_to_file($self, data => $self->csv_buchungsexport);
 
+=item check_vcnumbers_are_valid_pk_numbers
+
+Returns 1 if all vcnumbers are suitable for the DATEV export, 0 if not.
+
+Finds the default length of charts (e.g. 4), adds 1 for the pk chart length
+(e.g. 5), and checks the database for any customers or vendors whose customer-
+or vendornumber doesn't consist of only numbers with exactly that length. E.g.
+for a chart length of four "10001" would be ok, but not "10001b" or "1000".
+
+All vcnumbers are checked, obsolete customers or vendors aren't exempt.
+
+There is also no check for the typical customer range 10000-69999 and the
+typical vendor range 70000-99999.
 
 =back