From: Moritz Bunkus Date: Fri, 3 Dec 2010 14:13:59 +0000 (+0100) Subject: Unterstützung für die XML-Ausgabe von Lastschriften X-Git-Tag: release-2.6.2beta1~73^2~5^2 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=c1716dbdeb0ba6d2cd9c708a2c54b9845e9532b4;p=kivitendo-erp.git Unterstützung für die XML-Ausgabe von Lastschriften --- diff --git a/SL/SEPA.pm b/SL/SEPA.pm index d4967964e..38341eccd 100644 --- a/SL/SEPA.pm +++ b/SL/SEPA.pm @@ -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)|; diff --git a/SL/SEPA/XML.pm b/SL/SEPA/XML.pm index ddac25e26..2c7f63253 100644 --- a/SL/SEPA/XML.pm +++ b/SL/SEPA/XML.pm @@ -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; diff --git a/SL/SEPA/XML/Transaction.pm b/SL/SEPA/XML/Transaction.pm index be444af8b..83a749e37 100644 --- a/SL/SEPA/XML/Transaction.pm +++ b/SL/SEPA/XML/Transaction.pm @@ -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 { diff --git a/bin/mozilla/sepa.pl b/bin/mozilla/sepa.pl index 2305cf850..7971b7d9d 100755 --- a/bin/mozilla/sepa.pl +++ b/bin/mozilla/sepa.pl @@ -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; diff --git a/locale/de/all b/locale/de/all index 60dfbc46e..55eb698a5 100644 --- a/locale/de/all +++ b/locale/de/all @@ -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üssen mindestens eine Benutzergruppe anlegen, ihr Zugriff auf die verschiedenen Funktionsbereiche von Lx-Office gewä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üssen neue Buchungsgruppen für alle Kombinationen aus Inventar-, Erlö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.',