X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FDATEV.pm;h=c39ba34074f21e2107f4e205e7456e0c447de92c;hb=47ee7483575f1029a56fe3ac10b1680d91bc1601;hp=0ab044af0e0e00c574c386594f37891d33995a09;hpb=f7b15d4334848fad1bcd0b569eff5139f9a6ba4d;p=kivitendo-erp.git diff --git a/SL/DATEV.pm b/SL/DATEV.pm index 0ab044af0..c39ba3407 100644 --- a/SL/DATEV.pm +++ b/SL/DATEV.pm @@ -56,10 +56,11 @@ use Time::HiRes qw(gettimeofday); DATEV_FORMAT_KNE => $i++, DATEV_FORMAT_OBE => $i++, + DATEV_FORMAT_CSV => $i++, }; } -my @export_constants = qw(DATEV_ET_BUCHUNGEN DATEV_ET_STAMM DATEV_ET_CSV DATEV_FORMAT_KNE DATEV_FORMAT_OBE); +my @export_constants = qw(DATEV_ET_BUCHUNGEN DATEV_ET_STAMM DATEV_ET_CSV DATEV_FORMAT_KNE DATEV_FORMAT_OBE DATEV_FORMAT_CSV); our @EXPORT_OK = (@export_constants); our %EXPORT_TAGS = (CONSTANTS => [ @export_constants ]); @@ -311,7 +312,9 @@ sub export { die 'no format set!' unless $self->has_format; - if ($self->format == DATEV_FORMAT_KNE) { + if ($self->format == DATEV_FORMAT_CSV) { + $result = $self->csv_export; + } elsif ($self->format == DATEV_FORMAT_KNE) { $result = $self->kne_export; } elsif ($self->format == DATEV_FORMAT_OBE) { $result = $self->obe_export; @@ -341,6 +344,10 @@ sub kne_export { return $result; } +sub csv_export { + die 'not yet implemented'; +} + sub obe_export { die 'not yet implemented'; } @@ -357,16 +364,24 @@ sub _sign { $_[0] <=> 0; } -sub _get_transactions { +sub generate_datev_data { $main::lxdebug->enter_sub(); my ($self, %params) = @_; - my $fromto = $params{from_to}; + my $fromto = $params{from_to} // ''; my $progress_callback = $params{progress_callback} || sub {}; my $form = $main::form; my $trans_id_filter = ''; + my $ar_department_id_filter = ''; + my $ap_department_id_filter = ''; + my $gl_department_id_filter = ''; + if ( $form->{department_id} ) { + $ar_department_id_filter = " AND ar.department_id = ? "; + $ap_department_id_filter = " AND ap.department_id = ? "; + $gl_department_id_filter = " AND gl.department_id = ? "; + } if ( $self->{trans_id} ) { # ignore dates when trans_id is passed so that the entire transaction is @@ -393,6 +408,7 @@ sub _get_transactions { t.rate AS taxrate, t.taxdescription, 'ar' as table, tc.accno AS tax_accno, tc.description AS tax_accname, + ar.department_id, ar.notes FROM acc_trans ac LEFT JOIN ar ON (ac.trans_id = ar.id) @@ -403,6 +419,7 @@ sub _get_transactions { WHERE (ar.id IS NOT NULL) AND $fromto $trans_id_filter + $ar_department_id_filter $filter UNION ALL @@ -415,6 +432,7 @@ sub _get_transactions { t.rate AS taxrate, t.taxdescription, 'ap' as table, tc.accno AS tax_accno, tc.description AS tax_accname, + ap.department_id, ap.notes FROM acc_trans ac LEFT JOIN ap ON (ac.trans_id = ap.id) @@ -425,6 +443,7 @@ sub _get_transactions { WHERE (ap.id IS NOT NULL) AND $fromto $trans_id_filter + $ap_department_id_filter $filter UNION ALL @@ -437,6 +456,7 @@ sub _get_transactions { t.rate AS taxrate, t.taxdescription, 'gl' as table, tc.accno AS tax_accno, tc.description AS tax_accname, + gl.department_id, gl.notes FROM acc_trans ac LEFT JOIN gl ON (ac.trans_id = gl.id) @@ -446,11 +466,17 @@ sub _get_transactions { WHERE (gl.id IS NOT NULL) AND $fromto $trans_id_filter + $gl_department_id_filter $filter ORDER BY trans_id, acc_trans_id|; - my $sth = prepare_execute_query($form, $self->dbh, $query); + my @query_args; + if ( $form->{department_id} ) { + push(@query_args, ($form->{department_id}) x 3); + } + + my $sth = prepare_execute_query($form, $self->dbh, $query, @query_args); $self->{DATEV} = []; my $counter = 0; @@ -814,6 +840,114 @@ sub make_ev_header { return $ev_header; } +sub generate_datev_lines { + my ($self) = @_; + + my @datev_lines = (); + + foreach my $transaction ( @{ $self->{DATEV} } ) { + + # each $transaction entry contains data from several acc_trans entries + # belonging to the same trans_id + + my %datev_data = (); # data for one transaction + my $trans_lines = scalar(@{$transaction}); + + my $umsatz = 0; + my $gegenkonto = ""; + my $konto = ""; + my $belegfeld1 = ""; + my $datum = ""; + my $waehrung = ""; + my $buchungstext = ""; + my $belegfeld2 = ""; + my $datevautomatik = 0; + my $taxkey = 0; + my $charttax = 0; + my $ustid =""; + my ($haben, $soll); + for (my $i = 0; $i < $trans_lines; $i++) { + if ($trans_lines == 2) { + if (abs($transaction->[$i]->{'amount'}) > abs($umsatz)) { + $umsatz = $transaction->[$i]->{'amount'}; + } + } else { + if (abs($transaction->[$i]->{'umsatz'}) > abs($umsatz)) { + $umsatz = $transaction->[$i]->{'umsatz'}; + } + } + if ($transaction->[$i]->{'datevautomatik'}) { + $datevautomatik = 1; + } + if ($transaction->[$i]->{'taxkey'}) { + $taxkey = $transaction->[$i]->{'taxkey'}; + } + if ($transaction->[$i]->{'charttax'}) { + $charttax = $transaction->[$i]->{'charttax'}; + } + if ($transaction->[$i]->{'amount'} > 0) { + $haben = $i; + } else { + $soll = $i; + } + } + + if ($trans_lines >= 2) { + + $datev_data{'gegenkonto'} = $transaction->[$haben]->{'accno'}; + $datev_data{'konto'} = $transaction->[$soll]->{'accno'}; + if ($transaction->[$haben]->{'invnumber'} ne "") { + $datev_data{belegfeld1} = $transaction->[$haben]->{'invnumber'}; + } + $datev_data{datum} = $transaction->[$haben]->{'transdate'}; + $datev_data{waehrung} = 'EUR'; + + if ($transaction->[$haben]->{'name'} ne "") { + $datev_data{buchungstext} = $transaction->[$haben]->{'name'}; + } + if (($transaction->[$haben]->{'ustid'} // '') ne "") { + $datev_data{ustid} = $transaction->[$haben]->{'ustid'}; + } + if (($transaction->[$haben]->{'duedate'} // '') ne "") { + $datev_data{belegfeld2} = $transaction->[$haben]->{'duedate'}; + } + } + + $datev_data{umsatz} = abs($umsatz); # sales invoices without tax have a different sign??? + + # 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? + # + + # only set buchungsschluessel if the following conditions are met: + if ( ( $datevautomatik || $taxkey) + && (!$datevautomatik || ($datevautomatik && ($charttax ne $taxkey)))) { + # $datev_data{buchungsschluessel} = !$datevautomatik ? $taxkey : "4"; + $datev_data{buchungsschluessel} = $taxkey; + } + + push(@datev_lines, \%datev_data); + } + + # example of modifying export data: + # foreach my $datev_line ( @datev_lines ) { + # if ( $datev_line{"konto"} eq '1234' ) { + # $datev_line{"konto"} = '9999'; + # } + # } + # + + return \@datev_lines; +} + + sub kne_buchungsexport { $main::lxdebug->enter_sub(); @@ -823,160 +957,92 @@ sub kne_buchungsexport { my @filenames; - my $filename = "ED00000"; + my $filename = "ED00001"; my $evfile = "EV01"; my @ed_versionset; - my $fileno = 0; + my $fileno = 1; + my $ed_filename = $self->export_path . $filename; my $fromto = $self->fromto; - $self->_get_transactions(from_to => $fromto); - + $self->generate_datev_data(from_to => $self->fromto); # fetches data from db, transforms data and fills $self->{DATEV} return if $self->errors; - my $counter = 0; + my @datev_lines = @{ $self->generate_datev_lines }; - while (scalar(@{ $self->{DATEV} || [] })) { - my $umsatzsumme = 0; - $filename++; - my $ed_filename = $self->export_path . $filename; - push(@filenames, $filename); - my $header = $self->make_kne_data_header($form); - - my $kne_file = SL::DATEV::KNEFile->new(); - $kne_file->add_block($header); - - while (scalar(@{ $self->{DATEV} }) > 0) { - my $transaction = shift @{ $self->{DATEV} }; - my $trans_lines = scalar(@{$transaction}); - $counter++; - - my $umsatz = 0; - my $gegenkonto = ""; - my $konto = ""; - my $belegfeld1 = ""; - my $datum = ""; - my $waehrung = ""; - my $buchungstext = ""; - my $belegfeld2 = ""; - my $datevautomatik = 0; - my $taxkey = 0; - my $charttax = 0; - my $ustid =""; - 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)) { - $umsatz = $transaction->[$i]->{'amount'}; - } - } else { - if (abs($transaction->[$i]->{'umsatz'}) > abs($umsatz)) { - $umsatz = $transaction->[$i]->{'umsatz'}; - } - } - if ($transaction->[$i]->{'datevautomatik'}) { - $datevautomatik = 1; - } - if ($transaction->[$i]->{'taxkey'}) { - $taxkey = $transaction->[$i]->{'taxkey'}; - } - if ($transaction->[$i]->{'charttax'}) { - $charttax = $transaction->[$i]->{'charttax'}; - } - if ($transaction->[$i]->{'amount'} > 0) { - $haben = $i; - } else { - $soll = $i; - } - } - # Umwandlung von Umlauten und Sonderzeichen in erlaubte Zeichen bei Textfeldern + + my $umsatzsumme = sum map { $_->{umsatz} } @datev_lines; + + # prepare kne file, everything gets stored in ED00001 + my $header = $self->make_kne_data_header($form); + my $kne_file = SL::DATEV::KNEFile->new(); + $kne_file->add_block($header); + + 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'); + + # add the data from @datev_lines to the kne_file, formatting as needed + foreach my $kne ( @datev_lines ) { + $kne_file->add_block("+" . $kne_file->format_amount(abs($kne->{umsatz}), 0)); + + # only add buchungsschluessel if it was previously defined + $kne_file->add_block("\x6C" . $kne->{buchungsschluessel}) if defined $kne->{buchungsschluessel}; + + # ($kne->{gegenkonto}) = $kne->{gegenkonto} =~ /^(\d+)/; + $kne_file->add_block("a" . trim_leading_zeroes($kne->{gegenkonto})); + + if ( $kne->{belegfeld1} ) { + my $invnumber = $kne->{belegfeld1}; foreach my $umlaut (keys(%umlaute)) { - $transaction->[$haben]->{'invnumber'} =~ s/${umlaut}/${umlaute{$umlaut}}/g; - $transaction->[$haben]->{'name'} =~ s/${umlaut}/${umlaute{$umlaut}}/g; + $invnumber =~ s/${umlaut}/${umlaute{$umlaut}}/g; } + $invnumber =~ s/[^0-9A-Za-z\$\%\&\*\+\-\/]//g; + $invnumber = substr($invnumber, 0, 12); + $invnumber =~ s/\ *$//; + $kne_file->add_block("\xBD" . $invnumber . "\x1C"); + } - $transaction->[$haben]->{'invnumber'} =~ s/[^0-9A-Za-z\$\%\&\*\+\-\/]//g; - $transaction->[$haben]->{'name'} =~ s/[^0-9A-Za-z\$\%\&\*\+\-\ \/]//g; + $kne_file->add_block("\xBE" . &datetofour($kne->{belegfeld2},1) . "\x1C"); - $transaction->[$haben]->{'invnumber'} = substr($transaction->[$haben]->{'invnumber'}, 0, 12); - $transaction->[$haben]->{'name'} = substr($transaction->[$haben]->{'name'}, 0, 30); - $transaction->[$haben]->{'invnumber'} =~ s/\ *$//; - $transaction->[$haben]->{'name'} =~ s/\ *$//; + $kne_file->add_block("d" . &datetofour($kne->{datum},0)); - if ($trans_lines >= 2) { + # ($kne->{konto}) = $kne->{konto} =~ /^(\d+)/; + $kne_file->add_block("e" . trim_leading_zeroes($kne->{konto})); - $gegenkonto = "a" . trim_leading_zeroes($transaction->[$haben]->{'accno'}); - $konto = "e" . trim_leading_zeroes($transaction->[$soll]->{'accno'}); - if ($transaction->[$haben]->{'invnumber'} ne "") { - $belegfeld1 = "\xBD" . $transaction->[$haben]->{'invnumber'} . "\x1C"; - } - $datum = "d"; - $datum .= &datetofour($transaction->[$haben]->{'transdate'}, 0); - $waehrung = "\xB3" . "EUR" . "\x1C"; - if ($transaction->[$haben]->{'name'} ne "") { - $buchungstext = "\x1E" . $transaction->[$haben]->{'name'} . "\x1C"; - } - if (($transaction->[$haben]->{'ustid'} // '') ne "") { - $ustid = "\xBA" . $transaction->[$haben]->{'ustid'} . "\x1C"; - } - if (($transaction->[$haben]->{'duedate'} // '') ne "") { - $belegfeld2 = "\xBE" . &datetofour($transaction->[$haben]->{'duedate'}, 1) . "\x1C"; - } - } + my $name = $kne->{buchungstext}; + foreach my $umlaut (keys(%umlaute)) { + $name =~ s/${umlaut}/${umlaute{$umlaut}}/g; + } + $name =~ s/[^0-9A-Za-z\$\%\&\*\+\-\ \/]//g; + $name = substr($name, 0, 30); + $name =~ s/\ *$//; + $kne_file->add_block("\x1E" . $name . "\x1C"); - $umsatz = $kne_file->format_amount(abs($umsatz), 0); - $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")); - $kne_file->add_block("\x6C${taxkey}"); - } + $kne_file->add_block("\xBA" . $kne->{'ustid'} . "\x1C") if $kne->{'ustid'}; - $kne_file->add_block($gegenkonto); - $kne_file->add_block($belegfeld1); - $kne_file->add_block($belegfeld2); - $kne_file->add_block($datum); - $kne_file->add_block($konto); - $kne_file->add_block($buchungstext); - $kne_file->add_block($ustid); - $kne_file->add_block($waehrung . "\x79"); - } + $kne_file->add_block("\xB3" . $kne->{'waehrung'} . "\x1C" . "\x79"); + }; - my $mandantenendsumme = "x" . $kne_file->format_amount($umsatzsumme / 100.0, 14) . "\x79\x7a"; + $umsatzsumme = $kne_file->format_amount(abs($umsatzsumme), 0); + my $mandantenendsumme = "x" . $kne_file->format_amount($umsatzsumme / 100.0, 14) . "\x79\x7a"; - $kne_file->add_block($mandantenendsumme); - $kne_file->flush(); + $kne_file->add_block($mandantenendsumme); + $kne_file->flush(); - open(ED, ">", $ed_filename) or die "can't open outputfile: $!\n"; - print(ED $kne_file->get_data()); - close(ED); + open(ED, ">", $ed_filename) or die "can't open outputfile: $!\n"; + print(ED $kne_file->get_data()); + close(ED); - $ed_versionset[$fileno] = $self->make_ed_versionset($header, $filename, $kne_file->get_block_count()); - $fileno++; - } + $ed_versionset[$fileno] = $self->make_ed_versionset($header, $filename, $kne_file->get_block_count()); #Make EV Verwaltungsdatei - my $ev_header = $self->make_ev_header($form, $fileno); + my $ev_header = $self->make_ev_header($form, $fileno); my $ev_filename = $self->export_path . $evfile; push(@filenames, $evfile); open(EV, ">", $ev_filename) or die "can't open outputfile: EV01\n"; @@ -1108,7 +1174,7 @@ sub _format_accno { sub csv_export_for_tax_accountant { my ($self) = @_; - $self->_get_transactions(from_to => $self->fromto); + $self->generate_datev_data(from_to => $self->fromto); foreach my $transaction (@{ $self->{DATEV} }) { foreach my $entry (@{ $transaction }) { @@ -1136,7 +1202,7 @@ sub csv_export_for_tax_accountant { taxkey => { 'text' => $::locale->text('Taxkey'), }, tax_accname => { 'text' => $::locale->text('Tax Account Name'), }, tax_accno => { 'text' => $::locale->text('Tax Account'), }, - transdate => { 'text' => $::locale->text('Invoice Date'), }, + transdate => { 'text' => $::locale->text('Transdate'), }, vcnumber => { 'text' => $::locale->text('Customer/Vendor Number'), }, ); @@ -1291,6 +1357,16 @@ SL::DATEV - kivitendo DATEV Export module my $path = $datev->export_path; my @files = glob("$path/*"); + # Only test the datev data of a specific trans_id, without generating an + # export file, but filling $datev->errors if errors exist + + my $datev = SL::DATEV->new( + trans_id => $invoice->trans_id, + ); + $datev->generate_datev_data; + # if ($datev->errors) { ... + + =head1 DESCRIPTION This module implements the DATEV export standard. For usage see above. @@ -1303,6 +1379,30 @@ This module implements the DATEV export standard. For usage see above. Generic constructor. See section attributes for information about what to pass. +=item generate_datev_data + +Fetches all transactions from the database (via a trans_id or a date range), +and does an initial transformation (e.g. filters out tax, determines +the brutto amount, checks split transactions ...) and stores this data in +$self->{DATEV}. + +If any errors are found these are collected in $self->errors. + +This function is needed for all the exports, but can be also called +independently in order to check transactions for DATEV compatibility. + +=item generate_datev_lines + +Parse the data in $self->{DATEV} and transform it into a format that can be +used by DATEV, e.g. determines Konto and Gegenkonto, the taxkey, ... + +The transformed data is returned as an arrayref, which is ready to be converted +to a DATEV data format, e.g. KNE, OBE, CSV, ... + +At this stage the "DATEV rule" has already been applied to the taxkeys, i.e. +entries with datevautomatik have an empty taxkey, as the taxkey is already +determined by the chart. + =item get_datev_stamm Loads DATEV Stammdaten and returns as hashref. @@ -1366,6 +1466,25 @@ Returns a list of errors that occured. If no errors occured, the export was a su Exports data. You have to have set L and L or an error will occur. OBE exports are currently not implemented. +=item csv_export_for_tax_accountant + +Generates up to four downloadable csv files containing data about sales and +purchase invoices, and their respective payments: + +Example: + my $startdate = DateTime->new(year => 2012, month => 1, day => 1); + my $enddate = DateTime->new(year => 2012, month => 12, day => 31); + SL::DATEV->new(from => $startdate, to => $enddate)->csv_export_for_tax_accountant; + # { + # 'download_token' => '1488551625-815654-22430', + # 'filenames' => [ + # 'Zahlungen Kreditorenbuchungen 2012-01-01 - 2012-12-31.csv', + # 'Kreditorenbuchungen 2012-01-01 - 2012-12-31.csv', + # 'Zahlungen Debitorenbuchungen 2012-01-01 - 2012-12-31.csv', + # 'Debitorenbuchungen 2012-01-01 - 2012-12-31.csv' + # ] + # }; + =back =head1 ATTRIBUTES