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