1 package SL::DATEV::CSV;
7 use Scalar::Util qw(looks_like_number);
11 use SL::Helper::DateTime;
12 use SL::Locale::String qw(t8);
14 use Rose::Object::MakeMethods::Generic (
15 scalar => [ qw(datev_lines from to locked warnings) ],
18 my @kivitendo_to_datev = (
20 kivi_datev_name => 'umsatz',
21 csv_header_name => t8('Transaction Value'),
25 input_check => sub { my ($input) = @_; return (looks_like_number($input) && length($input) <= 13 && $input > 0) },
26 formatter => \&_format_amount,
27 valid_check => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) },
30 kivi_datev_name => 'soll_haben_kennzeichen',
31 csv_header_name => t8('Debit/Credit Label'),
36 input_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
37 formatter => sub { my ($input) = @_; return $input eq 'H' ? 'H' : 'S' },
38 valid_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
41 kivi_datev_name => 'waehrung',
42 csv_header_name => t8('Transaction Value Currency Code'),
46 input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/^[A-Z]{3}$/) },
47 valid_check => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) },
50 kivi_datev_name => 'wechselkurs',
51 csv_header_name => t8('Exchange Rate'),
55 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) },
58 kivi_datev_name => 'not yet implemented',
59 sv_header_name => t8('Base Transaction Value'),
62 kivi_datev_name => 'not yet implemented',
63 csv_header_name => t8('Base Transaction Value Currency Code'),
66 kivi_datev_name => 'konto',
67 csv_header_name => t8('Account'),
71 input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
74 kivi_datev_name => 'gegenkonto',
75 csv_header_name => t8('Contra Account'),
79 input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
82 kivi_datev_name => 'buchungsschluessel',
83 csv_header_name => t8('Posting Key'),
87 input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
90 kivi_datev_name => 'datum',
91 csv_header_name => t8('Invoice Date'),
95 input_check => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
96 formatter => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') },
97 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
100 kivi_datev_name => 'belegfeld1',
101 csv_header_name => t8('Invoice Field 1'),
105 input_check => sub { my ($text) = @_; check_encoding($text); },
106 formatter => sub { my ($input) = @_; return substr($input, 0, 12) },
109 kivi_datev_name => 'belegfeld2',
110 csv_header_name => t8('Invoice Field 2'),
114 input_check => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
115 formatter => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') },
116 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
119 kivi_datev_name => 'not yet implemented',
120 csv_header_name => t8('Discount'),
124 kivi_datev_name => 'buchungsbes',
125 csv_header_name => t8('Posting Text'),
129 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
130 formatter => sub { my ($input) = @_; return substr($input, 0, 60) },
133 kivi_datev_name => 'not yet implemented',
136 kivi_datev_name => 'not yet implemented',
139 kivi_datev_name => 'not yet implemented',
142 kivi_datev_name => 'not yet implemented',
145 kivi_datev_name => 'not yet implemented',
148 kivi_datev_name => 'not yet implemented',
149 csv_header_name => t8('Link to invoice'),
150 max_length => 210, # DMS Application shortcut and GUID
152 # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
155 kivi_datev_name => 'not yet implemented',
158 kivi_datev_name => 'not yet implemented',
161 kivi_datev_name => 'not yet implemented',
164 kivi_datev_name => 'not yet implemented',
167 kivi_datev_name => 'not yet implemented',
170 kivi_datev_name => 'not yet implemented',
173 kivi_datev_name => 'not yet implemented',
176 kivi_datev_name => 'not yet implemented',
179 kivi_datev_name => 'not yet implemented',
182 kivi_datev_name => 'not yet implemented',
185 kivi_datev_name => 'not yet implemented',
188 kivi_datev_name => 'not yet implemented',
191 kivi_datev_name => 'not yet implemented',
194 kivi_datev_name => 'not yet implemented',
197 kivi_datev_name => 'not yet implemented',
200 kivi_datev_name => 'not yet implemented',
203 kivi_datev_name => 'kost1',
204 csv_header_name => t8('Cost Center'),
208 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
209 formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
212 kivi_datev_name => 'kost2',
213 csv_header_name => t8('Cost Center'),
217 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
218 formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
221 kivi_datev_name => 'not yet implemented',
222 csv_header_name => t8('KOST Quantity'),
225 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,9}$/) },
228 kivi_datev_name => 'ustid',
229 csv_header_name => t8('EU Member State and VAT ID Number'),
233 input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/[A-Z]{2}\w{5,13}/) },
234 formatter => sub { my ($input) = @_; $input =~ s/\s//g; return $input },
237 return 1 if ('' eq $ustid);
238 return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
247 croak(t8('We need a valid from date')) unless (ref $data{from} eq 'DateTime');
248 croak(t8('We need a valid to date')) unless (ref $data{to} eq 'DateTime');
249 croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
251 my $obj = bless {}, $class;
252 $obj->$_($data{$_}) for keys %data;
258 return undef unless $test;
260 decode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
267 sub _kivitendo_to_datev {
268 @kivitendo_to_datev, ({ kivi_datev_name => 'not yet implemented' }) x (116 - @kivitendo_to_datev);
276 # we can safely set these defaults
277 # TODO get length_of_accounts from DATEV.pm
278 my $today = DateTime->now_local;
279 my $created_on = $today->ymd('') . $today->hms('') . '000';
280 my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
281 my $default_curr = SL::DB::Default->get_default_currency;
283 # datev metadata and the string length limits
285 my %meta_datev_to_valid_length = (
291 my $datev = SL::DB::Manager::Datev->get_first();
293 while (my ($k, $v) = each %meta_datev_to_valid_length) {
294 next unless $datev->{$k};
295 $meta_datev{$k} = substr $datev->{$k}, 0, $v;
299 "EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki",
300 "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
301 $self->first_day_of_fiscal_year->ymd(''), $length_of_accounts,
302 $self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked,
303 $default_curr, "", "", "",""
305 push @header, [ @header_row_1 ];
307 # second header row, just the column names
308 push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ];
316 my (@array_of_datev, @warnings);
317 my @csv_columns = _kivitendo_to_datev();
319 foreach my $row (@{ $self->datev_lines }) {
320 my @current_datev_row;
322 # 1. check all datev_lines and see if we have a defined value
323 # 2. if we don't have a defined value set a default if exists
325 foreach my $column (@csv_columns) {
326 if ($column->{kivi_datev_name} eq 'not yet implemented') {
327 push @current_datev_row, '';
330 my $data = $row->{$column->{kivi_datev_name}};
331 if (!defined $data) {
332 if (defined $column->{default}) {
333 $data = $column->{default};
335 die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
338 # checkpoint a: no undefined data. All strict checks now!
339 if (exists $column->{input_check} && !$column->{input_check}->($data)) {
340 die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
341 $data, $column->{kivi_datev_name}, $row->{umsatz});
343 # checkpoint b: we can safely format the input
344 if ($column->{formatter}) {
345 $data = $column->{formatter}->($data);
347 # checkpoint c: all soft checks now, will pop up as a user warning
348 if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
349 push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
350 " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
352 push @current_datev_row, $data;
354 push @array_of_datev, \@current_datev_row;
356 $self->warnings(\@warnings);
357 return \@array_of_datev;
363 $::form->format_amount({ numberformat => '1000,00' }, @_);
366 sub first_day_of_fiscal_year {
367 $_[0]->to->clone->truncate(to => 'year');
378 SL::DATEV::CSV - kivitendo DATEV CSV Specification
382 use SL::DATEV qw(:CONSTANTS);
385 my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
386 my $enddate = DateTime->new(year => 2014, month => 9, day => 31);
387 my $datev = SL::DATEV->new(
388 exporttype => DATEV_ET_BUCHUNGEN,
389 format => DATEV_FORMAT_CSV,
393 $datev->generate_datev_data;
395 my $datev_csv = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines,
396 from => $datev->from,
398 locked => $datev->locked,
400 $datev_csv->header; # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array
401 $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
402 $datev_csv->warnings; # returns warnings
405 # The above object methods can be directly chained to a CSV export function, like this:
406 my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!";
407 $csv->print($csv_file, $_) for @{ $datev_csv->header };
408 $csv->print($csv_file, $_) for @{ $datev_csv->lines };
410 $self->{warnings} = $datev_csv->warnings;
417 The parsing of the DATEV CSV is index based, therefore the correct
418 column must be present at the corresponding index, i.e.:
420 Field Name : Debit/Credit Label
421 Valid Values : 'S' or 'H'
424 The columns in C<@kivi_datev> are in the correct order and the
425 specific attributes are defined as a key value hash list for each entry.
427 The key names are the english translation according to the DATEV specs
428 (Leitfaden DATEV englisch).
430 The two attributes C<max_length> and C<type> are also set as specified
433 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
434 which is by convention the key name as generated by DATEV->generate_datev_data.
435 A value of C<'not yet implemented'> indicates that this field has no
436 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
441 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
446 The filename is subject to the following restrictions:
447 1. The filename must begin with the prefix DTVF_ or EXTF_.
448 2. The filename must end with .csv.
450 When exporting from or importing into DATEV applications, the filename is
451 marked with the prefix "DTVF_" (DATEV Format).
452 The prefix "DTVF_" is reserved for DATEV applications.
453 If you are using a third-party application to create a file in the DATEV format
454 that you want to import using batch processing, use the prefix "EXTF_"
457 =head2 File Structure
459 The file structure of the text file exported/imported is defined as follows
461 Line 1: Header (serves to assist in the interpretation of the following data)
463 Line 2: Headline (headline of the user data)
465 Line 3 – n: Records (user data)
467 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
470 =head2 Detailed Description
472 Line 1 must contain 11 fields.
474 Line 2 must contain 26 fields.
476 Line 3 - n: must contain 116 fields, a smaller subset is mandatory.
484 Constructor for CSV-DATEV export.
485 Checks mandantory params as described in section synopsis.
489 Helper function, returns true if a string is not empty and cp1252 encoded
490 For example some arabic utf-8 like ݐ will return false
494 Mostly all other header information are constants or metadata loaded
495 from SL::DB::Datev.pm.
497 Returns the first two entries for the header (see above: File Structure)
500 =item kivitendo_to_datev
502 Returns the data structure C<@datev_data> as an array
506 Lightweight wrapper for form->format_amount.
507 Expects a number in kivitendo database format and returns the same number
510 =item first_day_of_fiscal_year
512 Takes a look at $self->to to determine the first day of the fiscal year.
516 Generates the CSV-Format data for the CSV DATEV export and returns
517 an 2-dimensional array as an array_ref.
518 May additionally return a second array_ref with warnings.
520 Requires the same date fields as the constructor for a valid DATEV header.
522 Furthermore we assume that the first day of the fiscal year is
523 the first of January and we cannot guarantee that our data in kivitendo
524 is locked, that means a booking cannot be modified after a defined (vat tax)
526 Some validity checks (max_length and regex) will be done if the
527 data structure contains them and the field is defined.
529 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.
535 One can circumevent the check of the warnings.quite easily,
536 becaus warnings are generated after the call to lines:
539 die if @{ $datev_csv->warnings };
540 somethin_with($datev_csv->lines);
543 my $lines = $datev_csv->lines;
544 die if @{ $datev_csv->warnings };
545 somethin_with($lines);