1 package SL::DATEV::CSV;
 
   5 use SL::Locale::String qw(t8);
 
  12 my @kivitendo_to_datev = (
 
  14                               kivi_datev_name => 'umsatz',
 
  15                               csv_header_name => t8('Transaction Value'),
 
  18                               valid_check     => sub { return (shift =~ m/^\d{1,10}(\,\d{1,2})?$/) },
 
  21                               kivi_datev_name => 'soll_haben_kennzeichen',
 
  22                               csv_header_name => t8('Debit/Credit Label'),
 
  25                               valid_check     => sub { return (shift =~ m/^(S|H)$/) },
 
  28                               kivi_datev_name => 'waehrung',
 
  29                               csv_header_name => t8('Transaction Value Currency Code'),
 
  32                               valid_check     => sub { return (shift =~ m/^[A-Z]{3}$/) },
 
  35                               kivi_datev_name => 'wechselkurs',
 
  36                               csv_header_name => t8('Exchange Rate'),
 
  39                               valid_check     => sub { return (shift =~ m/^[0-9]*\.?[0-9]*$/) },
 
  42                               kivi_datev_name => 'not yet implemented',
 
  43                               csv_header_name => t8('Base Transaction Value'),
 
  46                               kivi_datev_name => 'not yet implemented',
 
  47                               csv_header_name => t8('Base Transaction Value Currency Code'),
 
  50                               kivi_datev_name => 'konto',
 
  51                               csv_header_name => t8('Account'),
 
  52                               max_length      => 9, # May contain a maximum of 8 or 9 digits -> perldoc
 
  54                               valid_check     => sub { return (shift =~ m/^[0-9]{4,9}$/) },
 
  57                               kivi_datev_name => 'gegenkonto',
 
  58                               csv_header_name => t8('Contra Account'),
 
  59                               max_length      => 9, # May contain a maximum of 8 or 9 digits -> perldoc
 
  61                               valid_check     => sub { return (shift =~ m/^[0-9]{4,9}$/) },
 
  64                               kivi_datev_name => 'buchungsschluessel',
 
  65                               csv_header_name => t8('Posting Key'),
 
  68                               valid_check     => sub { return (shift =~ m/^[0-9]{0,2}$/) },
 
  71                               kivi_datev_name => 'datum',
 
  72                               csv_header_name => t8('Invoice Date'),
 
  75                               valid_check     => sub { return (shift =~ m/^[0-9]{4}$/) },
 
  78                               kivi_datev_name => 'belegfeld1',
 
  79                               csv_header_name => t8('Invoice Field 1'),
 
  82                               valid_check     => sub { my $text = shift; check_encoding($text); },
 
  85                               kivi_datev_name => 'not yet implemented',
 
  86                               csv_header_name => t8('Invoice Field 2'),
 
  89                               valid_check     => sub { return (shift =~ m/[ -~]{1,12}/) },
 
  92                               kivi_datev_name => 'not yet implemented',
 
  93                               csv_header_name => t8('Discount'),
 
  97                               kivi_datev_name => 'buchungsbes',
 
  98                               csv_header_name => t8('Posting Text'),
 
 101                               valid_check     => sub { my $text = shift; return 1 unless $text; check_encoding($text);  },
 
 104                               kivi_datev_name => 'not yet implemented',
 
 107                               kivi_datev_name => 'not yet implemented',
 
 110                               kivi_datev_name => 'not yet implemented',
 
 113                               kivi_datev_name => 'not yet implemented',
 
 116                               kivi_datev_name => 'not yet implemented',
 
 119                               kivi_datev_name => 'not yet implemented',
 
 120                               csv_header_name => t8('Link to invoice'),
 
 121                               max_length      => 210, # DMS Application shortcut and GUID
 
 123                                                       # "8DB85C02-4CC3-FF3E-06D7-7F87EEECCF3A".
 
 126                               kivi_datev_name => 'not yet implemented',
 
 129                               kivi_datev_name => 'not yet implemented',
 
 132                               kivi_datev_name => 'not yet implemented',
 
 135                               kivi_datev_name => 'not yet implemented',
 
 138                               kivi_datev_name => 'not yet implemented',
 
 141                               kivi_datev_name => 'not yet implemented',
 
 144                               kivi_datev_name => 'not yet implemented',
 
 147                               kivi_datev_name => 'not yet implemented',
 
 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 => 'kost1',
 
 175                               csv_header_name => t8('Cost Center'),
 
 178                               valid_check     => sub { my $text = shift; return 1 unless $text; check_encoding($text);  },
 
 181                               kivi_datev_name => 'kost2',
 
 182                               csv_header_name => t8('Cost Center'),
 
 185                               valid_check     => sub { my $text = shift; return 1 unless $text; check_encoding($text);  },
 
 188                               kivi_datev_name => 'not yet implemented',
 
 189                               csv_header_name => t8('KOST Quantity'),
 
 192                               valid_check     => sub { return (shift =~ m/^[0-9]{0,9}$/) },
 
 195                               kivi_datev_name => 'ustid',
 
 196                               csv_header_name => t8('EU Member State and VAT ID Number'),
 
 201                                                        return 1 unless defined($ustid);
 
 202                                                        return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
 
 208   use Encode qw( decode );
 
 209   # counter test: arabic doesnt work: ݐ
 
 211   return undef unless $test;
 
 213     decode('Windows-1252', $test, Encode::FB_CROAK|Encode::LEAVE_SRC);
 
 220 sub kivitendo_to_datev {
 
 223   my $entries = scalar (@kivitendo_to_datev);
 
 224   push @kivitendo_to_datev, { kivi_datev_name => 'not yet implemented' } for 1 .. (116 - $entries);
 
 225   return @kivitendo_to_datev;
 
 228 sub generate_csv_header {
 
 229   my ($self, %params)   = @_;
 
 231   # we need from and to in YYYYDDMM
 
 232   croak "Wrong format for from" unless $params{from} =~ m/^[0-9]{8}$/;
 
 233   croak "Wrong format for to"   unless $params{to} =~ m/^[0-9]{8}$/;
 
 235   # who knows if we want locking and when our fiscal year starts
 
 236   croak "Wrong state of locking"      unless $params{locked} =~ m/(0|1)/;
 
 237   croak "No startdate of fiscal year" unless $params{first_day_of_fiscal_year} =~ m/^[0-9]{8}$/;
 
 240   # we can safely set these defaults
 
 241   my $today              = DateTime->now(time_zone => "local");
 
 242   my $created_on         = $today->ymd('') . $today->hms('') . '000';
 
 243   my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
 
 244   my $default_curr       = SL::DB::Default->get_default_currency;
 
 246   # datev metadata and the string lenght limits
 
 248   my %meta_datev_to_valid_length = (
 
 254   my $datev = SL::DB::Manager::Datev->get_first();
 
 256   while (my ($k, $v) = each %meta_datev_to_valid_length) {
 
 257     $meta_datev{$k} = substr $datev->{$k}, 0, $v;
 
 261     "EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki",
 
 262     "kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
 
 263     $params{first_day_of_fiscal_year}, $length_of_accounts,
 
 264     $params{from}, $params{to}, "", "", 1, "", $params{locked},
 
 265     $default_curr, "", "", "",""
 
 278 SL::DATEV::CSV - kivitendo DATEV CSV Specification
 
 282 The parsing of the DATEV CSV is index based, therefore the correct
 
 283 column must be present at the corresponding index, i.e.:
 
 285  Field Name   : Debit/Credit Label
 
 286  Valid Values : 'S' or 'H'
 
 289 The columns in C<@kivi_datev> are in the correct order and the
 
 290 specific attributes are defined as a key value hash list for each entry.
 
 292 The key names are the english translation according to the DATEV specs
 
 293 (Leitfaden DATEV englisch).
 
 295 The two attributes C<max_length> and C<type> are also set as specified
 
 298 To link the structure to kivitendo data, each entry has the attribute C<kivi_datev_name>
 
 299 which is by convention the key name as generated by DATEV->generate_datev_data.
 
 300 A value of C<'not yet implemented'> indicates that this field has no
 
 301 corresponding kivitendo data and will be given an empty value by DATEV->csv_buchungsexport.
 
 306 This is an excerpt of the DATEV Format 2015 Specification for CSV-Header
 
 311 The filename is subject to the following restrictions:
 
 312 1. The filename must begin with the prefix DTVF_ or EXTF_.
 
 313 2. The filename must end with .csv.
 
 315 When exporting from or importing into DATEV applications, the filename is
 
 316 marked with the prefix "DTVF_" (DATEV Format).
 
 317 The prefix "DTVF_" is reserved for DATEV applications.
 
 318 If you are using a third-party application to create a file in the DATEV format
 
 319 that you want to import using batch processing, use the prefix "EXTF_"
 
 322 =head2 File Structure
 
 324 The file structure of the text file exported/imported is defined as follows
 
 326 Line 1: Header (serves to assist in the interpretation of the following data)
 
 328 Line 2: Headline (headline of the user data)
 
 330 Line 3 – n: Records (user data)
 
 332 For an valid example file take a look at doc/DATEV-2015/EXTF_Buchungsstapel.csv
 
 335 =head2 Detailed Description
 
 337 Line 1 must contain 11 fields.
 
 339 Line 2 must contain 26 fields.
 
 341 Line 3 - n:  must contain 116 fields, a smaller subset is mandatory.
 
 349 Helper function, returns true if a string is not empty and cp1252 encoded
 
 351 =item generate_csv_header(from => 'YYYYDDMM', to => 'YYYYDDMM', locked => 0,
 
 352                           first_day_of_fiscal_year => 'YYYYDDMM')
 
 354 Mostly all other header information are constants or metadata loaded
 
 355 from SL::DB::Datev.pm.
 
 357 Returns the first two entries for the header (see above: File Structure)
 
 360 All params are mandatory:
 
 361 C<params{from}>,  C<params{to}>
 
 362 and C<params{first_day_of_fiscal_year}> have to be in YYYYDDMM date string
 
 364 Furthermore C<params{locked}> needs to be a boolean in number format (0|1).
 
 367 =item kivitendo_to_datev
 
 369 Returns the data structure C<@datev_data> as an array