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