Unterstützung für die XML-Ausgabe von Lastschriften
authorMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 3 Dec 2010 14:13:59 +0000 (15:13 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 3 Dec 2010 14:13:59 +0000 (15:13 +0100)
SL/SEPA.pm
SL/SEPA/XML.pm
SL/SEPA/XML/Transaction.pm
bin/mozilla/sepa.pl
locale/de/all

index d496796..38341ec 100644 (file)
@@ -178,7 +178,7 @@ sub retrieve_export {
     my ($columns, $joins);
 
     if ($params{details}) {
-      $columns = qq|, arap.invnumber, arap.invoice, vc.name AS vc_name, c.accno AS chart_accno, c.description AS chart_description|;
+      $columns = qq|, arap.invnumber, arap.invoice, arap.transdate AS reference_date, vc.name AS vc_name, c.accno AS chart_accno, c.description AS chart_description|;
       $joins   = qq|LEFT JOIN ${arap} arap ON (sei.${arap}_id = arap.id)
                     LEFT JOIN ${vc} vc     ON (arap.${vc}_id  = vc.id)
                     LEFT JOIN chart c      ON (sei.chart_id   = c.id)|;
index ddac25e..2c7f632 100644 (file)
@@ -32,14 +32,15 @@ sub _init {
   $self->{src_charset}  = 'UTF-8';
   $self->{grouped}      = 0;
 
-  map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company message_id grouped);
+  map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company creditor_id message_id grouped collection);
 
   $self->{iconv} = SL::Iconv->new($self->{src_charset}, "UTF-8") || croak "Unsupported source charset $self->{src_charset}.";
 
   my $missing_parameter = first { !$self->{$_} } qw(company message_id);
   croak "Missing parameter: $missing_parameter" if ($missing_parameter);
+  croak "Missing parameter: creditor_id"        if !$self->{creditor_id} && $self->{collection};
 
-  map { $self->{$_} = $self->_replace_special_chars($self->{iconv}->convert($self->{$_})) } qw(company message_id);
+  map { $self->{$_} = $self->_replace_special_chars($self->{iconv}->convert($self->{$_})) } qw(company message_id creditor_id);
 }
 
 sub add_transaction {
@@ -77,7 +78,7 @@ sub _format_amount {
   my $self   = shift;
   my $amount = shift;
 
-  return sprintf '%d.%02d', int($amount), int($amount * 100) % 100;
+  return sprintf '%.02f', $amount;
 }
 
 sub _group_transactions {
@@ -116,19 +117,27 @@ sub to_xml {
                                 DATA_INDENT => 2,
                                 ENCODING    => 'utf-8');
 
-  my @now        = localtime;
-  my $time_zone  = strftime "%z", @now;
-  my $now_str    = strftime('%Y-%m-%dT%H:%M:%S', @now) . substr($time_zone, 0, 3) . ':' . substr($time_zone, 3, 2);
+  my @now       = localtime;
+  my $time_zone = strftime "%z", @now;
+  my $now_str   = strftime('%Y-%m-%dT%H:%M:%S', @now) . substr($time_zone, 0, 3) . ':' . substr($time_zone, 3, 2);
+
+  my $is_coll   = $self->{collection};
+  my $cd_src    = $is_coll ? 'Cdtr' : 'Dbtr';
+  my $cd_dst    = $is_coll ? 'Dbtr' : 'Cdtr';
+  my $pain_id   = $is_coll ? 'pain.008.002.01' : 'pain.001.001.02';
+  my $pain_elmt = $is_coll ? 'pain.008.001.01' : 'pain.001.001.02';
+  my @pii_base  = (strftime('PII%Y%m%d%H%M%S', @now), rand(1000000000));
 
   my $grouped_transactions = $self->_group_transactions();
 
   $xml->xmlDecl();
+
   $xml->startTag('Document',
-                 'xmlns'              => 'urn:sepade:xsd:pain.001.001.02.grp',
+                 'xmlns'              => "urn:swift:xsd:\$${pain_id}",
                  'xmlns:xsi'          => 'http://www.w3.org/2001/XMLSchema-instance',
-                 'xsi:schemaLocation' => 'urn:sepade:xsd:pain.001.001.02.grp pain.001.001.02.grp.xsd');
+                 'xsi:schemaLocation' => "urn:swift:xsd:\$${pain_id} ${pain_id}.xsd");
 
-  $xml->startTag('pain.001.001.02');
+  $xml->startTag($pain_elmt);
 
   $xml->startTag('GrpHdr');
   $xml->dataElement('MsgId', encode('UTF-8', substr($self->{message_id}, 0, 35)));
@@ -148,73 +157,111 @@ sub to_xml {
     my $master_transaction = $transaction_group->{transactions}->[0];
 
     $xml->startTag('PmtInf');
-    $xml->dataElement('PmtMtd', 'TRF');
+    if ($is_coll) {
+      $xml->dataElement('PmtInfId', sprintf('%s%010d', @pii_base));
+      $pii_base[1]++;
+    }
+    $xml->dataElement('PmtMtd', $is_coll ? 'DD' : 'TRF');
 
     $xml->startTag('PmtTpInf');
     $xml->startTag('SvcLvl');
     $xml->dataElement('Cd', 'SEPA');
     $xml->endTag('SvcLvl');
+
+    if ($is_coll) {
+      $xml->startTag('LclInstrm');
+      $xml->dataElement('Cd', 'CORE');
+      $xml->endTag('LclInstrm');
+      $xml->dataElement('SeqTp', 'OOFF');
+    }
     $xml->endTag('PmtTpInf');
 
-    $xml->dataElement('ReqdExctnDt', $master_transaction->get('execution_date'));
-    $xml->startTag('Dbtr');
+    $xml->dataElement($is_coll ? 'ReqdColltnDt' : 'ReqdExctnDt', $master_transaction->get('execution_date'));
+    $xml->startTag($cd_src);
     $xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
-    $xml->endTag('Dbtr');
+    $xml->endTag($cd_src);
 
-    $xml->startTag('DbtrAcct');
+    $xml->startTag($cd_src . 'Acct');
     $xml->startTag('Id');
     $xml->dataElement('IBAN', $master_transaction->get('src_iban', 34));
     $xml->endTag('Id');
-    $xml->endTag('DbtrAcct');
+    $xml->endTag($cd_src . 'Acct');
 
-    $xml->startTag('DbtrAgt');
+    $xml->startTag($cd_src . 'Agt');
     $xml->startTag('FinInstnId');
     $xml->dataElement('BIC', $master_transaction->get('src_bic', 20));
     $xml->endTag('FinInstnId');
-    $xml->endTag('DbtrAgt');
+    $xml->endTag($cd_src . 'Agt');
 
     $xml->dataElement('ChrgBr', 'SLEV');
 
     foreach my $transaction (@{ $transaction_group->{transactions} }) {
-      $xml->startTag('CdtTrfTxInf');
+      $xml->startTag($is_coll ? 'DrctDbtTxInf' : 'CdtTrfTxInf');
 
       $xml->startTag('PmtId');
       $xml->dataElement('EndToEndId', $transaction->get('end_to_end_id', 35));
       $xml->endTag('PmtId');
 
-      $xml->startTag('Amt');
-      $xml->startTag('InstdAmt', 'Ccy' => 'EUR');
-      $xml->characters($self->_format_amount($transaction->{amount}));
-      $xml->endTag('InstdAmt');
-      $xml->endTag('Amt');
-
-      $xml->startTag('CdtrAgt');
+      if ($is_coll) {
+        $xml->startTag('InstdAmt', 'Ccy' => 'EUR');
+        $xml->characters($self->_format_amount($transaction->{amount}));
+        $xml->endTag('InstdAmt');
+
+        $xml->startTag('DrctDbtTx');
+
+        $xml->startTag('MndtRltdInf');
+        $xml->dataElement('MndtId', $transaction->get('reference', 35));
+        $xml->dataElement('DtOfSgntr', $transaction->get('reference_date', 2010-12-02));
+        $xml->endTag('MndtRltdInf');
+
+        $xml->startTag('CdtrSchmeId');
+        $xml->startTag('Id');
+        $xml->startTag('PrvtId');
+        $xml->startTag('OthrId');
+        $xml->dataElement('Id', encode('UTF-8', substr($self->{creditor_id}, 0, 35)));
+        $xml->dataElement('IdTp', 'SEPA');
+        $xml->endTag('OthrId');
+        $xml->endTag('PrvtId');
+        $xml->endTag('Id');
+        $xml->endTag('CdtrSchmeId');
+
+        $xml->endTag('DrctDbtTx');
+
+      } else {
+        $xml->startTag('Amt');
+        $xml->startTag('InstdAmt', 'Ccy' => 'EUR');
+        $xml->characters($self->_format_amount($transaction->{amount}));
+        $xml->endTag('InstdAmt');
+        $xml->endTag('Amt');
+      }
+
+      $xml->startTag("${cd_dst}Agt");
       $xml->startTag('FinInstnId');
       $xml->dataElement('BIC', $transaction->get('dst_bic', 20));
       $xml->endTag('FinInstnId');
-      $xml->endTag('CdtrAgt');
+      $xml->endTag("${cd_dst}Agt");
 
-      $xml->startTag('Cdtr');
-      $xml->dataElement('Nm', $transaction->get('recipient', 70));
-      $xml->endTag('Cdtr');
+      $xml->startTag("${cd_dst}");
+      $xml->dataElement('Nm', $transaction->get('company', 70));
+      $xml->endTag("${cd_dst}");
 
-      $xml->startTag('CdtrAcct');
+      $xml->startTag("${cd_dst}Acct");
       $xml->startTag('Id');
       $xml->dataElement('IBAN', $transaction->get('dst_iban', 34));
       $xml->endTag('Id');
-      $xml->endTag('CdtrAcct');
+      $xml->endTag("${cd_dst}Acct");
 
       $xml->startTag('RmtInf');
       $xml->dataElement('Ustrd', $transaction->get('reference', 140));
       $xml->endTag('RmtInf');
 
-      $xml->endTag('CdtTrfTxInf');
+      $xml->endTag($is_coll ? 'DrctDbtTxInf' : 'CdtTrfTxInf');
     }
 
     $xml->endTag('PmtInf');
   }
 
-  $xml->endTag('pain.001.001.02');
+  $xml->endTag($pain_elmt);
   $xml->endTag('Document');
 
   return $output;
index be444af..83a749e 100644 (file)
@@ -25,7 +25,7 @@ sub _init {
   $self->{sepa}  = $params{sepa};
   delete $params{sepa};
 
-  my $missing_parameter = first { !$params{$_} } qw(src_iban src_bic dst_iban dst_bic recipient reference amount end_to_end_id);
+  my $missing_parameter = first { !$params{$_} } qw(src_iban src_bic dst_iban dst_bic company reference amount end_to_end_id);
   croak "Missing parameter: $missing_parameter" if ($missing_parameter);
 
   $params{end_to_end_id}  ||= 'NOTPROVIDED';
@@ -35,7 +35,7 @@ sub _init {
 
   map { $self->{$_} = $self->{sepa}->{iconv}->convert($params{$_})       } keys %params;
   map { $self->{$_} =~ s/\s+//g                                          } qw(src_iban src_bic dst_iban dst_bic);
-  map { $self->{$_} = $self->{sepa}->_replace_special_chars($self->{$_}) } qw(recipient reference end_to_end_id);
+  map { $self->{$_} = $self->{sepa}->_replace_special_chars($self->{$_}) } qw(company reference end_to_end_id);
 }
 
 sub get {
index 2305cf8..7971b7d 100755 (executable)
@@ -425,6 +425,7 @@ sub bank_transfer_payment_list_as_pdf {
   $main::lxdebug->leave_sub();
 }
 
+# TODO
 sub bank_transfer_download_sepa_xml {
   $main::lxdebug->enter_sub();
 
@@ -432,11 +433,16 @@ sub bank_transfer_download_sepa_xml {
   my $myconfig = \%main::myconfig;
   my $locale   =  $main::locale;
   my $cgi      =  $main::cgi;
+  my $vc       = $form->{vc} eq 'customer' ? 'customer' : 'vendor';
 
   if (!$myconfig->{company}) {
     $form->show_generic_error($locale->text('You have to enter a company name in your user preferences (see the "Program" menu, "Preferences").'), 'back_button' => 1);
   }
 
+  if (($vc eq 'customer') && !$myconfig->{sepa_creditor_id}) {
+    $form->show_generic_error($locale->text('You have to enter the SEPA creditor ID in your user preferences (see the "Program" menu, "Preferences").'), 'back_button' => 1);
+  }
+
   my @ids;
   if ($form->{mode} && ($form->{mode} eq 'multi')) {
      @ids = map $_->{id}, grep { $_->{selected} } @{ $form->{exports} || [] };
@@ -452,7 +458,7 @@ sub bank_transfer_download_sepa_xml {
   my @items = ();
 
   foreach my $id (@ids) {
-    my $export = SL::SEPA->retrieve_export('id' => $id, 'details' => 1);
+    my $export = SL::SEPA->retrieve_export('id' => $id, 'details' => 1, vc => $vc);
     push @items, grep { !$_->{executed} } @{ $export->{items} } if ($export && !$export->{closed});
   }
 
@@ -463,9 +469,11 @@ sub bank_transfer_download_sepa_xml {
   my $message_id = strftime('MSG%Y%m%d%H%M%S', localtime) . sprintf('%06d', $$);
 
   my $sepa_xml   = SL::SEPA::XML->new('company'     => $myconfig->{company},
+                                      'creditor_id' => $myconfig->{sepa_creditor_id},
                                       'src_charset' => $main::dbcharset || 'ISO-8859-15',
                                       'message_id'  => $message_id,
                                       'grouped'     => 1,
+                                      'collection'  => $vc eq 'customer',
     );
 
   foreach my $item (@items) {
@@ -475,13 +483,19 @@ sub bank_transfer_download_sepa_xml {
       $requested_execution_date = sprintf '%04d-%02d-%02d', $yy, $mm, $dd;
     }
 
+    if ($vc eq 'customer') {
+      my ($yy, $mm, $dd)      = $locale->parse_date($myconfig, $item->{reference_date});
+      $item->{reference_date} = sprintf '%04d-%02d-%02d', $yy, $mm, $dd;
+    }
+
     $sepa_xml->add_transaction({ 'src_iban'       => $item->{our_iban},
                                  'src_bic'        => $item->{our_bic},
-                                 'dst_iban'       => $item->{vendor_iban},
-                                 'dst_bic'        => $item->{vendor_bic},
-                                 'recipient'      => $item->{vendor_name},
+                                 'dst_iban'       => $item->{vc_iban},
+                                 'dst_bic'        => $item->{vc_bic},
+                                 'company'        => $item->{vc_name},
                                  'amount'         => $item->{amount},
                                  'reference'      => $item->{reference},
+                                 'reference_date' => $item->{reference_date},
                                  'execution_date' => $requested_execution_date,
                                  'end_to_end_id'  => $item->{end_to_end_id} });
   }
@@ -489,7 +503,7 @@ sub bank_transfer_download_sepa_xml {
   my $xml = $sepa_xml->to_xml();
 
   print $cgi->header('-type'                => 'application/octet-stream',
-                     '-content-disposition' => 'attachment; filename="SEPA_' . $message_id . '.cct"',
+                     '-content-disposition' => 'attachment; filename="SEPA_' . $message_id . ($vc eq 'customer' ? '.cdd' : '.cct') . '"',
                      '-content-length'      => length $xml);
   print $xml;
 
index 60dfbc4..55eb698 100644 (file)
@@ -1923,6 +1923,7 @@ $self->{texts} = {
   'You have to create at least one group, grant it access to Lx-Office\'s functions and assign users to it.' => 'Sie m&uuml;ssen mindestens eine Benutzergruppe anlegen, ihr Zugriff auf die verschiedenen Funktionsbereiche von Lx-Office gew&auml;hren und Benutzer dieser Gruppe zuordnen.',
   'You have to create new Buchungsgruppen for all the combinations of inventory, income and expense accounts that have been used already.' => 'Sie m&uuml;ssen neue Buchungsgruppen f&uuml;r alle Kombinationen aus Inventar-, Erl&ouml;s- und Aufwandskonto, die bereits benutzt wurden.',
   'You have to enter a company name in your user preferences (see the "Program" menu, "Preferences").' => 'Sie müssen einen Firmennamen in Ihren Einstellungen angeben (siehe Menü "Programm", "Einstellungen").',
+  'You have to enter the SEPA creditor ID in your user preferences (see the "Program" menu, "Preferences").' => 'Sie müssen die SEPA-Kreditoren-Identifikation in Ihren Einstellungen angeben (siehe Menü "Programm", "Einstellungen").',
   'You have to fill in at least an account number, the bank code, the IBAN and the BIC.' => 'Sie müssen zumindest die Kontonummer, die Bankleitzahl, die IBAN und den BIC angeben.',
   'You have to specify a department.' => 'Sie müssen eine Abteilung wählen.',
   'You have to specify an execution date for each antry.' => 'Sie müssen für jeden zu buchenden Eintrag ein Ausführungsdatum angeben.',