DATEV-Export Festschreibungskennzeichen für jede Buchung einzel setzen
[kivitendo-erp.git] / SL / DATEV / CSV.pm
1 package SL::DATEV::CSV;
2
3 use strict;
4 use Carp;
5 use DateTime;
6 use Encode qw(encode);
7 use Scalar::Util qw(looks_like_number);
8
9 use SL::DB::Datev;
10 use SL::DB::Chart;
11 use SL::Helper::DateTime;
12 use SL::Locale::String qw(t8);
13 use SL::Util qw(trim);
14
15 use Rose::Object::MakeMethods::Generic (
16   scalar => [ qw(datev_lines from to locked warnings) ],
17 );
18
19 my @kivitendo_to_datev = (
20                             {
21                               kivi_datev_name => 'umsatz',
22                               csv_header_name => t8('Transaction Value'),
23                               max_length      => 13,
24                               type            => 'Value',
25                               required        => 1,
26                               input_check     => sub { my ($input) = @_; return (looks_like_number($input) && length($input) <= 13 && $input > 0) },
27                               formatter       => \&_format_amount,
28                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) },
29                             },
30                             {
31                               kivi_datev_name => 'soll_haben_kennzeichen',
32                               csv_header_name => t8('Debit/Credit Label'),
33                               max_length      => 1,
34                               type            => 'Text',
35                               required        => 1,
36                               default         => 'S',
37                               input_check     => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
38                               formatter       => sub { my ($input) = @_; return $input eq 'H' ? 'H' : 'S' },
39                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
40                             },
41                             {
42                               kivi_datev_name => 'waehrung',
43                               csv_header_name => t8('Transaction Value Currency Code'),
44                               max_length      => 3,
45                               type            => 'Text',
46                               default         => '',
47                               input_check     => sub { my ($check) = @_; return ($check eq '' || $check =~ m/^[A-Z]{3}$/) },
48                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) },
49                             },
50                             {
51                               kivi_datev_name => 'wechselkurs',
52                               csv_header_name => t8('Exchange Rate'),
53                               max_length      => 11,
54                               type            => 'Number',
55                               default         => '',
56                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) },
57                             },
58                             {
59                               kivi_datev_name => 'not yet implemented',
60                               csv_header_name => t8('Base Transaction Value'),
61                             },
62                             {
63                               kivi_datev_name => 'not yet implemented',
64                               csv_header_name => t8('Base Transaction Value Currency Code'),
65                             },
66                             {
67                               kivi_datev_name => 'konto',
68                               csv_header_name => t8('Account'),
69                               max_length      => 9,
70                               type            => 'Account',
71                               required        => 1,
72                               input_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
73                             },
74                             {
75                               kivi_datev_name => 'gegenkonto',
76                               csv_header_name => t8('Contra Account'),
77                               max_length      => 9,
78                               type            => 'Account',
79                               required        => 1,
80                               input_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
81                             },
82                             {
83                               kivi_datev_name => 'buchungsschluessel',
84                               csv_header_name => t8('Posting Key'),
85                               max_length      => 2,
86                               type            => 'Text',
87                               default         => '',
88                               input_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
89                             },
90                             {
91                               kivi_datev_name => 'datum',
92                               csv_header_name => t8('Invoice Date'),
93                               max_length      => 4,
94                               type            => 'Date',
95                               required        => 1,
96                               input_check     => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
97                               formatter       => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') },
98                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
99                             },
100                             {
101                               kivi_datev_name => 'belegfeld1',
102                               csv_header_name => t8('Invoice Field 1'),
103                               max_length      => 12,
104                               type            => 'Text',
105                               default         => '',
106                               input_check     => sub { return 1 unless $::instance_conf->get_datev_export_format eq 'cp1252';
107                                                        my ($text) = @_; check_encoding($text); },
108                               valid_check     => sub { return 1 if     $::instance_conf->get_datev_export_format eq 'cp1252';
109                                                        my ($text) = @_; check_encoding($text); },
110                               formatter       => sub { my ($input) = @_; return substr($input, 0, 12) },
111                             },
112                             {
113                               kivi_datev_name => 'belegfeld2',
114                               csv_header_name => t8('Invoice Field 2'),
115                               max_length      => 12,
116                               type            => 'Text',
117                               default         => '',
118                               input_check     => sub { my ($check) = @_; return 1 unless $check; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
119                               formatter       => sub { my ($input) = @_; return '' unless $input; return trim(DateTime->from_kivitendo($input)->strftime('%e%m%y')) },
120                               valid_check     => sub { my ($check) = @_; return 1 unless $check; return ($check =~ m/^[0-9]{5,6}$/) },
121                             },
122                             {
123                               kivi_datev_name => 'not yet implemented',
124                               csv_header_name => t8('Discount'),
125                               type            => 'Value',
126                             },
127                             {
128                               kivi_datev_name => 'buchungstext',
129                               csv_header_name => t8('Posting Text'),
130                               max_length      => 60,
131                               type            => 'Text',
132                               default         => '',
133                               input_check     => sub { return 1 unless $::instance_conf->get_datev_export_format eq 'cp1252';
134                                                        my ($text) = @_; check_encoding($text); },
135                               valid_check     => sub { return 1 if     $::instance_conf->get_datev_export_format eq 'cp1252';
136                                                        my ($text) = @_; check_encoding($text); },
137                             },  # pos 14
138                             {
139                               kivi_datev_name => 'not yet implemented',
140                             },
141                             {
142                               kivi_datev_name => 'not yet implemented',
143                             },
144                             {
145                               kivi_datev_name => 'not yet implemented',
146                             },
147                             {
148                               kivi_datev_name => 'not yet implemented',
149                             },
150                             {
151                               kivi_datev_name => 'not yet implemented',
152                             },
153                             {
154                               kivi_datev_name => 'not yet implemented',
155                               csv_header_name => t8('Link to invoice'),
156                               max_length      => 210, # DMS Application shortcut and GUID
157                                                       # Example: "BEDI"
158                                                       # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
159                             }, # pos 20
160                             {
161                               kivi_datev_name => 'not yet implemented',
162                             },
163                             {
164                               kivi_datev_name => 'not yet implemented',
165                             },
166                             {
167                               kivi_datev_name => 'not yet implemented',
168                             },
169                             {
170                               kivi_datev_name => 'not yet implemented',
171                             },
172                             {
173                               kivi_datev_name => 'not yet implemented',
174                             },
175                             {
176                               kivi_datev_name => 'not yet implemented',
177                             },
178                             {
179                               kivi_datev_name => 'not yet implemented',
180                             },
181                             {
182                               kivi_datev_name => 'not yet implemented',
183                             },
184                             {
185                               kivi_datev_name => 'not yet implemented',
186                             },
187                             {
188                               kivi_datev_name => 'not yet implemented',
189                             },
190                             {
191                               kivi_datev_name => 'not yet implemented',
192                             },
193                             {
194                               kivi_datev_name => 'not yet implemented',
195                             },
196                             {
197                               kivi_datev_name => 'not yet implemented',
198                             },
199                             {
200                               kivi_datev_name => 'not yet implemented',
201                             },
202                             {
203                               kivi_datev_name => 'not yet implemented',
204                             },
205                             {
206                               kivi_datev_name => 'not yet implemented',
207                             },
208                             {
209                               kivi_datev_name => 'kost1',
210                               csv_header_name => t8('Cost Center'),
211                               max_length      => 8,
212                               type            => 'Text',
213                               default         => '',
214                               input_check     => sub { my ($text) = @_; return 1 unless $text; check_encoding($text);  },
215                               formatter       => sub { my ($input) = @_; return substr($input, 0, 8) },
216                             }, # pos 37
217                             {
218                               kivi_datev_name => 'kost2',
219                               csv_header_name => t8('Cost Center'),
220                               max_length      => 8,
221                               type            => 'Text',
222                               default         => '',
223                               input_check     => sub { my ($text) = @_; return 1 unless $text; check_encoding($text);  },
224                               formatter       => sub { my ($input) = @_; return substr($input, 0, 8) },
225                             }, # pos 38
226                             {
227                               kivi_datev_name => 'not yet implemented',
228                               csv_header_name => t8('KOST Quantity'),
229                               max_length      => 9,
230                               type            => 'Number',
231                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,9}$/) },
232                             }, # pos 39
233                             {
234                               kivi_datev_name => 'ustid',
235                               csv_header_name => t8('EU Member State and VAT ID Number'),
236                               max_length      => 15,
237                               type            => 'Text',
238                               default         => '',
239                               input_check     => sub {
240                                                        my ($ustid) = @_;
241                                                        return 1 if ('' eq $ustid);
242                                                        $ustid =~ s{\s+}{}g;
243                                                        return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
244                                                      },
245                               formatter       => sub { my ($input) = @_; $input =~ s/\s//g; return $input },
246                               valid_check     => sub {
247                                                        my ($ustid) = @_;
248                                                        return 1 if ('' eq $ustid);
249                                                        return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
250                                                      },
251                             }, # pos 40
252                             {
253                               kivi_datev_name => 'not yet implemented',
254                             },
255                             {
256                               kivi_datev_name => 'not yet implemented',
257                             },
258                             {
259                               kivi_datev_name => 'not yet implemented',
260                             },
261                             {
262                               kivi_datev_name => 'not yet implemented',
263                             },
264                             {
265                               kivi_datev_name => 'not yet implemented',
266                             },
267                             {
268                               kivi_datev_name => 'not yet implemented',
269                             },
270                             {
271                               kivi_datev_name => 'not yet implemented',
272                             },
273                             {
274                               kivi_datev_name => 'not yet implemented',
275                             },
276                             {
277                               kivi_datev_name => 'not yet implemented',
278                             },
279                             {
280                               kivi_datev_name => 'not yet implemented',
281                             },  # pos 50
282                             {
283                               kivi_datev_name => 'not yet implemented',
284                             },
285                             {
286                               kivi_datev_name => 'not yet implemented',
287                             },
288                             {
289                               kivi_datev_name => 'not yet implemented',
290                             },
291                             {
292                               kivi_datev_name => 'not yet implemented',
293                             },
294                             {
295                               kivi_datev_name => 'not yet implemented',
296                             },
297                             {
298                               kivi_datev_name => 'not yet implemented',
299                             },
300                             {
301                               kivi_datev_name => 'not yet implemented',
302                             },
303                             {
304                               kivi_datev_name => 'not yet implemented',
305                             },
306                             {
307                               kivi_datev_name => 'not yet implemented',
308                             },
309                             {
310                               kivi_datev_name => 'not yet implemented',
311                             },  # pos 60
312                             {
313                               kivi_datev_name => 'not yet implemented',
314                             },
315                             {
316                               kivi_datev_name => 'not yet implemented',
317                             },
318                             {
319                               kivi_datev_name => 'not yet implemented',
320                             },
321                             {
322                               kivi_datev_name => 'not yet implemented',
323                             },
324                             {
325                               kivi_datev_name => 'not yet implemented',
326                             },
327                             {
328                               kivi_datev_name => 'not yet implemented',
329                             },
330                             {
331                               kivi_datev_name => 'not yet implemented',
332                             },
333                             {
334                               kivi_datev_name => 'not yet implemented',
335                             },
336                             {
337                               kivi_datev_name => 'not yet implemented',
338                             },
339                             {
340                               kivi_datev_name => 'not yet implemented',
341                             },  # pos 70
342                             {
343                               kivi_datev_name => 'not yet implemented',
344                             },
345                             {
346                               kivi_datev_name => 'not yet implemented',
347                             },
348                             {
349                               kivi_datev_name => 'not yet implemented',
350                             },
351                             {
352                               kivi_datev_name => 'not yet implemented',
353                             },
354                             {
355                               kivi_datev_name => 'not yet implemented',
356                             },
357                             {
358                               kivi_datev_name => 'not yet implemented',
359                             },
360                             {
361                               kivi_datev_name => 'not yet implemented',
362                             },
363                             {
364                               kivi_datev_name => 'not yet implemented',
365                             },
366                             {
367                               kivi_datev_name => 'not yet implemented',
368                             },
369                             {
370                               kivi_datev_name => 'not yet implemented',
371                             },  # pos 80
372                             {
373                               kivi_datev_name => 'not yet implemented',
374                             },
375                             {
376                               kivi_datev_name => 'not yet implemented',
377                             },
378                             {
379                               kivi_datev_name => 'not yet implemented',
380                             },
381                             {
382                               kivi_datev_name => 'not yet implemented',
383                             },
384                             {
385                               kivi_datev_name => 'not yet implemented',
386                             },
387                             {
388                               kivi_datev_name => 'not yet implemented',
389                             },
390                             {
391                               kivi_datev_name => 'not yet implemented',
392                             },
393                             {
394                               kivi_datev_name => 'not yet implemented',
395                             },
396                             {
397                               kivi_datev_name => 'not yet implemented',
398                             },
399                             {
400                               kivi_datev_name => 'not yet implemented',
401                             },  # pos 90
402                             {
403                               kivi_datev_name => 'not yet implemented',
404                             },
405                             {
406                               kivi_datev_name => 'not yet implemented',
407                             },
408                             {
409                               kivi_datev_name => 'not yet implemented',
410                             },
411                             {
412                               kivi_datev_name => 'not yet implemented',
413                             },
414                             {
415                               kivi_datev_name => 'not yet implemented',
416                             },
417                             {
418                               kivi_datev_name => 'not yet implemented',
419                             },
420                             {
421                               kivi_datev_name => 'not yet implemented',
422                             },
423                             {
424                               kivi_datev_name => 'not yet implemented',
425                             },
426                             {
427                               kivi_datev_name => 'not yet implemented',
428                             },
429                             {
430                               kivi_datev_name => 'not yet implemented',
431                             },  # pos 100
432                             {
433                               kivi_datev_name => 'not yet implemented',
434                             },
435                             {
436                               kivi_datev_name => 'not yet implemented',
437                             },
438                             {
439                               kivi_datev_name => 'not yet implemented',
440                             },
441                             {
442                               kivi_datev_name => 'not yet implemented',
443                             },
444                             {
445                               kivi_datev_name => 'not yet implemented',
446                             },
447                             {
448                               kivi_datev_name => 'not yet implemented',
449                             },
450                             {
451                               kivi_datev_name => 'not yet implemented',
452                             },
453                             {
454                               kivi_datev_name => 'not yet implemented',
455                             },
456                             {
457                               kivi_datev_name => 'not yet implemented',
458                             },
459                             {
460                               kivi_datev_name => 'not yet implemented',
461                             },  # pos 110
462                             {
463                               kivi_datev_name => 'not yet implemented',
464                             },
465                             {
466                               kivi_datev_name => 'not yet implemented',
467                             },
468                             {
469                               kivi_datev_name => 'not yet implemented',
470                             },
471                             {
472                               kivi_datev_name => 'locked',
473                               csv_header_name => t8('Lock'),
474                               max_length      => 1,
475                               type            => 'Number',
476                               default         => 1,
477                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^(0|1)$/) },
478                             },  # pos 114
479                             {
480                               kivi_datev_name => 'not yet implemented',
481                             },
482                             {
483                               kivi_datev_name => 'not yet implemented',
484                             },
485
486                             {
487                               kivi_datev_name => 'not yet implemented',
488                             },
489                             {
490                               kivi_datev_name => 'not yet implemented',
491                             },
492                             {
493                               kivi_datev_name => 'not yet implemented',
494                             },
495                             {
496                               kivi_datev_name => 'not yet implemented',
497                             },  # pos 120
498   );
499
500 sub new {
501   my $class = shift;
502   my %data  = @_;
503
504   croak(t8('We need a valid from date'))      unless (ref $data{from} eq 'DateTime');
505   croak(t8('We need a valid to date'))        unless (ref $data{to}   eq 'DateTime');
506   croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
507
508   my $obj = bless {}, $class;
509   $obj->$_($data{$_}) for keys %data;
510   $obj;
511 }
512
513 sub check_encoding {
514   my ($test) = @_;
515   return undef unless $test;
516   if (eval {
517     encode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
518     1
519   }) {
520     return 1;
521   }
522 }
523
524 sub _kivitendo_to_datev {
525   @kivitendo_to_datev, ({ kivi_datev_name => 'not yet implemented' }) x (116 - @kivitendo_to_datev);
526 }
527
528 sub header {
529   my ($self) = @_;
530
531   my @header;
532
533   # we can safely set these defaults
534   # TODO get length_of_accounts from DATEV.pm
535   my $today              = DateTime->now_local;
536   my $created_on         = $today->ymd('') . $today->hms('') . '000';
537   my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
538   my $default_curr       = SL::DB::Default->get_default_currency;
539
540   # datev metadata and the string length limits
541   my %meta_datev;
542   my %meta_datev_to_valid_length = (
543     beraternr   =>  7,
544     beratername => 25,
545     mandantennr =>  5,
546   );
547
548   my $datev = SL::DB::Manager::Datev->get_first();
549
550   while (my ($k, $v) = each %meta_datev_to_valid_length) {
551     next unless $datev->{$k};
552     $meta_datev{$k} = substr $datev->{$k}, 0, $v;
553   }
554
555   my @header_row_1 = (
556     "EXTF", "510", 21, "Buchungsstapel", 7, $created_on, "", "ki",
557     "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
558     $self->first_day_of_fiscal_year->ymd(''), $length_of_accounts,
559     $self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked,
560     $default_curr, "", "", "",""
561   );
562   push @header, [ @header_row_1 ];
563
564   # second header row, just the column names
565   push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ];
566
567   return \@header;
568 }
569
570 sub lines {
571   my ($self) = @_;
572
573   my (@array_of_datev, @warnings);
574   my @csv_columns = _kivitendo_to_datev();
575
576   foreach my $row (@{ $self->datev_lines }) {
577     my @current_datev_row;
578
579     # 1. check all datev_lines and see if we have a defined value
580     # 2. if we don't have a defined value set a default if exists
581     # 3. otherwise die
582     foreach my $column (@csv_columns) {
583       if ($column->{kivi_datev_name} eq 'not yet implemented') {
584         push @current_datev_row, '';
585         next;
586       }
587       my $data = $row->{$column->{kivi_datev_name}};
588       if (!defined $data) {
589         if (defined $column->{default}) {
590           $data = $column->{default};
591         } else {
592           die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
593         }
594       }
595       # checkpoint a: no undefined data. All strict checks now!
596       if (exists $column->{input_check} && !$column->{input_check}->($data)) {
597         die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
598                 $data, $column->{kivi_datev_name}, $row->{umsatz});
599       }
600       # checkpoint b: we can safely format the input
601       if ($column->{formatter}) {
602         $data = $column->{formatter}->($data);
603       }
604       # checkpoint c: all soft checks now, will pop up as a user warning
605       if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
606         push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
607                            " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
608       }
609       push @current_datev_row, $data;
610     }
611     push @array_of_datev, \@current_datev_row;
612   }
613   $self->warnings(\@warnings);
614   return \@array_of_datev;
615 }
616
617 # helper
618
619 sub _format_amount {
620   $::form->format_amount({ numberformat => '1000,00' }, @_);
621 }
622
623 sub first_day_of_fiscal_year {
624   $_[0]->to->clone->truncate(to => 'year');
625 }
626
627 1;
628
629 __END__
630
631 =encoding utf-8
632
633 =head1 NAME
634
635 SL::DATEV::CSV - kivitendo DATEV CSV Specification
636
637 =head1 SYNOPSIS
638
639   use SL::DATEV qw(:CONSTANTS);
640   use SL::DATEV::CSV;
641
642   my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
643   my $enddate   = DateTime->new(year => 2014, month => 9, day => 31);
644   my $datev = SL::DATEV->new(
645     exporttype => DATEV_ET_BUCHUNGEN,
646     format     => DATEV_FORMAT_CSV,
647     from       => $startdate,
648     to         => $enddate,
649   );
650   $datev->generate_datev_data;
651
652   my $datev_csv = SL::DATEV::CSV->new(datev_lines  => $datev->generate_datev_lines,
653                                       from         => $datev->from,
654                                       to           => $datev->to,
655                                       locked       => $datev->locked,
656                                      );
657   $datev_csv->header;   # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array
658   $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
659   $datev_csv->warnings; # returns warnings
660
661
662   # The above object methods can be directly chained to a CSV export function, like this:
663   my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!";
664   $csv->print($csv_file, $_) for @{ $datev_csv->header };
665   $csv->print($csv_file, $_) for @{ $datev_csv->lines  };
666   $csv_file->close;
667   $self->{warnings} = $datev_csv->warnings;
668
669
670
671
672 =head1 DESCRIPTION
673
674 The parsing of the DATEV CSV is index based, therefore the correct
675 column must be present at the corresponding index, i.e.:
676  Index 2
677  Field Name   : Debit/Credit Label
678  Valid Values : 'S' or 'H'
679  Length:      : 1
680
681 The columns in C<@kivi_datev> are in the correct order and the
682 specific attributes are defined as a key value hash list for each entry.
683
684 The key names are the english translation according to the DATEV specs
685 (Leitfaden DATEV englisch).
686
687 The two attributes C<max_length> and C<type> are also set as specified
688 by the DATEV specs.
689
690 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
691 which is by convention the key name as generated by DATEV->generate_datev_data.
692 A value of C<'not yet implemented'> indicates that this field has no
693 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
694
695
696 =head1 SPECIFICATION
697
698 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
699 and CSV-Data lines.
700
701 =head2 FILENAME
702
703 The filename is subject to the following restrictions:
704 1. The filename must begin with the prefix DTVF_ or EXTF_.
705 2. The filename must end with .csv.
706
707 When exporting from or importing into DATEV applications, the filename is
708 marked with the prefix "DTVF_" (DATEV Format).
709 The prefix "DTVF_" is reserved for DATEV applications.
710 If you are using a third-party application to create a file in the DATEV format
711 that you want to import using batch processing, use the prefix "EXTF_"
712 (External Format).
713
714 =head2 File Structure
715
716 The file structure of the text file exported/imported is defined as follows
717
718 Line 1: Header (serves to assist in the interpretation of the following data)
719
720 Line 2: Headline (headline of the user data)
721
722 Line 3 – n: Records (user data)
723
724 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
725
726
727 =head2 Detailed Description
728
729 Line 1 must contain 11 fields.
730
731 Line 2 must contain 26 fields.
732
733 Line 3 - n:  must contain 116 fields, a smaller subset is mandatory.
734
735 =head1 FUNCTIONS
736
737 =over 4
738
739 =item new PARAMS
740
741 Constructor for CSV-DATEV export.
742 Checks mandantory params as described in section synopsis.
743
744 =item check_encoding
745
746 Helper function, returns true if a string is not empty and cp1252 encoded
747 For example some arabic utf-8 like  ݐ  will return false
748
749 =item header
750
751 Mostly all other header information are constants or metadata loaded
752 from SL::DB::Datev.pm.
753
754 Returns the first two entries for the header (see above: File Structure)
755 as an array.
756
757 =item kivitendo_to_datev
758
759 Returns the data structure C<@datev_data> as an array
760
761 =item _format_amount
762
763 Lightweight wrapper for form->format_amount.
764 Expects a number in kivitendo database format and returns the same number
765 in DATEV format.
766
767 =item first_day_of_fiscal_year
768
769 Takes a look at $self->to to  determine the first day of the fiscal year.
770
771 =item lines
772
773 Generates the CSV-Format data for the CSV DATEV export and returns
774 an 2-dimensional array as an array_ref.
775 May additionally return a second array_ref with warnings.
776
777 Requires the same date fields as the constructor for a valid DATEV header.
778
779 Furthermore we assume that the first day of the fiscal year is
780 the first of January and we cannot guarantee that our data in kivitendo
781 is locked, that means a booking cannot be modified after a defined (vat tax)
782 period.
783 Some validity checks (max_length and regex) will be done if the
784 data structure contains them and the field is defined.
785
786 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.
787
788 =back
789
790 =head1 TODO CAVEAT
791
792 One can circumevent the check of the warnings.quite easily,
793 becaus warnings are generated after the call to lines:
794
795   # WRONG usage
796   die if @{ $datev_csv->warnings };
797   somethin_with($datev_csv->lines);
798
799   # safe usage
800   my $lines = $datev_csv->lines;
801   die if @{ $datev_csv->warnings };
802   somethin_with($lines);