1 package SL::DATEV::CSV;
5 use SL::Locale::String qw(t8);
7 use SL::Helper::DateTime;
11 use Encode qw(decode);
14 my @kivitendo_to_datev = (
16 kivi_datev_name => 'umsatz',
17 csv_header_name => t8('Transaction Value'),
20 valid_check => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) },
23 kivi_datev_name => 'soll_haben_kennzeichen',
24 csv_header_name => t8('Debit/Credit Label'),
27 valid_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
30 kivi_datev_name => 'waehrung',
31 csv_header_name => t8('Transaction Value Currency Code'),
34 valid_check => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) },
37 kivi_datev_name => 'wechselkurs',
38 csv_header_name => t8('Exchange Rate'),
41 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) },
44 kivi_datev_name => 'not yet implemented',
45 csv_header_name => t8('Base Transaction Value'),
48 kivi_datev_name => 'not yet implemented',
49 csv_header_name => t8('Base Transaction Value Currency Code'),
52 kivi_datev_name => 'konto',
53 csv_header_name => t8('Account'),
54 max_length => 9, # May contain a maximum of 8 or 9 digits -> perldoc
56 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
59 kivi_datev_name => 'gegenkonto',
60 csv_header_name => t8('Contra Account'),
61 max_length => 9, # May contain a maximum of 8 or 9 digits -> perldoc
63 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
66 kivi_datev_name => 'buchungsschluessel',
67 csv_header_name => t8('Posting Key'),
70 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
73 kivi_datev_name => 'datum',
74 csv_header_name => t8('Invoice Date'),
77 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
80 kivi_datev_name => 'belegfeld1',
81 csv_header_name => t8('Invoice Field 1'),
84 valid_check => sub { my ($text) = @_; check_encoding($text); },
87 kivi_datev_name => 'not yet implemented',
88 csv_header_name => t8('Invoice Field 2'),
91 valid_check => sub { my ($check) = @_; return ($check =~ m/[ -~]{1,12}/) },
94 kivi_datev_name => 'not yet implemented',
95 csv_header_name => t8('Discount'),
99 kivi_datev_name => 'buchungsbes',
100 csv_header_name => t8('Posting Text'),
103 valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
106 kivi_datev_name => 'not yet implemented',
109 kivi_datev_name => 'not yet implemented',
112 kivi_datev_name => 'not yet implemented',
115 kivi_datev_name => 'not yet implemented',
118 kivi_datev_name => 'not yet implemented',
121 kivi_datev_name => 'not yet implemented',
122 csv_header_name => t8('Link to invoice'),
123 max_length => 210, # DMS Application shortcut and GUID
125 # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
128 kivi_datev_name => 'not yet implemented',
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',
149 kivi_datev_name => 'not yet implemented',
152 kivi_datev_name => 'not yet implemented',
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 => 'kost1',
177 csv_header_name => t8('Cost Center'),
180 valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
183 kivi_datev_name => 'kost2',
184 csv_header_name => t8('Cost Center'),
187 valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
190 kivi_datev_name => 'not yet implemented',
191 csv_header_name => t8('KOST Quantity'),
194 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,9}$/) },
197 kivi_datev_name => 'ustid',
198 csv_header_name => t8('EU Member State and VAT ID Number'),
203 return 1 unless defined($ustid);
204 return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
213 my $obj = bless {}, $class;
215 croak(t8('We need a valid from date')) unless (ref $data{from} eq 'DateTime');
216 croak(t8('We need a valid to date')) unless (ref $data{to} eq 'DateTime');
217 croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
219 # TODO no params here, better class variables/values
220 return _csv_buchungsexport(from => $data{from},
222 datev_lines => $data{datev_lines},
223 locked => $data{locked},
231 return undef unless $test;
233 decode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
240 sub _kivitendo_to_datev {
243 my $entries = scalar (@kivitendo_to_datev);
244 push @kivitendo_to_datev, { kivi_datev_name => 'not yet implemented' } for 1 .. (116 - $entries);
245 return @kivitendo_to_datev;
248 sub _generate_csv_header {
251 # we need from and to in YYYYDDMM
252 croak "Wrong format for from $params{from}" unless $params{from} =~ m/^[0-9]{8}$/;
253 croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/;
255 # who knows if we want locking and when our fiscal year starts
256 # croak "Wrong state of locking" unless $params{locked} =~ m/^(0|1)$/;
257 my $locked = defined($params{locked}) ? 1 : 0;
258 croak "No startdate of fiscal year" unless $params{first_day_of_fiscal_year} =~ m/^[0-9]{8}$/;
261 # we can safely set these defaults
262 my $today = DateTime->now(time_zone => "local");
263 my $created_on = $today->ymd('') . $today->hms('') . '000';
264 my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
265 my $default_curr = SL::DB::Default->get_default_currency;
267 # datev metadata and the string length limits
269 my %meta_datev_to_valid_length = (
275 my $datev = SL::DB::Manager::Datev->get_first();
277 while (my ($k, $v) = each %meta_datev_to_valid_length) {
278 $meta_datev{$k} = substr $datev->{$k}, 0, $v;
282 "EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki",
283 "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
284 $params{first_day_of_fiscal_year}, $length_of_accounts,
285 $params{from}, $params{to}, "", "", 1, "", $locked,
286 $default_curr, "", "", "",""
292 sub _csv_buchungsexport {
295 my @csv_columns = _kivitendo_to_datev();
296 my @csv_headers = _generate_csv_header(
297 from => $params{from}->ymd(''),
298 to => $params{to}->ymd(''),
299 first_day_of_fiscal_year => $params{to}->year . '0101',
300 locked => $params{locked}
306 push @array_of_datev, \@csv_headers;
307 push @array_of_datev, [ map { $_->{csv_header_name} } @csv_columns ];
310 foreach my $row (@{ $params{datev_lines} }) {
311 my @current_datev_row;
314 if ($row->{belegfeld1}) {
315 $row->{buchungsbes} = $row->{belegfeld1} if $row->{belegfeld1};
316 $row->{belegfeld1} = substr($row->{belegfeld1}, 0, 12);
317 $row->{buchungsbes} = substr($row->{buchungsbes}, 0, 60);
320 $row->{datum} = DateTime->from_kivitendo($row->{datum})->strftime('%d%m');
322 $row->{kost1} = substr($row->{kost1}, 0, 8) if $row->{kost1};
323 $row->{kost2} = substr($row->{kost2}, 0, 8) if $row->{kost2};
325 # , as decimal point and trim for UstID
326 $row->{umsatz} = _format_amount($row->{umsatz});
327 $row->{ustid} =~ s/\s//g if $row->{ustid}; # trim whitespace
329 foreach my $column (@csv_columns) {
330 if (exists $column->{max_length} && $column->{kivi_datev_name} ne 'not yet implemented') {
332 die "Incorrect length of field" if length($row->{ $column->{kivi_datev_name} }) > $column->{max_length};
334 if (exists $column->{valid_check} && $column->{kivi_datev_name} ne 'not yet implemented') {
335 # more checks, listed as user warnings
336 push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
337 " with amount '#3'",$row->{ $column->{kivi_datev_name} },
338 $column->{kivi_datev_name},$row->{umsatz})
339 unless ($column->{valid_check}->($row->{ $column->{kivi_datev_name} }));
341 push @current_datev_row, $row->{ $column->{kivi_datev_name} };
343 push @array_of_datev, \@current_datev_row;
345 return (\@array_of_datev, \@warnings);
349 $::form->format_amount({ numberformat => '1000,00' }, @_);
360 SL::DATEV::CSV - kivitendo DATEV CSV Specification
364 use SL::DATEV qw(:CONSTANTS);
367 my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
368 my $enddate = DateTime->new(year => 2014, month => 9, day => 31);
369 my $datev = SL::DATEV->new(
370 exporttype => DATEV_ET_BUCHUNGEN,
371 format => DATEV_FORMAT_CSV,
375 $datev->generate_datev_data;
377 my $datev_ref = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines,
378 from => $datev->from,
380 locked => $datev->locked,
385 The parsing of the DATEV CSV is index based, therefore the correct
386 column must be present at the corresponding index, i.e.:
388 Field Name : Debit/Credit Label
389 Valid Values : 'S' or 'H'
392 The columns in C<@kivi_datev> are in the correct order and the
393 specific attributes are defined as a key value hash list for each entry.
395 The key names are the english translation according to the DATEV specs
396 (Leitfaden DATEV englisch).
398 The two attributes C<max_length> and C<type> are also set as specified
401 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
402 which is by convention the key name as generated by DATEV->generate_datev_data.
403 A value of C<'not yet implemented'> indicates that this field has no
404 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
409 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
414 The filename is subject to the following restrictions:
415 1. The filename must begin with the prefix DTVF_ or EXTF_.
416 2. The filename must end with .csv.
418 When exporting from or importing into DATEV applications, the filename is
419 marked with the prefix "DTVF_" (DATEV Format).
420 The prefix "DTVF_" is reserved for DATEV applications.
421 If you are using a third-party application to create a file in the DATEV format
422 that you want to import using batch processing, use the prefix "EXTF_"
425 =head2 File Structure
427 The file structure of the text file exported/imported is defined as follows
429 Line 1: Header (serves to assist in the interpretation of the following data)
431 Line 2: Headline (headline of the user data)
433 Line 3 – n: Records (user data)
435 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
438 =head2 Detailed Description
440 Line 1 must contain 11 fields.
442 Line 2 must contain 26 fields.
444 Line 3 - n: must contain 116 fields, a smaller subset is mandatory.
452 Constructor for CSV-DATEV export.
453 Checks mandantory params as described in section synopsis.
457 Helper function, returns true if a string is not empty and cp1252 encoded
458 For example some arabic utf-8 like ݐ will return false
460 =item generate_csv_header(from => 'YYYYDDMM', to => 'YYYYDDMM', locked => 0,
461 first_day_of_fiscal_year => 'YYYYDDMM')
463 Mostly all other header information are constants or metadata loaded
464 from SL::DB::Datev.pm.
466 Returns the first two entries for the header (see above: File Structure)
469 All params are mandatory:
470 C<params{from}>, C<params{to}>
471 and C<params{first_day_of_fiscal_year}> have to be in YYYYDDMM date string
473 Furthermore C<params{locked}> is a perlish boolean.
476 =item kivitendo_to_datev
478 Returns the data structure C<@datev_data> as an array
482 Lightweight wrapper for form->format_amount.
483 Expects a number in kivitendo database format and returns the same number
486 =item _csv_buchungsexport
488 Generates the CSV-Format data for the CSV DATEV export and returns
489 an 2-dimensional array as an array_ref.
490 May additionally return a second array_ref with warnings.
492 Requires the same date fields as the constructor for a valid DATEV header.
494 Furthermore we assume that the first day of the fiscal year is
495 the first of January and we cannot guarantee that our data in kivitendo
496 is locked, that means a booking cannot be modified after a defined (vat tax)
498 Some validity checks (max_length and regex) will be done if the
499 data structure contains them and the field is defined.
501 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.