From 8bf25ad536af8805c1c59c6261b0cbae38903dc6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jan=20B=C3=BCren?= Date: Tue, 7 Nov 2017 10:24:15 +0100 Subject: [PATCH] =?utf8?q?Weitere=20=C3=9Cberarbeitung=20DATEV/CSV.pm?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Default-Werte falls definiert in datev_csv gesetzt. Pflichtfelder markiert Kern-Algorithmus klarer definiert (Hinweise von Sven) - Formatierung in Array pro Feldwert ausgelagert - Eingangs-Werte prüfen (input_check) - Logikstruktur klarer (kein unless nach if-bedingung) --- SL/DATEV.pm | 4 +- SL/DATEV/CSV.pm | 108 +++++++++++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/SL/DATEV.pm b/SL/DATEV.pm index 1b2f42672..bfcc59f19 100644 --- a/SL/DATEV.pm +++ b/SL/DATEV.pm @@ -375,8 +375,8 @@ sub csv_export { $self->generate_datev_data(from_to => $self->fromto); return if $self->errors; - - my $datev_ref, $self->warnings = SL::DATEV::CSV->new(datev_lines => $self->generate_datev_lines, + my $datev_ref; + ($datev_ref, $self->{warnings}) = SL::DATEV::CSV->new(datev_lines => $self->generate_datev_lines, from => $self->from, to => $self->to, locked => $self->locked, diff --git a/SL/DATEV/CSV.pm b/SL/DATEV/CSV.pm index 3ea826cf3..8c1537578 100644 --- a/SL/DATEV/CSV.pm +++ b/SL/DATEV/CSV.pm @@ -9,6 +9,7 @@ use SL::Helper::DateTime; use Carp; use DateTime; use Encode qw(decode); +use Scalar::Util qw(looks_like_number); my @kivitendo_to_datev = ( @@ -17,6 +18,9 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Transaction Value'), max_length => 13, type => 'Value', + required => 1, + input_check => sub { my ($input) = @_; return (looks_like_number($input) && length($input) <= 13) }, + formatter => sub { my ($input) = @_; return _format_amount($input) }, valid_check => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) }, }, { @@ -24,6 +28,10 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Debit/Credit Label'), max_length => 1, type => 'Text', + required => 1, + default => 'S', + input_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) }, + formatter => sub { my ($input) = @_; return $input eq 'H' ? 'H' : 'S' }, valid_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) }, }, { @@ -31,6 +39,8 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Transaction Value Currency Code'), max_length => 3, type => 'Text', + default => '', + input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/^[A-Z]{3}$/) }, valid_check => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) }, }, { @@ -38,11 +48,12 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Exchange Rate'), max_length => 11, type => 'Number', + default => '', valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) }, }, { kivi_datev_name => 'not yet implemented', - csv_header_name => t8('Base Transaction Value'), + sv_header_name => t8('Base Transaction Value'), }, { kivi_datev_name => 'not yet implemented', @@ -51,29 +62,35 @@ my @kivitendo_to_datev = ( { kivi_datev_name => 'konto', csv_header_name => t8('Account'), - max_length => 9, # May contain a maximum of 8 or 9 digits -> perldoc + max_length => 9, type => 'Account', - valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) }, + required => 1, + input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) }, }, { kivi_datev_name => 'gegenkonto', csv_header_name => t8('Contra Account'), - max_length => 9, # May contain a maximum of 8 or 9 digits -> perldoc + max_length => 9, type => 'Account', - valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) }, + required => 1, + input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) }, }, { kivi_datev_name => 'buchungsschluessel', csv_header_name => t8('Posting Key'), max_length => 2, type => 'Text', - valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) }, + default => '', + input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) }, }, { kivi_datev_name => 'datum', csv_header_name => t8('Invoice Date'), max_length => 4, type => 'Date', + required => 1, + input_check => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') }, + formatter => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') }, valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) }, }, { @@ -81,13 +98,16 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Invoice Field 1'), max_length => 12, type => 'Text', - valid_check => sub { my ($text) = @_; check_encoding($text); }, + default => '', + input_check => sub { my ($text) = @_; check_encoding($text); }, + formatter => sub { my ($input) = @_; return substr($input, 0, 12) }, }, { kivi_datev_name => 'not yet implemented', csv_header_name => t8('Invoice Field 2'), - max_length => 12, + max_length => 12, type => 'Text', + default => '', valid_check => sub { my ($check) = @_; return ($check =~ m/[ -~]{1,12}/) }, }, { @@ -100,7 +120,9 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Posting Text'), max_length => 60, type => 'Text', - valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); }, + default => '', + input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); }, + formatter => sub { my ($input) = @_; return substr($input, 0, 60) }, }, # pos 14 { kivi_datev_name => 'not yet implemented', @@ -177,14 +199,18 @@ my @kivitendo_to_datev = ( csv_header_name => t8('Cost Center'), max_length => 8, type => 'Text', - valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); }, + default => '', + input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); }, + formatter => sub { my ($input) = @_; return substr($input, 0, 8) }, }, # pos 37 { kivi_datev_name => 'kost2', csv_header_name => t8('Cost Center'), max_length => 8, type => 'Text', - valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); }, + default => '', + input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); }, + formatter => sub { my ($input) = @_; return substr($input, 0, 8) }, }, # pos 38 { kivi_datev_name => 'not yet implemented', @@ -198,9 +224,12 @@ my @kivitendo_to_datev = ( csv_header_name => t8('EU Member State and VAT ID Number'), max_length => 15, type => 'Text', + default => '', + input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/[A-Z]{2}\w{5,13}/) }, + formatter => sub { my ($input) = @_; return ($input =~ s/\s//g) }, valid_check => sub { my ($ustid) = @_; - return 1 unless defined($ustid); + return 1 if ('' eq $ustid); return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/); }, }, # pos 40 @@ -250,7 +279,7 @@ sub _generate_csv_header { # we need from and to in YYYYDDMM croak "Wrong format for from $params{from}" unless $params{from} =~ m/^[0-9]{8}$/; - croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/; + croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/; # who knows if we want locking and when our fiscal year starts # croak "Wrong state of locking" unless $params{locked} =~ m/^(0|1)$/; @@ -310,35 +339,38 @@ sub _csv_buchungsexport { foreach my $row (@{ $params{datev_lines} }) { my @current_datev_row; - # shorten strings - if ($row->{belegfeld1}) { - $row->{buchungsbes} = $row->{belegfeld1} if $row->{belegfeld1}; - $row->{belegfeld1} = substr($row->{belegfeld1}, 0, 12); - $row->{buchungsbes} = substr($row->{buchungsbes}, 0, 60); - } - - $row->{datum} = DateTime->from_kivitendo($row->{datum})->strftime('%d%m'); - - $row->{kost1} = substr($row->{kost1}, 0, 8) if $row->{kost1}; - $row->{kost2} = substr($row->{kost2}, 0, 8) if $row->{kost2}; - - # , as decimal point and trim for UstID - $row->{umsatz} = _format_amount($row->{umsatz}); - $row->{ustid} =~ s/\s//g if $row->{ustid}; # trim whitespace - + # 1. check all datev_lines and see if we have a defined value + # 2. if we don't have a defined value set a default if exists + # 3. otherwise die foreach my $column (@csv_columns) { - if (exists $column->{max_length} && $column->{kivi_datev_name} ne 'not yet implemented') { - # check max length - die "Incorrect length of field" if length($row->{ $column->{kivi_datev_name} }) > $column->{max_length}; + if ($column->{kivi_datev_name} eq 'not yet implemented') { + push @current_datev_row, ''; + next; + } + my $data = $row->{$column->{kivi_datev_name}}; + if (!defined $data) { + if (defined $column->{default}) { + $data = $column->{default}; + } else { + die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name}; + } + } + # checkpoint a: no undefined data. All strict checks now! + if (exists $column->{input_check}) { + die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'", + $data, $column->{kivi_datev_name}, $row->{umsatz}) + unless $column->{input_check}->($data); + } + # checkpoint b: we can safely format the input + if ($column->{formatter}) { + $data = $column->{formatter}->($data); } - if (exists $column->{valid_check} && $column->{kivi_datev_name} ne 'not yet implemented') { - # more checks, listed as user warnings + # checkpoint c: all soft checks now, will pop up as a user warning + if (exists $column->{valid_check} && !$column->{valid_check}->($data)) { 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} })); + " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz}); } - push @current_datev_row, $row->{ $column->{kivi_datev_name} }; + push @current_datev_row, $data; } push @array_of_datev, \@current_datev_row; } -- 2.20.1