]> wagnertech.de Git - mfinanz.git/blob - SL/DATEV/CSV.pm
restart apache2 in postinst
[mfinanz.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 use SL::VATIDNr;
15
16 use Rose::Object::MakeMethods::Generic (
17   scalar => [ qw(datev_lines from to locked warnings) ],
18 );
19
20 my @kivitendo_to_datev = (
21                             {
22                               kivi_datev_name => 'umsatz',
23                               csv_header_name => t8('Transaction Value'),
24                               max_length      => 13,
25                               type            => 'Value',
26                               required        => 1,
27                               input_check     => sub { my ($input) = @_; return (looks_like_number($input) && length($input) <= 13 && $input > 0) },
28                               formatter       => \&_format_amount,
29                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) },
30                             },
31                             {
32                               kivi_datev_name => 'soll_haben_kennzeichen',
33                               csv_header_name => t8('Debit/Credit Label'),
34                               max_length      => 1,
35                               type            => 'Text',
36                               required        => 1,
37                               default         => 'S',
38                               input_check     => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
39                               formatter       => sub { my ($input) = @_; return $input eq 'H' ? 'H' : 'S' },
40                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
41                             },
42                             {
43                               kivi_datev_name => 'waehrung',
44                               csv_header_name => t8('Transaction Value Currency Code'),
45                               max_length      => 3,
46                               type            => 'Text',
47                               default         => '',
48                               input_check     => sub { my ($check) = @_; return ($check eq '' || $check =~ m/^[A-Z]{3}$/) },
49                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) },
50                             },
51                             {
52                               kivi_datev_name => 'wechselkurs',
53                               csv_header_name => t8('Exchange Rate'),
54                               max_length      => 11,
55                               type            => 'Number',
56                               default         => '',
57                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) },
58                             },
59                             {
60                               kivi_datev_name => 'not yet implemented',
61                               csv_header_name => t8('Base Transaction Value'),
62                             },
63                             {
64                               kivi_datev_name => 'not yet implemented',
65                               csv_header_name => t8('Base Transaction Value Currency Code'),
66                             },
67                             {
68                               kivi_datev_name => 'konto',
69                               csv_header_name => t8('Account'),
70                               max_length      => 9,
71                               type            => 'Account',
72                               required        => 1,
73                               input_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
74                             },
75                             {
76                               kivi_datev_name => 'gegenkonto',
77                               csv_header_name => t8('Contra Account'),
78                               max_length      => 9,
79                               type            => 'Account',
80                               required        => 1,
81                               input_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
82                             },
83                             {
84                               kivi_datev_name => 'buchungsschluessel',
85                               csv_header_name => t8('Posting Key'),
86                               max_length      => 2,
87                               type            => 'Text',
88                               default         => '',
89                               input_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
90                             },
91                             {
92                               kivi_datev_name => 'datum',
93                               csv_header_name => t8('Invoice Date'),
94                               max_length      => 4,
95                               type            => 'Date',
96                               required        => 1,
97                               input_check     => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
98                               formatter       => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') },
99                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
100                             },
101                             {
102                               kivi_datev_name => 'belegfeld1',
103                               csv_header_name => t8('Invoice Field 1'),
104                               max_length      => 12,
105                               type            => 'Text',
106                               default         => '',
107                               input_check     => sub { return 1 unless $::instance_conf->get_datev_export_format eq 'cp1252';
108                                                        my ($text) = @_; check_encoding($text); },
109                               valid_check     => sub { return 1 if     $::instance_conf->get_datev_export_format eq 'cp1252';
110                                                        my ($text) = @_; check_encoding($text); },
111                               formatter       => sub { my ($input) = @_; return substr($input, 0, 12) },
112                             },
113                             {
114                               kivi_datev_name => 'belegfeld2',
115                               csv_header_name => t8('Invoice Field 2'),
116                               max_length      => 12,
117                               type            => 'Text',
118                               default         => '',
119                               input_check     => sub { my ($check) = @_; return 1 unless $check; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
120                               formatter       => sub { my ($input) = @_; return '' unless $input; return trim(DateTime->from_kivitendo($input)->strftime('%e%m%y')) },
121                               valid_check     => sub { my ($check) = @_; return 1 unless $check; return ($check =~ m/^[0-9]{5,6}$/) },
122                             },
123                             {
124                               kivi_datev_name => 'not yet implemented',
125                               csv_header_name => t8('Discount'),
126                               type            => 'Value',
127                             },
128                             {
129                               kivi_datev_name => 'buchungstext',
130                               csv_header_name => t8('Posting Text'),
131                               max_length      => 60,
132                               type            => 'Text',
133                               default         => '',
134                               input_check     => sub { return 1 unless $::instance_conf->get_datev_export_format eq 'cp1252';
135                                                        my ($text) = @_; check_encoding($text); },
136                               valid_check     => sub { return 1 if     $::instance_conf->get_datev_export_format eq 'cp1252';
137                                                        my ($text) = @_; check_encoding($text); },
138                               formatter       => sub { my ($input) = @_; return substr($input, 0, 60) },
139                             },  # pos 14
140                             {
141                               kivi_datev_name => 'not yet implemented',
142                             },
143                             {
144                               kivi_datev_name => 'not yet implemented',
145                             },
146                             {
147                               kivi_datev_name => 'not yet implemented',
148                             },
149                             {
150                               kivi_datev_name => 'not yet implemented',
151                             },
152                             {
153                               kivi_datev_name => 'not yet implemented',
154                             },
155                             {
156                               kivi_datev_name => 'document_guid',
157                               csv_header_name => t8('Link to invoice'),
158                               max_length      => 210, # DMS Application shortcut and GUID
159                                                       # Example: "BEDI"
160                                                       # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
161                               type            => 'Text',
162                               default         => '',
163                               input_check     => sub { my ($check) = @_; return 1 unless $check;
164                                                        my @guids = split(/,/,$check);
165                                                        foreach my $guid (@guids) {
166                                                          return unless ($guid =~ m/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/);
167                                                        }
168                                                        return 1; },
169                               formatter       => sub { my ($input) = @_; return '' unless $input;
170                                                        my @guids = split (/,/,$input);
171                                                        my $first = shift @guids;
172                                                        my $bedi = 'BEDI "' . $first . '"';
173                                                        foreach my $guid (@guids) {
174                                                          $bedi .= ',"' . $guid . '"';
175                                                        }
176                                                        return $bedi; },
177
178                             }, # pos 20
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 => 'not yet implemented',
211                             },
212                             {
213                               kivi_datev_name => 'not yet implemented',
214                             },
215                             {
216                               kivi_datev_name => 'not yet implemented',
217                             },
218                             {
219                               kivi_datev_name => 'not yet implemented',
220                             },
221                             {
222                               kivi_datev_name => 'not yet implemented',
223                             },
224                             {
225                               kivi_datev_name => 'not yet implemented',
226                             },
227                             {
228                               kivi_datev_name => 'kost1',
229                               csv_header_name => t8('Cost Center'),
230                               max_length      => 8,
231                               type            => 'Text',
232                               default         => '',
233                               input_check     => sub { my ($text) = @_; return 1 unless $text; check_encoding($text);  },
234                               formatter       => sub { my ($input) = @_; return substr($input, 0, 8) },
235                             }, # pos 37
236                             {
237                               kivi_datev_name => 'kost2',
238                               csv_header_name => t8('Cost Center'),
239                               max_length      => 8,
240                               type            => 'Text',
241                               default         => '',
242                               input_check     => sub { my ($text) = @_; return 1 unless $text; check_encoding($text);  },
243                               formatter       => sub { my ($input) = @_; return substr($input, 0, 8) },
244                             }, # pos 38
245                             {
246                               kivi_datev_name => 'not yet implemented',
247                               csv_header_name => t8('KOST Quantity'),
248                               max_length      => 9,
249                               type            => 'Number',
250                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,9}$/) },
251                             }, # pos 39
252                             {
253                               kivi_datev_name => 'ustid',
254                               csv_header_name => t8('EU Member State and VAT ID Number'),
255                               max_length      => 15,
256                               type            => 'Text',
257                               default         => '',
258                               input_check     => sub {
259                                                        my ($ustid) = @_;
260                                                        return 1 if ('' eq $ustid);
261                                                        return SL::VATIDNr->validate($ustid);
262                                                      },
263                               formatter       => sub { my ($input) = @_; $input =~ s/\s//g; return $input },
264                               valid_check     => sub {
265                                                        my ($ustid) = @_;
266                                                        return 1 if ('' eq $ustid);
267                                                        return SL::VATIDNr->validate($ustid);
268                                                      },
269                             }, # pos 40
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                             },
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                             },  # pos 50
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                             },
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                             },  # pos 60
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                             },
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                             },  # pos 70
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                             },
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                             },  # pos 80
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                             },
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                             },  # pos 90
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                             },
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                             },  # pos 100
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                             },
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 => 'not yet implemented',
473                             },
474                             {
475                               kivi_datev_name => 'not yet implemented',
476                             },
477                             {
478                               kivi_datev_name => 'not yet implemented',
479                             },  # pos 110
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 => 'locked',
491                               csv_header_name => t8('Lock'),
492                               max_length      => 1,
493                               type            => 'Number',
494                               default         => 1,
495                               valid_check     => sub { my ($check) = @_; return ($check =~ m/^(0|1)$/) },
496                             },  # pos 114
497                             {
498                               kivi_datev_name => 'leistungsdatum',
499                               csv_header_name => t8('Payment Date'),
500                               max_length      => 8,
501                               type            => 'Date',
502                               default         => '',
503                               input_check     => sub { my ($check) = @_; return  1 if ('' eq $check); return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
504                               formatter       => sub { my ($input) = @_; return '' if ('' eq $input); return DateTime->from_kivitendo($input)->strftime('%d%m%Y') },
505                               valid_check     => sub { my ($check) = @_; return  1 if ('' eq $check); return ($check =~ m/^[0-9]{8}$/) },
506                             },  # pos 115
507                             {
508                               kivi_datev_name => 'not yet implemented',
509                             },
510                             # DATEV Prüfprogramm says: Only 116 fields are allowed
511                             #{
512                             #  kivi_datev_name => 'not yet implemented',
513                             #},
514                             #{
515                             #  kivi_datev_name => 'not yet implemented',
516                             #},
517                             #{
518                             #  kivi_datev_name => 'not yet implemented',
519                             #},
520                             #{
521                             #  kivi_datev_name => 'not yet implemented',
522                             #},  # pos 120
523   );
524
525 sub new {
526   my $class = shift;
527   my %data  = @_;
528
529   croak(t8('We need a valid from date'))      unless (ref $data{from} eq 'DateTime');
530   croak(t8('We need a valid to date'))        unless (ref $data{to}   eq 'DateTime');
531   croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
532
533   my $obj = bless {}, $class;
534   $obj->$_($data{$_}) for keys %data;
535   $obj;
536 }
537
538 sub check_encoding {
539   my ($test) = @_;
540   return undef unless $test;
541   if (eval {
542     encode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
543     1
544   }) {
545     return 1;
546   }
547 }
548
549 sub header {
550   my ($self) = @_;
551
552   my @header;
553
554   # we can safely set these defaults
555   # TODO get length_of_accounts from DATEV.pm
556   my $today              = DateTime->now_local;
557   my $created_on         = $today->ymd('') . $today->hms('') . '000';
558   my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
559   my $default_curr       = SL::DB::Default->get_default_currency;
560
561   # datev metadata and the string length limits
562   my %meta_datev;
563   my %meta_datev_to_valid_length = (
564     beraternr   =>  7,
565     beratername => 25,
566     mandantennr =>  5,
567   );
568
569   my $datev = SL::DB::Manager::Datev->get_first();
570
571   while (my ($k, $v) = each %meta_datev_to_valid_length) {
572     next unless $datev->{$k};
573     $meta_datev{$k} = substr $datev->{$k}, 0, $v;
574   }
575   my $coa = $::instance_conf->get_coa eq 'Germany-DATEV-SKR03EU' ? '03'
576           : $::instance_conf->get_coa eq 'Germany-DATEV-SKR04EU' ? '04'
577           : '';
578
579   my @header_row_1 = (
580     "EXTF", "510", 21, "Buchungsstapel", 7, $created_on, "", "ki",
581     "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
582     $self->first_day_of_fiscal_year->ymd(''), $length_of_accounts,
583     $self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked,
584     $default_curr, "", "", "","", $coa, "", "", "", ""
585   );
586   push @header, [ @header_row_1 ];
587
588   # second header row, just the column names
589   push @header, [ map { $_->{csv_header_name} } @kivitendo_to_datev ];
590
591   return \@header;
592 }
593
594 sub lines {
595   my ($self) = @_;
596
597   my (@array_of_datev, @warnings);
598
599   foreach my $row (@{ $self->datev_lines }) {
600     my @current_datev_row;
601
602     # 1. check all datev_lines and see if we have a defined value
603     # 2. if we don't have a defined value set a default if exists
604     # 3. otherwise die
605     foreach my $column (@kivitendo_to_datev) {
606       if ($column->{kivi_datev_name} eq 'not yet implemented') {
607         push @current_datev_row, '';
608         next;
609       }
610       my $data = $row->{$column->{kivi_datev_name}};
611       if (!defined $data) {
612         if (defined $column->{default}) {
613           $data = $column->{default};
614         } else {
615           die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
616         }
617       }
618       # checkpoint a: no undefined data. All strict checks now!
619       if (exists $column->{input_check} && !$column->{input_check}->($data)) {
620         die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
621                 $data, $column->{kivi_datev_name}, $row->{umsatz});
622       }
623       # checkpoint b: we can safely format the input
624       if ($column->{formatter}) {
625         $data = $column->{formatter}->($data);
626       }
627       # checkpoint c: all soft checks now, will pop up as a user warning
628       if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
629         push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
630                            " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
631       }
632       push @current_datev_row, $data;
633     }
634     push @array_of_datev, \@current_datev_row;
635   }
636   $self->warnings(\@warnings);
637   return \@array_of_datev;
638 }
639
640 # helper
641
642 sub _format_amount {
643   $::form->format_amount({ numberformat => '1000,00' }, @_);
644 }
645
646 sub first_day_of_fiscal_year {
647   $_[0]->to->clone->truncate(to => 'year');
648 }
649
650 1;
651
652 __END__
653
654 =encoding utf-8
655
656 =head1 NAME
657
658 SL::DATEV::CSV - kivitendo DATEV CSV Specification
659
660 =head1 SYNOPSIS
661
662   use SL::DATEV qw(:CONSTANTS);
663   use SL::DATEV::CSV;
664
665   my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
666   my $enddate   = DateTime->new(year => 2014, month => 9, day => 31);
667   my $datev = SL::DATEV->new(
668     exporttype => DATEV_ET_BUCHUNGEN,
669     format     => DATEV_FORMAT_CSV,
670     from       => $startdate,
671     to         => $enddate,
672   );
673   $datev->generate_datev_data;
674
675   my $datev_csv = SL::DATEV::CSV->new(datev_lines  => $datev->generate_datev_lines,
676                                       from         => $datev->from,
677                                       to           => $datev->to,
678                                       locked       => $datev->locked,
679                                      );
680   $datev_csv->header;   # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array
681   $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
682   $datev_csv->warnings; # returns warnings
683
684
685   # The above object methods can be directly chained to a CSV export function, like this:
686   my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!";
687   $csv->print($csv_file, $_) for @{ $datev_csv->header };
688   $csv->print($csv_file, $_) for @{ $datev_csv->lines  };
689   $csv_file->close;
690   $self->{warnings} = $datev_csv->warnings;
691
692
693
694
695 =head1 DESCRIPTION
696
697 The parsing of the DATEV CSV is index based, therefore the correct
698 column must be present at the corresponding index, i.e.:
699  Index 2
700  Field Name   : Debit/Credit Label
701  Valid Values : 'S' or 'H'
702  Length:      : 1
703
704 The columns in C<@kivi_datev> are in the correct order and the
705 specific attributes are defined as a key value hash list for each entry.
706
707 The key names are the english translation according to the DATEV specs
708 (Leitfaden DATEV englisch).
709
710 The two attributes C<max_length> and C<type> are also set as specified
711 by the DATEV specs.
712
713 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
714 which is by convention the key name as generated by DATEV->generate_datev_data.
715 A value of C<'not yet implemented'> indicates that this field has no
716 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
717
718
719 =head1 SPECIFICATION
720
721 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
722 and CSV-Data lines.
723
724 =head2 FILENAME
725
726 The filename is subject to the following restrictions:
727 1. The filename must begin with the prefix DTVF_ or EXTF_.
728 2. The filename must end with .csv.
729
730 When exporting from or importing into DATEV applications, the filename is
731 marked with the prefix "DTVF_" (DATEV Format).
732 The prefix "DTVF_" is reserved for DATEV applications.
733 If you are using a third-party application to create a file in the DATEV format
734 that you want to import using batch processing, use the prefix "EXTF_"
735 (External Format).
736
737 =head2 File Structure
738
739 The file structure of the text file exported/imported is defined as follows
740
741 Line 1: Header (serves to assist in the interpretation of the following data)
742
743 Line 2: Headline (headline of the user data)
744
745 Line 3 – n: Records (user data)
746
747 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
748
749
750 =head2 Detailed Description
751
752 Line 1 must contain 11 fields.
753
754 Line 2 must contain 26 fields.
755
756 Line 3 - n:  must contain 116 fields, a smaller subset is mandatory.
757
758 =head1 FUNCTIONS
759
760 =over 4
761
762 =item new PARAMS
763
764 Constructor for CSV-DATEV export.
765 Checks mandantory params as described in section synopsis.
766
767 =item check_encoding
768
769 Helper function, returns true if a string is not empty and cp1252 encoded
770 For example some arabic utf-8 like  ݐ  will return false
771
772 =item header
773
774 Mostly all other header information are constants or metadata loaded
775 from SL::DB::Datev.pm.
776
777 Returns the first two entries for the header (see above: File Structure)
778 as an array.
779
780 =item kivitendo_to_datev
781
782 Returns the data structure C<@datev_data> as an array
783
784 =item _format_amount
785
786 Lightweight wrapper for form->format_amount.
787 Expects a number in kivitendo database format and returns the same number
788 in DATEV format.
789
790 =item first_day_of_fiscal_year
791
792 Takes a look at $self->to to  determine the first day of the fiscal year.
793
794 =item lines
795
796 Generates the CSV-Format data for the CSV DATEV export and returns
797 an 2-dimensional array as an array_ref.
798 May additionally return a second array_ref with warnings.
799
800 Requires the same date fields as the constructor for a valid DATEV header.
801
802 Furthermore we assume that the first day of the fiscal year is
803 the first of January and we cannot guarantee that our data in kivitendo
804 is locked, that means a booking cannot be modified after a defined (vat tax)
805 period.
806 Some validity checks (max_length and regex) will be done if the
807 data structure contains them and the field is defined.
808
809 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.
810
811 =back
812
813 =head1 TODO CAVEAT
814
815 One can circumevent the check of the warnings.quite easily,
816 becaus warnings are generated after the call to lines:
817
818   # WRONG usage
819   die if @{ $datev_csv->warnings };
820   somethin_with($datev_csv->lines);
821
822   # safe usage
823   my $lines = $datev_csv->lines;
824   die if @{ $datev_csv->warnings };
825   somethin_with($lines);