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