1 package SL::DATEV::CSV;
5 use SL::Locale::String qw(t8);
7 use DateTime; # safer before the Helper
8 use SL::Helper::DateTime;
11 use Encode qw(decode);
12 use Scalar::Util qw(looks_like_number);
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) },
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 => 'not yet implemented',
110 csv_header_name => t8('Invoice Field 2'),
114 valid_check => sub { my ($check) = @_; return ($check =~ m/[ -~]{1,12}/) },
117 kivi_datev_name => 'not yet implemented',
118 csv_header_name => t8('Discount'),
122 kivi_datev_name => 'buchungsbes',
123 csv_header_name => t8('Posting Text'),
127 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
128 formatter => sub { my ($input) = @_; return substr($input, 0, 60) },
131 kivi_datev_name => 'not yet implemented',
134 kivi_datev_name => 'not yet implemented',
137 kivi_datev_name => 'not yet implemented',
140 kivi_datev_name => 'not yet implemented',
143 kivi_datev_name => 'not yet implemented',
146 kivi_datev_name => 'not yet implemented',
147 csv_header_name => t8('Link to invoice'),
148 max_length => 210, # DMS Application shortcut and GUID
150 # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
153 kivi_datev_name => 'not yet implemented',
156 kivi_datev_name => 'not yet implemented',
159 kivi_datev_name => 'not yet implemented',
162 kivi_datev_name => 'not yet implemented',
165 kivi_datev_name => 'not yet implemented',
168 kivi_datev_name => 'not yet implemented',
171 kivi_datev_name => 'not yet implemented',
174 kivi_datev_name => 'not yet implemented',
177 kivi_datev_name => 'not yet implemented',
180 kivi_datev_name => 'not yet implemented',
183 kivi_datev_name => 'not yet implemented',
186 kivi_datev_name => 'not yet implemented',
189 kivi_datev_name => 'not yet implemented',
192 kivi_datev_name => 'not yet implemented',
195 kivi_datev_name => 'not yet implemented',
198 kivi_datev_name => 'not yet implemented',
201 kivi_datev_name => 'kost1',
202 csv_header_name => t8('Cost Center'),
206 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
207 formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
210 kivi_datev_name => 'kost2',
211 csv_header_name => t8('Cost Center'),
215 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
216 formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
219 kivi_datev_name => 'not yet implemented',
220 csv_header_name => t8('KOST Quantity'),
223 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,9}$/) },
226 kivi_datev_name => 'ustid',
227 csv_header_name => t8('EU Member State and VAT ID Number'),
231 input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/[A-Z]{2}\w{5,13}/) },
232 formatter => sub { my ($input) = @_; return ($input =~ s/\s//g) },
235 return 1 if ('' eq $ustid);
236 return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
245 croak(t8('We need a valid from date')) unless (ref $data{from} eq 'DateTime');
246 croak(t8('We need a valid to date')) unless (ref $data{to} eq 'DateTime');
247 croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
249 my $obj = bless {}, $class;
250 $obj->$_($data{$_}) for keys %data;
256 return undef unless $test;
258 decode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
265 sub _kivitendo_to_datev {
268 my $entries = scalar (@kivitendo_to_datev);
269 push @kivitendo_to_datev, { kivi_datev_name => 'not yet implemented' } for 1 .. (116 - $entries);
270 return @kivitendo_to_datev;
278 # we can safely set these defaults
279 # TODO use Helper::DateTime and get lenght_of_accounts from DATEV.pm
280 my $today = DateTime->now(time_zone => "local");
281 my $created_on = $today->ymd('') . $today->hms('') . '000';
282 my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
283 my $default_curr = SL::DB::Default->get_default_currency;
285 # datev metadata and the string length limits
287 my %meta_datev_to_valid_length = (
293 my $datev = SL::DB::Manager::Datev->get_first();
295 while (my ($k, $v) = each %meta_datev_to_valid_length) {
296 next unless $datev->{$k};
297 $meta_datev{$k} = substr $datev->{$k}, 0, $v;
301 "EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki",
302 "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
303 $self->first_day_of_fiscal_year->ymd(''), $length_of_accounts,
304 $self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked,
305 $default_curr, "", "", "",""
307 push @header, [ @header_row_1 ];
309 # second header row, just the column names
310 push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ];
318 my (@array_of_datev, @warnings);
319 my @csv_columns = _kivitendo_to_datev();
321 foreach my $row (@{ $self->datev_lines }) {
322 my @current_datev_row;
324 # 1. check all datev_lines and see if we have a defined value
325 # 2. if we don't have a defined value set a default if exists
327 foreach my $column (@csv_columns) {
328 if ($column->{kivi_datev_name} eq 'not yet implemented') {
329 push @current_datev_row, '';
332 my $data = $row->{$column->{kivi_datev_name}};
333 if (!defined $data) {
334 if (defined $column->{default}) {
335 $data = $column->{default};
337 die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
340 # checkpoint a: no undefined data. All strict checks now!
341 if (exists $column->{input_check} && !$column->{input_check}->($data)) {
342 die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
343 $data, $column->{kivi_datev_name}, $row->{umsatz});
345 # checkpoint b: we can safely format the input
346 if ($column->{formatter}) {
347 $data = $column->{formatter}->($data);
349 # checkpoint c: all soft checks now, will pop up as a user warning
350 if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
351 push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
352 " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
354 push @current_datev_row, $data;
356 push @array_of_datev, \@current_datev_row;
358 $self->warnings(\@warnings);
359 return \@array_of_datev;
365 $::form->format_amount({ numberformat => '1000,00' }, @_);
368 sub first_day_of_fiscal_year {
369 $_[0]->to->clone->truncate(to => 'year');
380 SL::DATEV::CSV - kivitendo DATEV CSV Specification
384 use SL::DATEV qw(:CONSTANTS);
387 my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
388 my $enddate = DateTime->new(year => 2014, month => 9, day => 31);
389 my $datev = SL::DATEV->new(
390 exporttype => DATEV_ET_BUCHUNGEN,
391 format => DATEV_FORMAT_CSV,
395 $datev->generate_datev_data;
397 my $datev_csv = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines,
398 from => $datev->from,
400 locked => $datev->locked,
402 $datev_csv->header; # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array
403 $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
404 $datev_csv->warnings; # returns warnings
407 # The above object methods can be directly chained to a CSV export function, like this:
408 my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!";
409 $csv->print($csv_file, $_) for @{ $datev_csv->header };
410 $csv->print($csv_file, $_) for @{ $datev_csv->lines };
412 $self->{warnings} = $datev_csv->warnings;
419 The parsing of the DATEV CSV is index based, therefore the correct
420 column must be present at the corresponding index, i.e.:
422 Field Name : Debit/Credit Label
423 Valid Values : 'S' or 'H'
426 The columns in C<@kivi_datev> are in the correct order and the
427 specific attributes are defined as a key value hash list for each entry.
429 The key names are the english translation according to the DATEV specs
430 (Leitfaden DATEV englisch).
432 The two attributes C<max_length> and C<type> are also set as specified
435 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
436 which is by convention the key name as generated by DATEV->generate_datev_data.
437 A value of C<'not yet implemented'> indicates that this field has no
438 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
443 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
448 The filename is subject to the following restrictions:
449 1. The filename must begin with the prefix DTVF_ or EXTF_.
450 2. The filename must end with .csv.
452 When exporting from or importing into DATEV applications, the filename is
453 marked with the prefix "DTVF_" (DATEV Format).
454 The prefix "DTVF_" is reserved for DATEV applications.
455 If you are using a third-party application to create a file in the DATEV format
456 that you want to import using batch processing, use the prefix "EXTF_"
459 =head2 File Structure
461 The file structure of the text file exported/imported is defined as follows
463 Line 1: Header (serves to assist in the interpretation of the following data)
465 Line 2: Headline (headline of the user data)
467 Line 3 – n: Records (user data)
469 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
472 =head2 Detailed Description
474 Line 1 must contain 11 fields.
476 Line 2 must contain 26 fields.
478 Line 3 - n: must contain 116 fields, a smaller subset is mandatory.
486 Constructor for CSV-DATEV export.
487 Checks mandantory params as described in section synopsis.
491 Helper function, returns true if a string is not empty and cp1252 encoded
492 For example some arabic utf-8 like ݐ will return false
496 Mostly all other header information are constants or metadata loaded
497 from SL::DB::Datev.pm.
499 Returns the first two entries for the header (see above: File Structure)
502 =item kivitendo_to_datev
504 Returns the data structure C<@datev_data> as an array
508 Lightweight wrapper for form->format_amount.
509 Expects a number in kivitendo database format and returns the same number
512 =item first_day_of_fiscal_year
514 Takes a look at $self->to to determine the first day of the fiscal year.
518 Generates the CSV-Format data for the CSV DATEV export and returns
519 an 2-dimensional array as an array_ref.
520 May additionally return a second array_ref with warnings.
522 Requires the same date fields as the constructor for a valid DATEV header.
524 Furthermore we assume that the first day of the fiscal year is
525 the first of January and we cannot guarantee that our data in kivitendo
526 is locked, that means a booking cannot be modified after a defined (vat tax)
528 Some validity checks (max_length and regex) will be done if the
529 data structure contains them and the field is defined.
531 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.
538 Currently no effort has be done that _kivitenod_to_datev is only intializied once:
539 Therefore the second call may generate integrity faults:
541 my $datev_csv_1 = SL::DATEV::CSV->new(...)->lines;
542 my $datev_csv_2 = SL::DATEV::CSV->new(...)->lines;
544 Secondly one can circumevent the check of the warnings.quite easily,
545 becaus warnings are generated after the call to lines:
548 die if @{ $datev_csv->warnings };
549 somethin_with($datev_csv->lines);
552 my $lines = $datev_csv->lines;
553 die if @{ $datev_csv->warnings };
554 somethin_with($lines);