Einführung einer ID-Spalte in acc_trans
[kivitendo-erp.git] / SL / DATEV.pm
1 #=====================================================================
2 # Lx-Office ERP
3 # Copyright (c) 2004
4 #
5 #  Author: Philip Reetz
6 #   Email: p.reetz@linet-services.de
7 #     Web: http://www.lx-office.org
8 #
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #======================================================================
23 #
24 # Datev export module
25 #======================================================================
26
27 package DATEV;
28
29 use List::Util qw(max);
30
31 use SL::DBUtils;
32 use SL::DATEV::KNEFile;
33 use SL::Taxkeys;
34
35 use Data::Dumper;
36 use File::Path;
37 use Time::HiRes qw(gettimeofday);
38
39 sub _get_export_path {
40   $main::lxdebug->enter_sub();
41
42   my ($a, $b) = gettimeofday();
43   my $path    = get_path_for_download_token("${a}-${b}-${$}");
44
45   mkpath($path) unless (-d $path);
46
47   $main::lxdebug->leave_sub();
48
49   return $path;
50 }
51
52 sub get_path_for_download_token {
53   $main::lxdebug->enter_sub();
54
55   my $token = shift;
56   my $path;
57
58   if ($token =~ m|^(\d+)-(\d+)-(\d+)$|) {
59     $path = "${main::userspath}/datev-export-${1}-${2}-${3}";
60   }
61
62   $main::lxdebug->leave_sub();
63
64   return $path;
65 }
66
67 sub get_download_token_for_path {
68   $main::lxdebug->enter_sub();
69
70   my $path = shift;
71   my $token;
72
73   if ($path =~ m|.*datev-export-(\d+)-(\d+)-(\d+)/?$|) {
74     $token = "${1}-${2}-${3}";
75   }
76
77   $main::lxdebug->leave_sub();
78
79   return $token;
80 }
81
82 sub clean_temporary_directories {
83   $main::lxdebug->enter_sub();
84
85   foreach my $path (glob "${main::userspath}/datev-export-*") {
86     next unless (-d $path);
87
88     my $mtime = (stat($path))[9];
89     next if ((time() - $mtime) < 8 * 60 * 60);
90
91     rmtree $path;
92   }
93
94   $main::lxdebug->leave_sub();
95 }
96
97 sub _fill {
98   $main::lxdebug->enter_sub();
99
100   my $text      = shift;
101   my $field_len = shift;
102   my $fill_char = shift;
103   my $alignment = shift || 'right';
104
105   my $text_len  = length $text;
106
107   if ($field_len < $text_len) {
108     $text = substr $text, 0, $field_len;
109
110   } elsif ($field_len > $text_len) {
111     my $filler = ($fill_char) x ($field_len - $text_len);
112     $text      = $alignment eq 'right' ? $filler . $text : $text . $filler;
113   }
114
115   $main::lxdebug->leave_sub();
116
117   return $text;
118 }
119
120 sub get_datev_stamm {
121   $main::lxdebug->enter_sub();
122
123   my ($self, $myconfig, $form) = @_;
124
125   # connect to database
126   my $dbh = $form->dbconnect($myconfig);
127
128   $query = qq|SELECT * FROM datev|;
129   $sth   = $dbh->prepare($query);
130   $sth->execute || $form->dberror($query);
131
132   my $ref = $sth->fetchrow_hashref(NAME_lc);
133
134   map { $form->{$_} = $ref->{$_} } keys %$ref;
135
136   $sth->finish;
137   $dbh->disconnect;
138   $main::lxdebug->leave_sub();
139 }
140
141 sub save_datev_stamm {
142   $main::lxdebug->enter_sub();
143
144   my ($self, $myconfig, $form) = @_;
145
146   # connect to database
147   my $dbh = $form->dbconnect_noauto($myconfig);
148
149   $query = qq|DELETE FROM datev|;
150   $dbh->do($query) || $form->dberror($query);
151
152   $query = qq|INSERT INTO datev
153               (beraternr, beratername, dfvkz, mandantennr, datentraegernr, abrechnungsnr) VALUES
154               (|
155     . $dbh->quote($form->{beraternr}) . qq|,|
156     . $dbh->quote($form->{beratername}) . qq|,|
157     . $dbh->quote($form->{dfvkz}) . qq|,
158               |
159     . $dbh->quote($form->{mandantennr}) . qq|,|
160     . $dbh->quote($form->{datentraegernr}) . qq|,|
161     . $dbh->quote($form->{abrechnungsnr}) . qq|)|;
162   $sth = $dbh->prepare($query);
163   $sth->execute || $form->dberror($query);
164   $sth->finish;
165
166   $dbh->commit;
167   $dbh->disconnect;
168   $main::lxdebug->leave_sub();
169 }
170
171 sub kne_export {
172   $main::lxdebug->enter_sub();
173
174   my ($self, $myconfig, $form) = @_;
175   my $result;
176
177   if ($form->{exporttype} == 0) {
178     $result = kne_buchungsexport($myconfig, $form);
179   } else {
180     $result = kne_stammdatenexport($myconfig, $form);
181   }
182
183   $main::lxdebug->leave_sub();
184
185   return $result;
186 }
187
188 sub obe_export {
189   $main::lxdebug->enter_sub();
190
191   my ($self, $myconfig, $form) = @_;
192
193   # connect to database
194   my $dbh = $form->dbconnect_noauto($myconfig);
195   $dbh->commit;
196   $dbh->disconnect;
197   $main::lxdebug->leave_sub();
198 }
199
200 sub get_dates {
201   $main::lxdebug->enter_sub();
202
203   my ($zeitraum, $monat, $quartal, $transdatefrom, $transdateto) = @_;
204
205   $fromto = "transdate >= ";
206
207   my @a = localtime;
208   $a[5] += 1900;
209   $jahr = $a[5];
210   if ($zeitraum eq "monat") {
211   SWITCH: {
212       $monat eq "1" && do {
213         $form->{fromdate} = "1.1.$jahr";
214         $form->{todate}   = "31.1.$jahr";
215         last SWITCH;
216       };
217       $monat eq "2" && do {
218         $form->{fromdate} = "1.2.$jahr";
219
220         #this works from 1901 to 2099, 1900 and 2100 fail.
221         $leap = ($jahr % 4 == 0) ? "29" : "28";
222         $form->{todate} = "$leap.2.$jahr";
223         last SWITCH;
224       };
225       $monat eq "3" && do {
226         $form->{fromdate} = "1.3.$jahr";
227         $form->{todate}   = "31.3.$jahr";
228         last SWITCH;
229       };
230       $monat eq "4" && do {
231         $form->{fromdate} = "1.4.$jahr";
232         $form->{todate}   = "30.4.$jahr";
233         last SWITCH;
234       };
235       $monat eq "5" && do {
236         $form->{fromdate} = "1.5.$jahr";
237         $form->{todate}   = "31.5.$jahr";
238         last SWITCH;
239       };
240       $monat eq "6" && do {
241         $form->{fromdate} = "1.6.$jahr";
242         $form->{todate}   = "30.6.$jahr";
243         last SWITCH;
244       };
245       $monat eq "7" && do {
246         $form->{fromdate} = "1.7.$jahr";
247         $form->{todate}   = "31.7.$jahr";
248         last SWITCH;
249       };
250       $monat eq "8" && do {
251         $form->{fromdate} = "1.8.$jahr";
252         $form->{todate}   = "31.8.$jahr";
253         last SWITCH;
254       };
255       $monat eq "9" && do {
256         $form->{fromdate} = "1.9.$jahr";
257         $form->{todate}   = "30.9.$jahr";
258         last SWITCH;
259       };
260       $monat eq "10" && do {
261         $form->{fromdate} = "1.10.$jahr";
262         $form->{todate}   = "31.10.$jahr";
263         last SWITCH;
264       };
265       $monat eq "11" && do {
266         $form->{fromdate} = "1.11.$jahr";
267         $form->{todate}   = "30.11.$jahr";
268         last SWITCH;
269       };
270       $monat eq "12" && do {
271         $form->{fromdate} = "1.12.$jahr";
272         $form->{todate}   = "31.12.$jahr";
273         last SWITCH;
274       };
275     }
276     $fromto .=
277       "'" . $form->{fromdate} . "' and transdate <= '" . $form->{todate} . "'";
278   }
279
280   elsif ($zeitraum eq "quartal") {
281     if ($quartal == 1) {
282       $fromto .=
283         "'01.01." . $jahr . "' and transdate <= '31.03." . $jahr . "'";
284     } elsif ($quartal == 2) {
285       $fromto .=
286         "'01.04." . $jahr . "' and transdate <= '30.06." . $jahr . "'";
287     } elsif ($quartal == 3) {
288       $fromto .=
289         "'01.07." . $jahr . "' and transdate <= '30.09." . $jahr . "'";
290     } elsif ($quartal == 4) {
291       $fromto .=
292         "'01.10." . $jahr . "' and transdate <= '31.12." . $jahr . "'";
293     }
294   }
295
296   elsif ($zeitraum eq "zeit") {
297     $fromto            .= "'" . $transdatefrom . "' and transdate <= '" . $transdateto . "'";
298     my ($yy, $mm, $dd)  = $main::locale->parse_date(\%main::myconfig, $transdatefrom);
299     $jahr               = $yy;
300   }
301
302   $main::lxdebug->leave_sub();
303
304   return ($fromto, $jahr);
305 }
306
307 sub _sign {
308   my $value = shift;
309
310   return $value < 0 ? -1
311     :    $value > 0 ?  1
312     :                  0;
313 }
314
315 sub _get_transactions {
316   $main::lxdebug->enter_sub();
317
318   my $fromto   =  shift;
319
320   my $myconfig =  \%main::myconfig;
321   my $form     =  $main::form;
322
323   my $dbh      =  $form->get_standard_dbh($myconfig);
324
325   my @errors   = ();
326
327   $fromto      =~ s/transdate/ac\.transdate/g;
328
329   my $taxkeys  = Taxkeys->new();
330   my $filter   = '';            # Useful for debugging purposes
331
332   my %all_taxchart_ids = selectall_as_map($form, $dbh, qq|SELECT DISTINCT chart_id, TRUE AS is_set FROM tax|, 'chart_id', 'is_set');
333
334   my $query    =
335     qq|SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ar.id, ac.amount, ac.taxkey,
336          ar.invnumber, ar.duedate, ar.amount as umsatz,
337          ct.name,
338          c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, c.link,
339          ar.invoice
340        FROM acc_trans ac
341        LEFT JOIN ar          ON (ac.trans_id    = ar.id)
342        LEFT JOIN customer ct ON (ar.customer_id = ct.id)
343        LEFT JOIN chart c     ON (ac.chart_id    = c.id)
344        WHERE (ar.id IS NOT NULL)
345          AND $fromto
346          $filter
347
348        UNION ALL
349
350        SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ap.id, ac.amount, ac.taxkey,
351          ap.invnumber, ap.duedate, ap.amount as umsatz,
352          ct.name,
353          c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, c.link,
354          ap.invoice
355        FROM acc_trans ac
356        LEFT JOIN ap        ON (ac.trans_id  = ap.id)
357        LEFT JOIN vendor ct ON (ap.vendor_id = ct.id)
358        LEFT JOIN chart c   ON (ac.chart_id  = c.id)
359        WHERE (ap.id IS NOT NULL)
360          AND $fromto
361          $filter
362
363        UNION ALL
364
365        SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,gl.id, ac.amount, ac.taxkey,
366          gl.reference AS invnumber, gl.transdate AS duedate, ac.amount as umsatz,
367          gl.description AS name,
368          c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, c.link,
369          FALSE AS invoice
370        FROM acc_trans ac
371        LEFT JOIN gl      ON (ac.trans_id  = gl.id)
372        LEFT JOIN chart c ON (ac.chart_id  = c.id)
373        WHERE (gl.id IS NOT NULL)
374          AND $fromto
375          $filter
376
377        ORDER BY trans_id, acc_trans_id|;
378
379   my $sth = prepare_execute_query($form, $dbh, $query);
380
381   my $counter = 0;
382   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
383     $counter++;
384     if (($counter % 500) == 0) {
385       print("$counter ");
386     }
387
388     my $trans    = [ $ref ];
389
390     my $count    = $ref->{amount};
391     my $firstrun = 1;
392
393     while (abs($count) > 0.01 || $firstrun) {
394       my $ref2 = $sth->fetchrow_hashref(NAME_lc);
395       last unless ($ref2);
396
397       if ($ref2->{trans_id} != $trans->[0]->{trans_id}) {
398         $form->error("Unbalanced ledger! old trans_id " . $trans->[0]->{trans_id} . " new trans_id " . $ref2->{trans_id} . " count $count");
399         exit 1;
400       }
401
402       push @{ $trans }, $ref2;
403
404       $count    += $ref2->{amount};
405       $firstrun  = 0;
406     }
407
408     foreach my $i (0 .. scalar(@{ $trans }) - 1) {
409       my $ref        = $trans->[$i];
410       my $prev_ref   = 0 < $i ? $trans->[$i - 1] : undef;
411       if (   $all_taxchart_ids{$ref->{id}}
412           && ($ref->{link} =~ m/(?:AP_tax|AR_tax)/)
413           && (   ($prev_ref && $prev_ref->{taxkey} && (_sign($ref->{amount}) == _sign($prev_ref->{amount})))
414               || $ref->{invoice})) {
415         $ref->{is_tax} = 1;
416       }
417
418       if (   !$ref->{invoice}
419           &&  $ref->{is_tax}
420           && !($prev_ref->{is_tax})
421           &&  (_sign($ref->{amount}) == _sign($prev_ref->{amount}))) {
422         $trans->[$i - 1]->{tax_amount} = $ref->{amount};
423       }
424     }
425
426     my %taxid_taxkeys = ();
427     my $absumsatz     = 0;
428     if (scalar(@{$trans}) <= 2) {
429       push @{ $form->{DATEV} }, $trans;
430       next;
431     }
432
433     for my $j (0 .. (scalar(@{$trans}) - 1)) {
434       if (abs($trans->[$j]->{'amount'}) > abs($absumsatz)) {
435         $absumsatz     = $trans->[$j]->{'amount'};
436         $notsplitindex = $j;
437       }
438     }
439
440     my $ml             = ($trans->[0]->{'umsatz'} > 0) ? 1 : -1;
441     my $rounding_error = 0;
442
443     for my $j (0 .. (scalar(@{$trans}) - 1)) {
444       if (   ($j != $notsplitindex)
445           && !$trans->[$j]->{is_tax}
446           && (   $trans->[$j]->{'taxkey'} eq ""
447               || $trans->[$j]->{'taxkey'} eq "0"
448               || $trans->[$j]->{'taxkey'} eq "1"
449               || $trans->[$j]->{'taxkey'} eq "10"
450               || $trans->[$j]->{'taxkey'} eq "11")) {
451         my %new_trans = ();
452         map { $new_trans{$_} = $trans->[$notsplitindex]->{$_}; } keys %{ $trans->[$notsplitindex] };
453
454         $absumsatz               += $trans->[$j]->{'amount'};
455         $new_trans{'amount'}      = $trans->[$j]->{'amount'} * (-1);
456         $new_trans{'umsatz'}      = abs($trans->[$j]->{'amount'}) * $ml;
457         $trans->[$j]->{'umsatz'}  = abs($trans->[$j]->{'amount'}) * $ml;
458
459         push @{ $form->{DATEV} }, [ \%new_trans, $trans->[$j] ];
460
461       } elsif (($j != $notsplitindex) && !$trans->[$j]->{is_tax}) {
462         my %tax_info = $taxkeys->get_full_tax_info('transdate' => $trans->[$j]->{transdate});
463
464         my %new_trans = ();
465         map { $new_trans{$_} = $trans->[$notsplitindex]->{$_}; } keys %{ $trans->[$notsplitindex] };
466
467         my $tax_rate              = $tax_info{taxkeys}->{ $trans->[$j]->{'taxkey'} }->{taxrate};
468         $new_trans{'net_amount'}  = $trans->[$j]->{'amount'} * -1;
469         $new_trans{'tax_rate'}    = 1 + $tax_rate;
470
471         if (!$trans->[$j]->{'invoice'}) {
472           $new_trans{'amount'}      = $form->round_amount(-1 * ($trans->[$j]->{amount} + $trans->[$j]->{tax_amount}), 2);
473           $new_trans{'umsatz'}      = abs($new_trans{'amount'}) * $ml;
474           $trans->[$j]->{'umsatz'}  = $new_trans{'umsatz'};
475           $absumsatz               += -1 * $new_trans{'amount'};
476
477         } else {
478           my $unrounded             = $trans->[$j]->{'amount'} * (1 + $tax_rate) * -1; # + $rounding_error;
479           my $rounded               = $form->round_amount($unrounded, 2);
480           $rounding_error          += $unrounded - $rounded;
481           $new_trans{'amount'}      = $rounded;
482           $new_trans{'umsatz'}      = abs($form->round_amount(($trans->[$j]->{'amount'} * (1 + $tax_rate)), 2)) * $ml;
483           $trans->[$j]->{'umsatz'}  = abs($form->round_amount(($trans->[$j]->{'amount'} * (1 + $tax_rate)), 2)) * $ml;
484           $absumsatz               += $form->round_amount($trans->[$j]->{'amount'} + $trans->[$j]->{'amount'} * $tax_rate, 2);
485         }
486
487         push @{ $form->{DATEV} }, [ \%new_trans, $trans->[$j] ];
488       }
489     }
490
491     my $idx        = 0;
492     my $correction = 0;
493     while (abs($absumsatz) >= 0.01) {
494       if ($idx >= scalar @taxed) {
495         last if (!$correction);
496
497         $correction = 0;
498         $idx        = 0;
499       }
500
501       my $transaction = $taxed[$idx]->[0];
502
503       my $old_amount     = $transaction->{amount};
504       my $old_correction = $correction;
505       my @possible_diffs;
506
507       if (!$transaction->{diff}) {
508         @possible_diffs = (0.01, -0.01);
509       } else {
510         @possible_diffs = ($transaction->{diff});
511       }
512
513       foreach my $diff (@possible_diffs) {
514         my $net_amount = $form->round_amount(($transaction->{amount} + $diff) / $transaction->{tax_rate}, 2);
515         next if ($net_amount != $transaction->{net_amount});
516
517         $transaction->{diff}    = $diff;
518         $transaction->{amount} += $diff;
519         $transaction->{umsatz} += $diff;
520         $absumsatz             -= $diff;
521         $correction             = 1;
522
523         last;
524       }
525
526       $idx++;
527     }
528
529     if (abs($absumsatz) >= 0.01) {
530       push @errors, "Datev-Export fehlgeschlagen! Bei Transaktion $trans->[0]->{trans_id} ($absumsatz, Rundungsfehler $rounding_error)\n";
531     }
532   }
533
534   $sth->finish();
535
536   $form->error(join("<br>\n", @errors)) if (@errors);
537
538   $main::lxdebug->leave_sub();
539 }
540
541 sub make_kne_data_header {
542   $main::lxdebug->enter_sub();
543
544   my ($myconfig, $form, $fromto, $start_jahr) = @_;
545
546   my $jahr = $start_jahr;
547   if (!$jahr) {
548     my @a = localtime;
549     $jahr = $a[5];
550   }
551
552   #Header
553   my $header  = "\x1D\x181";
554   $header    .= _fill($form->{datentraegernr}, 3, ' ', 'left');
555   $header    .= ($fromto) ? "11" : "13"; # Anwendungsnummer
556   $header    .= _fill($form->{dfvkz}, 2, '0');
557   $header    .= _fill($form->{beraternr}, 7, '0');
558   $header    .= _fill($form->{mandantennr}, 5, '0');
559   $header    .= _fill($form->{abrechnungsnr} . $jahr, 6, '0');
560
561   $fromto         =~ s/transdate|>=|and|\'|<=//g;
562   my ($from, $to) =  split /   /, $fromto;
563   $from           =~ s/ //g;
564   $to             =~ s/ //g;
565
566   if ($from ne "") {
567     my ($fday, $fmonth, $fyear) = split(/\./, $from);
568     if (length($fmonth) < 2) {
569       $fmonth = "0" . $fmonth;
570     }
571     if (length($fday) < 2) {
572       $fday = "0" . $fday;
573     }
574     $from = $fday . $fmonth . substr($fyear, -2, 2);
575   } else {
576     $from = "";
577   }
578
579   $header .= $from;
580
581   if ($to ne "") {
582     my ($tday, $tmonth, $tyear) = split(/\./, $to);
583     if (length($tmonth) < 2) {
584       $tmonth = "0" . $tmonth;
585     }
586     if (length($tday) < 2) {
587       $tday = "0" . $tday;
588     }
589     $to = $tday . $tmonth . substr($tyear, -2, 2);
590   } else {
591     $to = "";
592   }
593   $header .= $to;
594
595   if ($fromto ne "") {
596     $primanota = "001";
597     $header .= $primanota;
598   }
599
600   $header .= _fill($form->{passwort}, 4, '0');
601   $header .= " " x 16;       # Anwendungsinfo
602   $header .= " " x 16;       # Inputinfo
603   $header .= "\x79";
604
605   #Versionssatz
606   my $versionssatz  = $form->{exporttype} == 0 ? "\xB5" . "1," : "\xB6" . "1,";
607
608   my $dbh           = $form->get_standard_dbh($myconfig);
609   my $query         = qq|SELECT accno FROM chart LIMIT 1|;
610   my $ref           = selectfirst_hashref_query($form, $dbh, $query);
611
612   $versionssatz    .= length $ref->{accno};
613   $versionssatz    .= ",";
614   $versionssatz    .= length $ref->{accno};
615   $versionssatz    .= ",SELF" . "\x1C\x79";
616
617   $header          .= $versionssatz;
618
619   $main::lxdebug->leave_sub();
620
621   return $header;
622 }
623
624 sub datetofour {
625   $main::lxdebug->enter_sub();
626
627   my ($date, $six) = @_;
628
629   ($day, $month, $year) = split(/\./, $date);
630
631   if ($day =~ /^0/) {
632     $day = substr($day, 1, 1);
633   }
634   if (length($month) < 2) {
635     $month = "0" . $month;
636   }
637   if (length($year) > 2) {
638     $year = substr($year, -2, 2);
639   }
640
641   if ($six) {
642     $date = $day . $month . $year;
643   } else {
644     $date = $day . $month;
645   }
646
647   $main::lxdebug->leave_sub();
648
649   return $date;
650 }
651
652 sub trim_leading_zeroes {
653   my $str = shift;
654
655   $str =~ s/^0+//g;
656
657   return $str;
658 }
659
660 sub make_ed_versionset {
661   $main::lxdebug->enter_sub();
662
663   my ($header, $filename, $blockcount, $fromto) = @_;
664
665   my $versionset  = "V" . substr($filename, 2, 5);
666   $versionset    .= substr($header, 6, 22);
667
668   if ($fromto ne "") {
669     $versionset .= "0000" . substr($header, 28, 19);
670   } else {
671     $datum = " " x 16;
672     $versionset .= $datum . "001" . substr($header, 28, 4);
673   }
674
675   $versionset .= _fill($blockcount, 5, '0');
676   $versionset .= "001";
677   $versionset .= " 1";
678   $versionset .= substr($header, -12, 10) . "    ";
679   $versionset .= " " x 53;
680
681   $main::lxdebug->leave_sub();
682
683   return $versionset;
684 }
685
686 sub make_ev_header {
687   $main::lxdebug->enter_sub();
688
689   my ($form, $fileno) = @_;
690
691   my $ev_header  = _fill($form->{datentraegernr}, 3, ' ', 'left');
692   $ev_header    .= "   ";
693   $ev_header    .= _fill($form->{beraternr}, 7, ' ', 'left');
694   $ev_header    .= _fill($form->{beratername}, 9, ' ', 'left');
695   $ev_header    .= " ";
696   $ev_header    .= (_fill($fileno, 5, '0')) x 2;
697   $ev_header    .= " " x 95;
698
699   $main::lxdebug->leave_sub();
700
701   return $ev_header;
702 }
703
704 sub kne_buchungsexport {
705   $main::lxdebug->enter_sub();
706
707   my ($myconfig, $form) = @_;
708
709   my @filenames;
710
711   my $export_path = _get_export_path() . "/";
712   my $filename    = "ED00000";
713   my $evfile      = "EV01";
714   my @ed_versionsets;
715   my $fileno = 0;
716
717   $form->header;
718   print qq|
719   <html>
720   <body>Export in Bearbeitung<br>
721   Buchungss&auml;tze verarbeitet:
722 |;
723
724   ($fromto, $start_jahr) =
725     &get_dates($form->{zeitraum}, $form->{monat},
726                $form->{quartal},  $form->{transdatefrom},
727                $form->{transdateto});
728   _get_transactions($fromto);
729   my $counter = 0;
730   print qq|<br>2. Durchlauf:|;
731   while (scalar(@{ $form->{DATEV} })) {
732     my $umsatzsumme = 0;
733     $filename++;
734     my $ed_filename = $export_path . $filename;
735     push(@filenames, $filename);
736     $header = &make_kne_data_header($myconfig, $form, $fromto, $start_jahr);
737
738     my $kne_file = SL::DATEV::KNEFile->new();
739     $kne_file->add_block($header);
740
741     while (scalar(@{ $form->{DATEV} }) > 0) {
742       $transaction = shift @{ $form->{DATEV} };
743       $trans_lines = scalar(@{$transaction});
744       $counter++;
745       if (($counter % 500) == 0) {
746         print("$counter ");
747       }
748
749       my $umsatz         = 0;
750       my $gegenkonto     = "";
751       my $konto          = "";
752       my $belegfeld1     = "";
753       my $datum          = "";
754       my $waehrung       = "";
755       my $buchungstext   = "";
756       my $belegfeld2     = "";
757       my $datevautomatik = 0;
758       my $taxkey         = 0;
759       my $charttax       = 0;
760       my $iconv          = $main::locale->{iconv_iso8859};
761       my %umlaute = ($iconv->convert('ä') => 'ae',
762                      $iconv->convert('ö') => 'oe',
763                      $iconv->convert('ü') => 'ue',
764                      $iconv->convert('Ä') => 'Ae',
765                      $iconv->convert('Ö') => 'Oe',
766                      $iconv->convert('Ü') => 'Ue',
767                      $iconv->convert('ß') => 'sz');
768       for (my $i = 0; $i < $trans_lines; $i++) {
769         if ($trans_lines == 2) {
770           if (abs($transaction->[$i]->{'amount'}) > abs($umsatz)) {
771             $umsatz = $transaction->[$i]->{'amount'};
772           }
773         } else {
774           if (abs($transaction->[$i]->{'umsatz'}) > abs($umsatz)) {
775             $umsatz = $transaction->[$i]->{'umsatz'};
776           }
777         }
778         if ($transaction->[$i]->{'datevautomatik'}) {
779           $datevautomatik = 1;
780         }
781         if ($transaction->[$i]->{'taxkey'}) {
782           $taxkey = $transaction->[$i]->{'taxkey'};
783         }
784         if ($transaction->[$i]->{'charttax'}) {
785           $charttax = $transaction->[$i]->{'charttax'};
786         }
787         if ($transaction->[$i]->{'amount'} > 0) {
788           $haben = $i;
789         } else {
790           $soll = $i;
791         }
792       }
793
794       # Umwandlung von Umlauten und Sonderzeichen in erlaubte Zeichen bei Textfeldern
795       foreach $umlaut (keys(%umlaute)) {
796         $transaction->[$haben]->{'invnumber'} =~ s/${umlaut}/${umlaute{$umlaut}}/g;
797         $transaction->[$haben]->{'name'}      =~ s/${umlaut}/${umlaute{$umlaut}}/g;
798       }
799
800       $transaction->[$haben]->{'invnumber'} =~ s/[^0-9A-Za-z\$\%\&\*\+\-\/]//g;
801       $transaction->[$haben]->{'name'}      =~ s/[^0-9A-Za-z\$\%\&\*\+\-\ \/]//g;
802
803       $transaction->[$haben]->{'invnumber'} =  substr($transaction->[$haben]->{'invnumber'}, 0, 12);
804       $transaction->[$haben]->{'name'}      =  substr($transaction->[$haben]->{'name'}, 0, 30);
805       $transaction->[$haben]->{'invnumber'} =~ s/\ *$//;
806       $transaction->[$haben]->{'name'}      =~ s/\ *$//;
807
808       if ($trans_lines >= 2) {
809
810         $gegenkonto = "a" . trim_leading_zeroes($transaction->[$haben]->{'accno'});
811         $konto      = "e" . trim_leading_zeroes($transaction->[$soll]->{'accno'});
812         if ($transaction->[$haben]->{'invnumber'} ne "") {
813           $belegfeld1 = "\xBD" . $transaction->[$haben]->{'invnumber'} . "\x1C";
814         }
815         $datum = "d";
816         $datum .= &datetofour($transaction->[$haben]->{'transdate'}, 0);
817         $waehrung = "\xB3" . "EUR" . "\x1C";
818         if ($transaction->[$haben]->{'name'} ne "") {
819           $buchungstext = "\x1E" . $transaction->[$haben]->{'name'} . "\x1C";
820         }
821         if ($transaction->[$haben]->{'duedate'} ne "") {
822           $belegfeld2 = "\xBE" . &datetofour($transaction->[$haben]->{'duedate'}, 1) . "\x1C";
823         }
824       }
825
826       $umsatz       = $kne_file->format_amount(abs($umsatz), 0);
827       $umsatzsumme += $umsatz;
828       $kne_file->add_block("+" . $umsatz);
829
830       if (   ( $datevautomatik || $taxkey)
831           && (!$datevautomatik || ($datevautomatik && ($charttax ne $taxkey)))) {
832 #         $kne_file->add_block("\x6C" . (!$datevautomatik ? $taxkey : "4"));
833         $kne_file->add_block("\x6C${taxkey}");
834       }
835
836       $kne_file->add_block($gegenkonto);
837       $kne_file->add_block($belegfeld1);
838       $kne_file->add_block($belegfeld2);
839       $kne_file->add_block($datum);
840       $kne_file->add_block($konto);
841       $kne_file->add_block($buchungstext);
842       $kne_file->add_block($waehrung . "\x79");
843     }
844
845     my $mandantenendsumme = "x" . $kne_file->format_amount($umsatzsumme / 100.0, 14) . "\x79\x7a";
846
847     $kne_file->add_block($mandantenendsumme);
848     $kne_file->flush();
849
850     open(ED, "> $ed_filename") or die "can't open outputfile: $!\n";
851     print(ED $kne_file->get_data());
852     close(ED);
853
854     $ed_versionset[$fileno] = &make_ed_versionset($header, $filename, $kne_file->get_block_count(), $fromto);
855     $fileno++;
856   }
857
858   #Make EV Verwaltungsdatei
859   $ev_header = &make_ev_header($form, $fileno);
860   $ev_filename = $export_path . $evfile;
861   push(@filenames, $evfile);
862   open(EV, "> $ev_filename") or die "can't open outputfile: EV01\n";
863   print(EV $ev_header);
864
865   foreach $file (@ed_versionset) {
866     print(EV $ed_versionset[$file]);
867   }
868   close(EV);
869   print qq|<br>Done. <br>
870 |;
871   ###
872   $main::lxdebug->leave_sub();
873
874   return { 'download_token' => get_download_token_for_path($export_path), 'filenames' => \@filenames };
875 }
876
877 sub kne_stammdatenexport {
878   $main::lxdebug->enter_sub();
879
880   my ($myconfig, $form) = @_;
881   $form->{abrechnungsnr} = "99";
882
883   $form->header;
884   print qq|
885   <html>
886   <body>Export in Bearbeitung<br>
887 |;
888
889   my @filenames;
890
891   my $export_path = _get_export_path() . "/";
892   my $filename    = "ED00000";
893   my $evfile      = "EV01";
894   my @ed_versionsets;
895   my $fileno          = 1;
896   my $i               = 0;
897   my $blockcount      = 1;
898   my $remaining_bytes = 256;
899   my $total_bytes     = 256;
900   my $buchungssatz    = "";
901   $filename++;
902   my $ed_filename = $export_path . $filename;
903   push(@filenames, $filename);
904   open(ED, "> $ed_filename") or die "can't open outputfile: $!\n";
905   $header = &make_kne_data_header($myconfig, $form, "");
906   $remaining_bytes -= length($header);
907
908   # connect to database
909   my $dbh = $form->dbconnect($myconfig);
910
911   my (@where, @values) = ((), ());
912   if ($form->{accnofrom}) {
913     push @where, 'c.accno >= ?';
914     push @values, $form->{accnofrom};
915   }
916   if ($form->{accnoto}) {
917     push @where, 'c.accno <= ?';
918     push @values, $form->{accnoto};
919   }
920
921   my $where_str = ' WHERE ' . join(' AND ', map { "($_)" } @where) if (scalar @where);
922
923   my $query     = qq|SELECT c.accno, c.description
924                      FROM chart c
925                      $where_str
926                      ORDER BY c.accno|;
927
928   my $sth = $dbh->prepare($query);
929   $sth->execute(@values) || $form->dberror($query);
930
931   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
932     if (($remaining_bytes - length("t" . $ref->{'accno'})) <= 6) {
933       $fuellzeichen = ($blockcount * 256 - length($buchungssatz . $header));
934       $buchungssatz .= "\x00" x $fuellzeichen;
935       $blockcount++;
936       $total_bytes = ($blockcount) * 256;
937     }
938     $buchungssatz .= "t" . $ref->{'accno'};
939     $remaining_bytes = $total_bytes - length($buchungssatz . $header);
940     $ref->{'description'} =~ s/[^0-9A-Za-z\$\%\&\*\+\-\/]//g;
941     $ref->{'description'} = substr($ref->{'description'}, 0, 40);
942     $ref->{'description'} =~ s/\ *$//;
943
944     if (
945         ($remaining_bytes - length("\x1E" . $ref->{'description'} . "\x1C\x79")
946         ) <= 6
947       ) {
948       $fuellzeichen = ($blockcount * 256 - length($buchungssatz . $header));
949       $buchungssatz .= "\x00" x $fuellzeichen;
950       $blockcount++;
951       $total_bytes = ($blockcount) * 256;
952     }
953     $buchungssatz .= "\x1E" . $ref->{'description'} . "\x1C\x79";
954     $remaining_bytes = $total_bytes - length($buchungssatz . $header);
955   }
956
957   $sth->finish;
958   print(ED $header);
959   print(ED $buchungssatz);
960   $fuellzeichen = 256 - (length($header . $buchungssatz . "z") % 256);
961   $dateiende = "\x00" x $fuellzeichen;
962   print(ED "z");
963   print(ED $dateiende);
964   close(ED);
965
966   #Make EV Verwaltungsdatei
967   $ed_versionset[0] =
968     &make_ed_versionset($header, $filename, $blockcount, $fromto);
969
970   $ev_header = &make_ev_header($form, $fileno);
971   $ev_filename = $export_path . $evfile;
972   push(@filenames, $evfile);
973   open(EV, "> $ev_filename") or die "can't open outputfile: EV01\n";
974   print(EV $ev_header);
975
976   foreach $file (@ed_versionset) {
977     print(EV $ed_versionset[$file]);
978   }
979   close(EV);
980
981   $dbh->disconnect;
982   ###
983
984   print qq|<br>Done. <br>
985 |;
986
987   $main::lxdebug->leave_sub();
988
989   return { 'download_token' => get_download_token_for_path($export_path), 'filenames' => \@filenames };
990 }
991
992 1;