38c7ad80ccf0a3ead19da04d67f3bea3574d7903
[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                               kivi_datev_name => 'not yet implemented',
487                             },
488                             {
489                               kivi_datev_name => 'not yet implemented',
490                             },
491                             {
492                               kivi_datev_name => 'not yet implemented',
493                             },
494                             {
495                               kivi_datev_name => 'not yet implemented',
496                             },  # pos 120
497   );
498
499 sub new {
500   my $class = shift;
501   my %data  = @_;
502
503   croak(t8('We need a valid from date'))      unless (ref $data{from} eq 'DateTime');
504   croak(t8('We need a valid to date'))        unless (ref $data{to}   eq 'DateTime');
505   croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
506
507   my $obj = bless {}, $class;
508   $obj->$_($data{$_}) for keys %data;
509   $obj;
510 }
511
512 sub check_encoding {
513   my ($test) = @_;
514   return undef unless $test;
515   if (eval {
516     encode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
517     1
518   }) {
519     return 1;
520   }
521 }
522
523 sub _kivitendo_to_datev {
524   @kivitendo_to_datev, ({ kivi_datev_name => 'not yet implemented' }) x (116 - @kivitendo_to_datev);
525 }
526
527 sub header {
528   my ($self) = @_;
529
530   my @header;
531
532   # we can safely set these defaults
533   # TODO get length_of_accounts from DATEV.pm
534   my $today              = DateTime->now_local;
535   my $created_on         = $today->ymd('') . $today->hms('') . '000';
536   my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
537   my $default_curr       = SL::DB::Default->get_default_currency;
538
539   # datev metadata and the string length limits
540   my %meta_datev;
541   my %meta_datev_to_valid_length = (
542     beraternr   =>  7,
543     beratername => 25,
544     mandantennr =>  5,
545   );
546
547   my $datev = SL::DB::Manager::Datev->get_first();
548
549   while (my ($k, $v) = each %meta_datev_to_valid_length) {
550     next unless $datev->{$k};
551     $meta_datev{$k} = substr $datev->{$k}, 0, $v;
552   }
553
554   my @header_row_1 = (
555     "EXTF", "510", 21, "Buchungsstapel", 7, $created_on, "", "ki",
556     "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
557     $self->first_day_of_fiscal_year->ymd(''), $length_of_accounts,
558     $self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked,
559     $default_curr, "", "", "",""
560   );
561   push @header, [ @header_row_1 ];
562
563   # second header row, just the column names
564   push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ];
565
566   return \@header;
567 }
568
569 sub lines {
570   my ($self) = @_;
571
572   my (@array_of_datev, @warnings);
573   my @csv_columns = _kivitendo_to_datev();
574
575   foreach my $row (@{ $self->datev_lines }) {
576     my @current_datev_row;
577
578     # 1. check all datev_lines and see if we have a defined value
579     # 2. if we don't have a defined value set a default if exists
580     # 3. otherwise die
581     foreach my $column (@csv_columns) {
582       if ($column->{kivi_datev_name} eq 'not yet implemented') {
583         push @current_datev_row, '';
584         next;
585       }
586       my $data = $row->{$column->{kivi_datev_name}};
587       if (!defined $data) {
588         if (defined $column->{default}) {
589           $data = $column->{default};
590         } else {
591           die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
592         }
593       }
594       # checkpoint a: no undefined data. All strict checks now!
595       if (exists $column->{input_check} && !$column->{input_check}->($data)) {
596         die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
597                 $data, $column->{kivi_datev_name}, $row->{umsatz});
598       }
599       # checkpoint b: we can safely format the input
600       if ($column->{formatter}) {
601         $data = $column->{formatter}->($data);
602       }
603       # checkpoint c: all soft checks now, will pop up as a user warning
604       if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
605         push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
606                            " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
607       }
608       push @current_datev_row, $data;
609     }
610     push @array_of_datev, \@current_datev_row;
611   }
612   $self->warnings(\@warnings);
613   return \@array_of_datev;
614 }
615
616 # helper
617
618 sub _format_amount {
619   $::form->format_amount({ numberformat => '1000,00' }, @_);
620 }
621
622 sub first_day_of_fiscal_year {
623   $_[0]->to->clone->truncate(to => 'year');
624 }
625
626 1;
627
628 __END__
629
630 =encoding utf-8
631
632 =head1 NAME
633
634 SL::DATEV::CSV - kivitendo DATEV CSV Specification
635
636 =head1 SYNOPSIS
637
638   use SL::DATEV qw(:CONSTANTS);
639   use SL::DATEV::CSV;
640
641   my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
642   my $enddate   = DateTime->new(year => 2014, month => 9, day => 31);
643   my $datev = SL::DATEV->new(
644     exporttype => DATEV_ET_BUCHUNGEN,
645     format     => DATEV_FORMAT_CSV,
646     from       => $startdate,
647     to         => $enddate,
648   );
649   $datev->generate_datev_data;
650
651   my $datev_csv = SL::DATEV::CSV->new(datev_lines  => $datev->generate_datev_lines,
652                                       from         => $datev->from,
653                                       to           => $datev->to,
654                                       locked       => $datev->locked,
655                                      );
656   $datev_csv->header;   # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array
657   $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
658   $datev_csv->warnings; # returns warnings
659
660
661   # The above object methods can be directly chained to a CSV export function, like this:
662   my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!";
663   $csv->print($csv_file, $_) for @{ $datev_csv->header };
664   $csv->print($csv_file, $_) for @{ $datev_csv->lines  };
665   $csv_file->close;
666   $self->{warnings} = $datev_csv->warnings;
667
668
669
670
671 =head1 DESCRIPTION
672
673 The parsing of the DATEV CSV is index based, therefore the correct
674 column must be present at the corresponding index, i.e.:
675  Index 2
676  Field Name   : Debit/Credit Label
677  Valid Values : 'S' or 'H'
678  Length:      : 1
679
680 The columns in C<@kivi_datev> are in the correct order and the
681 specific attributes are defined as a key value hash list for each entry.
682
683 The key names are the english translation according to the DATEV specs
684 (Leitfaden DATEV englisch).
685
686 The two attributes C<max_length> and C<type> are also set as specified
687 by the DATEV specs.
688
689 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
690 which is by convention the key name as generated by DATEV->generate_datev_data.
691 A value of C<'not yet implemented'> indicates that this field has no
692 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
693
694
695 =head1 SPECIFICATION
696
697 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
698 and CSV-Data lines.
699
700 =head2 FILENAME
701
702 The filename is subject to the following restrictions:
703 1. The filename must begin with the prefix DTVF_ or EXTF_.
704 2. The filename must end with .csv.
705
706 When exporting from or importing into DATEV applications, the filename is
707 marked with the prefix "DTVF_" (DATEV Format).
708 The prefix "DTVF_" is reserved for DATEV applications.
709 If you are using a third-party application to create a file in the DATEV format
710 that you want to import using batch processing, use the prefix "EXTF_"
711 (External Format).
712
713 =head2 File Structure
714
715 The file structure of the text file exported/imported is defined as follows
716
717 Line 1: Header (serves to assist in the interpretation of the following data)
718
719 Line 2: Headline (headline of the user data)
720
721 Line 3 – n: Records (user data)
722
723 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
724
725
726 =head2 Detailed Description
727
728 Line 1 must contain 11 fields.
729
730 Line 2 must contain 26 fields.
731
732 Line 3 - n:  must contain 116 fields, a smaller subset is mandatory.
733
734 =head1 FUNCTIONS
735
736 =over 4
737
738 =item new PARAMS
739
740 Constructor for CSV-DATEV export.
741 Checks mandantory params as described in section synopsis.
742
743 =item check_encoding
744
745 Helper function, returns true if a string is not empty and cp1252 encoded
746 For example some arabic utf-8 like  ݐ  will return false
747
748 =item header
749
750 Mostly all other header information are constants or metadata loaded
751 from SL::DB::Datev.pm.
752
753 Returns the first two entries for the header (see above: File Structure)
754 as an array.
755
756 =item kivitendo_to_datev
757
758 Returns the data structure C<@datev_data> as an array
759
760 =item _format_amount
761
762 Lightweight wrapper for form->format_amount.
763 Expects a number in kivitendo database format and returns the same number
764 in DATEV format.
765
766 =item first_day_of_fiscal_year
767
768 Takes a look at $self->to to  determine the first day of the fiscal year.
769
770 =item lines
771
772 Generates the CSV-Format data for the CSV DATEV export and returns
773 an 2-dimensional array as an array_ref.
774 May additionally return a second array_ref with warnings.
775
776 Requires the same date fields as the constructor for a valid DATEV header.
777
778 Furthermore we assume that the first day of the fiscal year is
779 the first of January and we cannot guarantee that our data in kivitendo
780 is locked, that means a booking cannot be modified after a defined (vat tax)
781 period.
782 Some validity checks (max_length and regex) will be done if the
783 data structure contains them and the field is defined.
784
785 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.
786
787 =back
788
789 =head1 TODO CAVEAT
790
791 One can circumevent the check of the warnings.quite easily,
792 becaus warnings are generated after the call to lines:
793
794   # WRONG usage
795   die if @{ $datev_csv->warnings };
796   somethin_with($datev_csv->lines);
797
798   # safe usage
799   my $lines = $datev_csv->lines;
800   die if @{ $datev_csv->warnings };
801   somethin_with($lines);