From 7130c4c1a0cd348455b9b046b58edd6b45ec47a8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jan=20B=C3=BCren?= Date: Thu, 9 Nov 2017 09:54:46 +0100 Subject: [PATCH] =?utf8?q?DATEV:=20Saubere=20Objektmethoden=20f=C3=BCr=20C?= =?utf8?q?SV.pm=20implementiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit PODs ergänzt. Hintergrund: Sehr klare Ideen von Sven implementiert, sprengt den Rahmen der Commit-Message, Details siehe Doku in redmine http://redmine.kivitendo-premium.de/documents/18 --- SL/DATEV.pm | 45 ++++------------- SL/DATEV/CSV.pm | 130 +++++++++++++++++++++++++++--------------------- 2 files changed, 85 insertions(+), 90 deletions(-) diff --git a/SL/DATEV.pm b/SL/DATEV.pm index 81327e40e..dc0c38355 100644 --- a/SL/DATEV.pm +++ b/SL/DATEV.pm @@ -375,12 +375,14 @@ sub csv_export { $self->generate_datev_data(from_to => $self->fromto); return if $self->errors; - 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, - ); + + my $datev_csv = SL::DATEV::CSV->new( + datev_lines => $self->generate_datev_lines, + from => $self->from, + to => $self->to, + locked => $self->locked, + ); + my $filename = "EXTF_DATEV_kivitendo" . $self->from->ymd() . '-' . $self->to->ymd() . ".csv"; @@ -392,8 +394,10 @@ sub csv_export { }) or die "Cannot use CSV: ".Text::CSV_XS->error_diag(); my $csv_file = IO::File->new($self->export_path . '/' . $filename, '>:encoding(cp1252)') or die "Can't open: $!"; - $csv->print($csv_file, $_) for @{ $datev_ref }; + $csv->print($csv_file, $_) for @{ $datev_csv->header }; + $csv->print($csv_file, $_) for @{ $datev_csv->lines }; $csv_file->close; + $self->{warnings} = $datev_csv->warnings; return { download_token => $self->download_token, filenames => $filename }; @@ -1634,33 +1638,6 @@ Example: # }; -=item csv_buchungsexport - -Generates the CSV-Format data for the CSV DATEV export and returns -an 2-dimensional array as an array_ref. - -Requires $self->fromto for a valid DATEV header. - -Furthermore we assume that the first day of the fiscal year is -the first of January and we cannot guarantee that our data in kivitendo -is locked, that means a booking cannot be modified after a defined (vat tax) -period. -Some validity checks (max_length and regex) will be done if the -data structure contains them and the field is defined. - -To add or alter the structure of the data take a look at SL::DATEV::CSV.pm - -=item _csv_buchungsexport_to_file - -Generates one downloadable csv file wrapped in a zip archive. -Basically this method is just a thin wrapper for TEXT::CSV_XS.pm - -Generates a CSV-file with the same encodings as defined in DATEV Format CSV 2015: - $ file - $ EXTF_Buchungsstapel.csv: ISO-8859 text, with very long lines, with CRLF line terminators - -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. diff --git a/SL/DATEV/CSV.pm b/SL/DATEV/CSV.pm index c11a9b598..560edd5a1 100644 --- a/SL/DATEV/CSV.pm +++ b/SL/DATEV/CSV.pm @@ -11,6 +11,9 @@ use Carp; use Encode qw(decode); use Scalar::Util qw(looks_like_number); +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(datev_lines from to locked warnings) ], +); my @kivitendo_to_datev = ( { @@ -239,19 +242,12 @@ sub new { my $class = shift; my %data = @_; - my $obj = bless {}, $class; - croak(t8('We need a valid from date')) unless (ref $data{from} eq 'DateTime'); croak(t8('We need a valid to date')) unless (ref $data{to} eq 'DateTime'); croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY'); - # TODO no params here, better class variables/values - return _csv_buchungsexport(from => $data{from}, - to => $data{to}, - datev_lines => $data{datev_lines}, - locked => $data{locked}, - ); - + my $obj = bless {}, $class; + $obj->$_($data{$_}) for keys %data; $obj; } @@ -274,20 +270,13 @@ sub _kivitendo_to_datev { return @kivitendo_to_datev; } -sub _generate_csv_header { - my %params = @_; - - # 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}$/; - - # who knows if we want locking and when our fiscal year starts - # croak "Wrong state of locking" unless $params{locked} =~ m/^(0|1)$/; - my $locked = defined($params{locked}) ? 1 : 0; - croak "No startdate of fiscal year" unless $params{first_day_of_fiscal_year} =~ m/^[0-9]{8}$/; +sub header { + my ($self) = @_; + my @header; # we can safely set these defaults + # TODO use Helper::DateTime and get lenght_of_accounts from DATEV.pm my $today = DateTime->now(time_zone => "local"); my $created_on = $today->ymd('') . $today->hms('') . '000'; my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4; @@ -308,36 +297,28 @@ sub _generate_csv_header { $meta_datev{$k} = substr $datev->{$k}, 0, $v; } - my @header = ( + my @header_row_1 = ( "EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki", "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr}, - $params{first_day_of_fiscal_year}, $length_of_accounts, - $params{from}, $params{to}, "", "", 1, "", $locked, + $self->first_day_of_fiscal_year->ymd(''), $length_of_accounts, + $self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked, $default_curr, "", "", "","" ); + push @header, [ @header_row_1 ]; + + # second header row, just the column names + push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ]; - return @header; + return \@header; } -sub _csv_buchungsexport { - my %params = @_; +sub lines { + my ($self) = @_; + my (@array_of_datev, @warnings); my @csv_columns = _kivitendo_to_datev(); - my @csv_headers = _generate_csv_header( - from => $params{from}->ymd(''), - to => $params{to}->ymd(''), - first_day_of_fiscal_year => $params{to}->year . '0101', - locked => $params{locked} - ); - - my @array_of_datev; - # 2 Headers - push @array_of_datev, \@csv_headers; - push @array_of_datev, [ map { $_->{csv_header_name} } @csv_columns ]; - - my @warnings; - foreach my $row (@{ $params{datev_lines} }) { + foreach my $row (@{ $self->datev_lines }) { my @current_datev_row; # 1. check all datev_lines and see if we have a defined value @@ -357,10 +338,9 @@ sub _csv_buchungsexport { } } # checkpoint a: no undefined data. All strict checks now! - if (exists $column->{input_check}) { + if (exists $column->{input_check} && !$column->{input_check}->($data)) { 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); + $data, $column->{kivi_datev_name}, $row->{umsatz}); } # checkpoint b: we can safely format the input if ($column->{formatter}) { @@ -375,13 +355,20 @@ sub _csv_buchungsexport { } push @array_of_datev, \@current_datev_row; } - return (\@array_of_datev, \@warnings); + $self->warnings(\@warnings); + return \@array_of_datev; } +# helper + sub _format_amount { $::form->format_amount({ numberformat => '1000,00' }, @_); } +sub first_day_of_fiscal_year { + $_[0]->to->clone->truncate(to => 'year'); +} + 1; __END__ @@ -407,11 +394,25 @@ SL::DATEV::CSV - kivitendo DATEV CSV Specification ); $datev->generate_datev_data; - my $datev_ref = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines, + my $datev_csv = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines, from => $datev->from, to => $datev->to, locked => $datev->locked, ); + $datev_csv->header; # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array + $datev_csv->lines; # returns an array_ref of rows of array_refs soll uns die ein Arrayref von Zeilen zurückgeben, die jeweils Arrayrefs sind + $datev_csv->warnings; # returns warnings + + + # The above object methods can be directly chained to a CSV export function, like this: + my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!"; + $csv->print($csv_file, $_) for @{ $datev_csv->header }; + $csv->print($csv_file, $_) for @{ $datev_csv->lines }; + $csv_file->close; + $self->{warnings} = $datev_csv->warnings; + + + =head1 DESCRIPTION @@ -490,8 +491,7 @@ Checks mandantory params as described in section synopsis. Helper function, returns true if a string is not empty and cp1252 encoded For example some arabic utf-8 like ݐ will return false -=item generate_csv_header(from => 'YYYYDDMM', to => 'YYYYDDMM', locked => 0, - first_day_of_fiscal_year => 'YYYYDDMM') +=item header Mostly all other header information are constants or metadata loaded from SL::DB::Datev.pm. @@ -499,13 +499,6 @@ from SL::DB::Datev.pm. Returns the first two entries for the header (see above: File Structure) as an array. -All params are mandatory: -C, C -and C have to be in YYYYDDMM date string -format. -Furthermore C is a perlish boolean. - - =item kivitendo_to_datev Returns the data structure C<@datev_data> as an array @@ -516,7 +509,11 @@ Lightweight wrapper for form->format_amount. Expects a number in kivitendo database format and returns the same number in DATEV format. -=item _csv_buchungsexport +=item first_day_of_fiscal_year + +Takes a look at $self->to to determine the first day of the fiscal year. + +=item lines Generates the CSV-Format data for the CSV DATEV export and returns an 2-dimensional array as an array_ref. @@ -533,5 +530,26 @@ data structure contains them and the field is defined. To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure. - =back + +=head1 TODO CAVEAT + + +Currently no effort has be done that _kivitenod_to_datev is only intializied once: +Therefore the second call may generate integrity faults: + + my $datev_csv_1 = SL::DATEV::CSV->new(...)->lines; + my $datev_csv_2 = SL::DATEV::CSV->new(...)->lines; + +Secondly one can circumevent the check of the warnings.quite easily, +becaus warnings are generated after the call to lines: + + # WRONG usage + die if @{ $datev_csv->warnings }; + somethin_with($datev_csv->lines); + + # safe usage + my $lines = $datev_csv->lines; + die if @{ $datev_csv->warnings }; + somethin_with($lines); + -- 2.20.1