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);
15 my @kivitendo_to_datev = (
17 kivi_datev_name => 'umsatz',
18 csv_header_name => t8('Transaction Value'),
22 input_check => sub { my ($input) = @_; return (looks_like_number($input) && length($input) <= 13) },
23 formatter => \&_format_amount,
24 valid_check => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) },
27 kivi_datev_name => 'soll_haben_kennzeichen',
28 csv_header_name => t8('Debit/Credit Label'),
33 input_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
34 formatter => sub { my ($input) = @_; return $input eq 'H' ? 'H' : 'S' },
35 valid_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
38 kivi_datev_name => 'waehrung',
39 csv_header_name => t8('Transaction Value Currency Code'),
43 input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/^[A-Z]{3}$/) },
44 valid_check => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) },
47 kivi_datev_name => 'wechselkurs',
48 csv_header_name => t8('Exchange Rate'),
52 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) },
55 kivi_datev_name => 'not yet implemented',
56 sv_header_name => t8('Base Transaction Value'),
59 kivi_datev_name => 'not yet implemented',
60 csv_header_name => t8('Base Transaction Value Currency Code'),
63 kivi_datev_name => 'konto',
64 csv_header_name => t8('Account'),
68 input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
71 kivi_datev_name => 'gegenkonto',
72 csv_header_name => t8('Contra Account'),
76 input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
79 kivi_datev_name => 'buchungsschluessel',
80 csv_header_name => t8('Posting Key'),
84 input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
87 kivi_datev_name => 'datum',
88 csv_header_name => t8('Invoice Date'),
92 input_check => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
93 formatter => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') },
94 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
97 kivi_datev_name => 'belegfeld1',
98 csv_header_name => t8('Invoice Field 1'),
102 input_check => sub { my ($text) = @_; check_encoding($text); },
103 formatter => sub { my ($input) = @_; return substr($input, 0, 12) },
106 kivi_datev_name => 'not yet implemented',
107 csv_header_name => t8('Invoice Field 2'),
111 valid_check => sub { my ($check) = @_; return ($check =~ m/[ -~]{1,12}/) },
114 kivi_datev_name => 'not yet implemented',
115 csv_header_name => t8('Discount'),
119 kivi_datev_name => 'buchungsbes',
120 csv_header_name => t8('Posting Text'),
124 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
125 formatter => sub { my ($input) = @_; return substr($input, 0, 60) },
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',
144 csv_header_name => t8('Link to invoice'),
145 max_length => 210, # DMS Application shortcut and GUID
147 # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
150 kivi_datev_name => 'not yet implemented',
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 => 'kost1',
199 csv_header_name => t8('Cost Center'),
203 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
204 formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
207 kivi_datev_name => 'kost2',
208 csv_header_name => t8('Cost Center'),
212 input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
213 formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
216 kivi_datev_name => 'not yet implemented',
217 csv_header_name => t8('KOST Quantity'),
220 valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,9}$/) },
223 kivi_datev_name => 'ustid',
224 csv_header_name => t8('EU Member State and VAT ID Number'),
228 input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/[A-Z]{2}\w{5,13}/) },
229 formatter => sub { my ($input) = @_; return ($input =~ s/\s//g) },
232 return 1 if ('' eq $ustid);
233 return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
242 my $obj = bless {}, $class;
244 croak(t8('We need a valid from date')) unless (ref $data{from} eq 'DateTime');
245 croak(t8('We need a valid to date')) unless (ref $data{to} eq 'DateTime');
246 croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
248 # TODO no params here, better class variables/values
249 return _csv_buchungsexport(from => $data{from},
251 datev_lines => $data{datev_lines},
252 locked => $data{locked},
260 return undef unless $test;
262 decode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
269 sub _kivitendo_to_datev {
272 my $entries = scalar (@kivitendo_to_datev);
273 push @kivitendo_to_datev, { kivi_datev_name => 'not yet implemented' } for 1 .. (116 - $entries);
274 return @kivitendo_to_datev;
277 sub _generate_csv_header {
280 # we need from and to in YYYYDDMM
281 croak "Wrong format for from $params{from}" unless $params{from} =~ m/^[0-9]{8}$/;
282 croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/;
284 # who knows if we want locking and when our fiscal year starts
285 # croak "Wrong state of locking" unless $params{locked} =~ m/^(0|1)$/;
286 my $locked = defined($params{locked}) ? 1 : 0;
287 croak "No startdate of fiscal year" unless $params{first_day_of_fiscal_year} =~ m/^[0-9]{8}$/;
290 # we can safely set these defaults
291 my $today = DateTime->now(time_zone => "local");
292 my $created_on = $today->ymd('') . $today->hms('') . '000';
293 my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
294 my $default_curr = SL::DB::Default->get_default_currency;
296 # datev metadata and the string length limits
298 my %meta_datev_to_valid_length = (
304 my $datev = SL::DB::Manager::Datev->get_first();
306 while (my ($k, $v) = each %meta_datev_to_valid_length) {
307 next unless $datev->{$k};
308 $meta_datev{$k} = substr $datev->{$k}, 0, $v;
312 "EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki",
313 "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
314 $params{first_day_of_fiscal_year}, $length_of_accounts,
315 $params{from}, $params{to}, "", "", 1, "", $locked,
316 $default_curr, "", "", "",""
322 sub _csv_buchungsexport {
325 my @csv_columns = _kivitendo_to_datev();
326 my @csv_headers = _generate_csv_header(
327 from => $params{from}->ymd(''),
328 to => $params{to}->ymd(''),
329 first_day_of_fiscal_year => $params{to}->year . '0101',
330 locked => $params{locked}
336 push @array_of_datev, \@csv_headers;
337 push @array_of_datev, [ map { $_->{csv_header_name} } @csv_columns ];
340 foreach my $row (@{ $params{datev_lines} }) {
341 my @current_datev_row;
343 # 1. check all datev_lines and see if we have a defined value
344 # 2. if we don't have a defined value set a default if exists
346 foreach my $column (@csv_columns) {
347 if ($column->{kivi_datev_name} eq 'not yet implemented') {
348 push @current_datev_row, '';
351 my $data = $row->{$column->{kivi_datev_name}};
352 if (!defined $data) {
353 if (defined $column->{default}) {
354 $data = $column->{default};
356 die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
359 # checkpoint a: no undefined data. All strict checks now!
360 if (exists $column->{input_check}) {
361 die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
362 $data, $column->{kivi_datev_name}, $row->{umsatz})
363 unless $column->{input_check}->($data);
365 # checkpoint b: we can safely format the input
366 if ($column->{formatter}) {
367 $data = $column->{formatter}->($data);
369 # checkpoint c: all soft checks now, will pop up as a user warning
370 if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
371 push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
372 " with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
374 push @current_datev_row, $data;
376 push @array_of_datev, \@current_datev_row;
378 return (\@array_of_datev, \@warnings);
382 $::form->format_amount({ numberformat => '1000,00' }, @_);
393 SL::DATEV::CSV - kivitendo DATEV CSV Specification
397 use SL::DATEV qw(:CONSTANTS);
400 my $startdate = DateTime->new(year => 2014, month => 9, day => 1);
401 my $enddate = DateTime->new(year => 2014, month => 9, day => 31);
402 my $datev = SL::DATEV->new(
403 exporttype => DATEV_ET_BUCHUNGEN,
404 format => DATEV_FORMAT_CSV,
408 $datev->generate_datev_data;
410 my $datev_ref = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines,
411 from => $datev->from,
413 locked => $datev->locked,
418 The parsing of the DATEV CSV is index based, therefore the correct
419 column must be present at the corresponding index, i.e.:
421 Field Name : Debit/Credit Label
422 Valid Values : 'S' or 'H'
425 The columns in C<@kivi_datev> are in the correct order and the
426 specific attributes are defined as a key value hash list for each entry.
428 The key names are the english translation according to the DATEV specs
429 (Leitfaden DATEV englisch).
431 The two attributes C<max_length> and C<type> are also set as specified
434 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
435 which is by convention the key name as generated by DATEV->generate_datev_data.
436 A value of C<'not yet implemented'> indicates that this field has no
437 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
442 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
447 The filename is subject to the following restrictions:
448 1. The filename must begin with the prefix DTVF_ or EXTF_.
449 2. The filename must end with .csv.
451 When exporting from or importing into DATEV applications, the filename is
452 marked with the prefix "DTVF_" (DATEV Format).
453 The prefix "DTVF_" is reserved for DATEV applications.
454 If you are using a third-party application to create a file in the DATEV format
455 that you want to import using batch processing, use the prefix "EXTF_"
458 =head2 File Structure
460 The file structure of the text file exported/imported is defined as follows
462 Line 1: Header (serves to assist in the interpretation of the following data)
464 Line 2: Headline (headline of the user data)
466 Line 3 – n: Records (user data)
468 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
471 =head2 Detailed Description
473 Line 1 must contain 11 fields.
475 Line 2 must contain 26 fields.
477 Line 3 - n: must contain 116 fields, a smaller subset is mandatory.
485 Constructor for CSV-DATEV export.
486 Checks mandantory params as described in section synopsis.
490 Helper function, returns true if a string is not empty and cp1252 encoded
491 For example some arabic utf-8 like ݐ will return false
493 =item generate_csv_header(from => 'YYYYDDMM', to => 'YYYYDDMM', locked => 0,
494 first_day_of_fiscal_year => 'YYYYDDMM')
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 All params are mandatory:
503 C<params{from}>, C<params{to}>
504 and C<params{first_day_of_fiscal_year}> have to be in YYYYDDMM date string
506 Furthermore C<params{locked}> is a perlish boolean.
509 =item kivitendo_to_datev
511 Returns the data structure C<@datev_data> as an array
515 Lightweight wrapper for form->format_amount.
516 Expects a number in kivitendo database format and returns the same number
519 =item _csv_buchungsexport
521 Generates the CSV-Format data for the CSV DATEV export and returns
522 an 2-dimensional array as an array_ref.
523 May additionally return a second array_ref with warnings.
525 Requires the same date fields as the constructor for a valid DATEV header.
527 Furthermore we assume that the first day of the fiscal year is
528 the first of January and we cannot guarantee that our data in kivitendo
529 is locked, that means a booking cannot be modified after a defined (vat tax)
531 Some validity checks (max_length and regex) will be done if the
532 data structure contains them and the field is defined.
534 To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.