Revision d62a113d
Von Moritz Bunkus vor 11 Monaten hinzugefügt
SL/SEPA/XML.pm | ||
---|---|---|
|
||
use Carp;
|
||
use Encode;
|
||
use List::Util qw(first sum);
|
||
use List::MoreUtils qw(any);
|
||
use List::Util qw(first sum);
|
||
use POSIX qw(strftime);
|
||
use XML::Writer;
|
||
|
||
use SL::Iconv;
|
||
use SL::SEPA::XML::Transaction;
|
||
|
||
use constant V3_0_0 => 3_000_000;
|
||
use constant V3_8_0 => 3_800_000;
|
||
|
||
sub get_supported_versions {
|
||
return (
|
||
versions => [
|
||
{ id => V3_0_0(), description => "v3.0.0" },
|
||
{ id => V3_8_0(), description => "v3.8.0" },
|
||
],
|
||
default => get_default_version(),
|
||
);
|
||
}
|
||
|
||
sub get_default_version {
|
||
my $today = DateTime->today_local;
|
||
my $cutoff_v3_8_0 = DateTime->new_local(year => 2025, month => 10, day => 1);
|
||
|
||
return V3_8_0() if $today >= $cutoff_v3_8_0;
|
||
return V3_0_0();
|
||
}
|
||
|
||
sub is_version_valid {
|
||
shift while @_ && ref($_[0]);
|
||
|
||
my $id = $_[0];
|
||
my %versions = get_supported_versions();
|
||
|
||
return any { $_->{id} == $id } @{ $versions{versions} };
|
||
}
|
||
|
||
sub new {
|
||
my $class = shift;
|
||
my $self = {};
|
||
... | ... | |
$self->{transactions} = [];
|
||
$self->{src_charset} = 'UTF-8';
|
||
$self->{grouped} = 0;
|
||
$self->{version} = get_default_version();
|
||
|
||
map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company creditor_id message_id grouped collection);
|
||
map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company creditor_id message_id grouped collection version);
|
||
|
||
$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};
|
||
croak "Invalid parameter: version" if !is_version_valid($self->{version});
|
||
|
||
map { $self->{$_} = $self->_replace_special_chars($self->{iconv}->convert($self->{$_})) } qw(company message_id creditor_id);
|
||
}
|
||
... | ... | |
return substr $string, 0, 35;
|
||
}
|
||
|
||
sub _emit_cdtr_scheme_id {
|
||
my ($self, $xml) = @_;
|
||
|
||
$xml->startTag('CdtrSchmeId');
|
||
$xml->startTag('Id');
|
||
$xml->startTag('PrvtId');
|
||
$xml->startTag('Othr');
|
||
$xml->dataElement('Id', encode('UTF-8', substr($self->{creditor_id}, 0, 35)));
|
||
$xml->startTag('SchmeNm');
|
||
$xml->dataElement('Prtry', 'SEPA');
|
||
$xml->endTag('SchmeNm');
|
||
$xml->endTag('Othr');
|
||
$xml->endTag('PrvtId');
|
||
$xml->endTag('Id');
|
||
$xml->endTag('CdtrSchmeId');
|
||
}
|
||
|
||
sub to_xml {
|
||
my $self = shift;
|
||
|
||
... | ... | |
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.001.08' : 'pain.001.001.09';
|
||
my $pain_elmt = $is_coll ? 'CstmrDrctDbtInitn' : 'CstmrCdtTrfInitn';
|
||
my @pii_base = (strftime('PII%Y%m%d%H%M%S', @now), rand(1000000000));
|
||
my $pain_id = $is_coll && ($self->{version} == V3_0_0()) ? 'pain.008.001.02'
|
||
: $is_coll && ($self->{version} == V3_8_0()) ? 'pain.008.001.08'
|
||
: !$is_coll && ($self->{version} == V3_0_0()) ? 'pain.001.001.03'
|
||
: !$is_coll && ($self->{version} == V3_8_0()) ? 'pain.001.001.09'
|
||
: die("programming error: version not handled for pain ID");
|
||
my $bic_elt = $self->{version} == V3_0_0() ? 'BIC'
|
||
: $self->{version} == V3_8_0() ? 'BICFI'
|
||
: die("programming error: version not handled for BIC element name");
|
||
|
||
my $grouped_transactions = $self->_group_transactions();
|
||
|
||
... | ... | |
}
|
||
$xml->endTag('PmtTpInf');
|
||
|
||
if ($is_coll) {
|
||
$xml->dataElement('ReqdColltnDt', $master_transaction->get('execution_date'));
|
||
if ($self->{version} == V3_0_0()) {
|
||
$xml->dataElement($is_coll ? 'ReqdColltnDt' : 'ReqdExctnDt', $master_transaction->get('execution_date'));
|
||
|
||
} elsif ($self->{version} == V3_8_0()) {
|
||
if ($is_coll) {
|
||
$xml->dataElement('ReqdColltnDt', $master_transaction->get('execution_date'));
|
||
} else {
|
||
$xml->startTag('ReqdExctnDt');
|
||
$xml->dataElement('Dt', $master_transaction->get('execution_date'));
|
||
$xml->endTag('ReqdExctnDt');
|
||
}
|
||
|
||
} else {
|
||
$xml->startTag('ReqdExctnDt');
|
||
$xml->dataElement('Dt', $master_transaction->get('execution_date'));
|
||
$xml->endTag('ReqdExctnDt');
|
||
die("programming error: version not handled for ReqdColl/ExctnDt");
|
||
}
|
||
|
||
$xml->startTag($cd_src);
|
||
... | ... | |
|
||
$xml->startTag($cd_src . 'Agt');
|
||
$xml->startTag('FinInstnId');
|
||
$xml->dataElement('BICFI', $master_transaction->get('src_bic', 20));
|
||
$xml->dataElement($bic_elt, $master_transaction->get('src_bic', 20));
|
||
$xml->endTag('FinInstnId');
|
||
$xml->endTag($cd_src . 'Agt');
|
||
|
||
$xml->dataElement('ChrgBr', 'SLEV');
|
||
|
||
if ($is_coll) {
|
||
$xml->startTag('CdtrSchmeId');
|
||
$xml->startTag('Id');
|
||
$xml->startTag('PrvtId');
|
||
$xml->startTag('Othr');
|
||
$xml->dataElement('Id', encode('UTF-8', substr($self->{creditor_id}, 0, 35)));
|
||
$xml->startTag('SchmeNm');
|
||
$xml->dataElement('Prtry', 'SEPA');
|
||
$xml->endTag('SchmeNm');
|
||
$xml->endTag('Othr');
|
||
$xml->endTag('PrvtId');
|
||
$xml->endTag('Id');
|
||
$xml->endTag('CdtrSchmeId');
|
||
if ($is_coll && ($self->{version} == V3_8_0())) {
|
||
$self->_emit_cdtr_scheme_id($xml);
|
||
}
|
||
|
||
foreach my $transaction (@{ $transaction_group->{transactions} }) {
|
||
... | ... | |
$xml->dataElement('MndtId', $self->_restricted_identification_sepa2($transaction->get('mandator_id')));
|
||
$xml->dataElement('DtOfSgntr', $self->_restricted_identification_sepa2($transaction->get('date_of_signature')));
|
||
|
||
if ($self->{version} == V3_0_0()) {
|
||
$self->_emit_cdtr_scheme_id($xml);
|
||
}
|
||
|
||
$xml->endTag('MndtRltdInf');
|
||
|
||
$xml->endTag('DrctDbtTx');
|
||
... | ... | |
|
||
$xml->startTag("${cd_dst}Agt");
|
||
$xml->startTag('FinInstnId');
|
||
$xml->dataElement('BICFI', $transaction->get('dst_bic', 20));
|
||
$xml->dataElement($bic_elt, $transaction->get('dst_bic', 20));
|
||
$xml->endTag('FinInstnId');
|
||
$xml->endTag("${cd_dst}Agt");
|
||
|
bin/mozilla/sepa.pl | ||
---|---|---|
'vc' => $vc);
|
||
|
||
$form->header();
|
||
print $form->parse_html_template('sepa/bank_transfer_created', { 'id' => $id, 'vc' => $vc });
|
||
my %sepa_versions = SL::SEPA::XML->get_supported_versions;
|
||
print $form->parse_html_template('sepa/bank_transfer_created', { 'id' => $id, 'vc' => $vc, sepa_versions => \%sepa_versions });
|
||
}
|
||
|
||
$main::lxdebug->leave_sub();
|
||
... | ... | |
push @options, $form->{l_executed} ? $locale->text('executed') : $locale->text('not yet executed') if ($form->{l_executed} != $form->{l_not_executed});
|
||
push @options, $form->{l_closed} ? $locale->text('closed') : $locale->text('open') if ($form->{l_open} != $form->{l_closed});
|
||
|
||
my %sepa_versions = SL::SEPA::XML->get_supported_versions;
|
||
|
||
$report->set_options('top_info_text' => join("\n", @options),
|
||
'raw_top_info_text' => $form->parse_html_template('sepa/bank_transfer_list_top'),
|
||
'raw_top_info_text' => $form->parse_html_template('sepa/bank_transfer_list_top', { 'show_buttons' => $open_available, sepa_versions => \%sepa_versions, }),
|
||
'raw_bottom_info_text' => $form->parse_html_template('sepa/bank_transfer_list_bottom', { 'show_buttons' => $open_available, vc => $vc }),
|
||
'std_column_visibility' => 1,
|
||
'output_format' => 'HTML',
|
||
... | ... | |
'message_id' => $message_id,
|
||
'grouped' => 1,
|
||
'collection' => $vc eq 'customer',
|
||
'version' => $form->{sepa_xml_version},
|
||
);
|
||
|
||
foreach my $item (@items) {
|
locale/de/all | ||
---|---|---|
'SEPA Transfer Amount' => 'Betrag in offenen SEPA Exporten',
|
||
'SEPA XML Docs for Exports ' => 'SEPA-XML-Dokumente Export-Nummer',
|
||
'SEPA XML download' => 'SEPA-XML-Download',
|
||
'SEPA XML version' => 'SEPA-XML-Version',
|
||
'SEPA creditor ID' => 'SEPA-Kreditoren-Identifikation',
|
||
'SEPA exports' => 'SEPA-Exporte',
|
||
'SEPA message ID' => 'SEPA-Nachrichten-ID',
|
||
... | ... | |
'The PDF has been printed' => 'Das PDF-Dokument wurde gedruckt.',
|
||
'The Protocol for Host Name seems invalid (expected: http:// or https://)!' => 'Das Protokoll für den Server sieht falsch aus. Erwartet wird "http://" oder "https://".',
|
||
'The Proxy Name seems invalid' => 'Der Hostname des Proxys sieht falsch aus',
|
||
'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt',
|
||
'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt.',
|
||
'The SEPA strings have been saved.' => 'Die bei SEPA-Überweisungen verwendeten Begriffe wurden gespeichert.',
|
||
'The SQL query can be parameterized with variables named as follows: <%name%>.' => 'Die SQL-Abfrage kann mittels Variablen wie folgt parametrisiert werden: <%Variablenname%>.',
|
||
'The SQL query does not contain any parameter that need to be configured.' => 'Die SQL-Abfrage enthält keine Parameter, die angegeben werden müssten.',
|
locale/en/all | ||
---|---|---|
'SEPA Transfer Amount' => '',
|
||
'SEPA XML Docs for Exports ' => '',
|
||
'SEPA XML download' => '',
|
||
'SEPA XML version' => '',
|
||
'SEPA creditor ID' => '',
|
||
'SEPA exports' => '',
|
||
'SEPA message ID' => '',
|
templates/design40_webpages/sepa/bank_transfer_created.html | ||
---|---|---|
[% USE T8 %]
|
||
[% USE HTML %]
|
||
|
||
[%- USE T8 %]
|
||
[% USE HTML %][%- USE LxERP -%][%- USE L -%]
|
||
<h1>[% title %]</h1>
|
||
|
||
<p>[% 'The SEPA export has been created.' | $T8 %]</p>
|
||
<ul>
|
||
<li><a href="sepa.pl?action=bank_transfer_download_sepa_xml&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]"> [% 'Download SEPA XML export file' | $T8 %] </a></li>
|
||
[%- IF INSTANCE_CONF.get_doc_storage %]
|
||
<li><a href="sepa.pl?action=bank_transfer_download_sepa_docs&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]"> [% 'Download Documents for exported bookings' | $T8 %] </a></li>
|
||
[% END %]
|
||
<li><a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]"> [% 'List open SEPA exports' | $T8 %] </a></li>
|
||
</ul>
|
||
<p>
|
||
[% 'The SEPA export has been created.' | $T8 %]
|
||
</p>
|
||
|
||
<h2>[% LxERP.t8('Download SEPA XML export file') %]</h2>
|
||
|
||
<form method="post" action="sepa.pl">
|
||
<p>
|
||
[% LxERP.t8("SEPA XML version") %]:
|
||
[% L.select_tag('sepa_xml_version', sepa_versions.versions, title_key='description', selected=sepa_versions.default) %]
|
||
</p>
|
||
|
||
<p>
|
||
[% L.hidden_tag('action', 'bank_transfer_download_sepa_xml') %]
|
||
[% L.hidden_tag('id', id) %]
|
||
[% L.hidden_tag('vc', vc) %]
|
||
|
||
[% L.submit_tag('dummy', LxERP.t8('Download')) %]
|
||
</p>
|
||
</form>
|
||
[%- IF INSTANCE_CONF.get_doc_storage %]
|
||
|
||
<h2>[% LxERP.t8('Download Documents for exported bookings') %]</h2>
|
||
|
||
<p>
|
||
<a href="sepa.pl?action=bank_transfer_download_sepa_docs&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]"> [% 'Download Documents for exported bookings' | $T8 %]</a>
|
||
</p>
|
||
[% END %]
|
||
|
||
<h2>[% LxERP.t8('List open SEPA exports') %]</h2>
|
||
|
||
<p>
|
||
<a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]">[% 'List open SEPA exports' | $T8 %]</a>
|
||
</p>
|
templates/design40_webpages/sepa/bank_transfer_list_top.html | ||
---|---|---|
[%- USE LxERP -%][%- USE L -%]
|
||
<form action="sepa.pl" method="post" id="form">
|
||
[% IF show_buttons %]
|
||
|
||
<p>
|
||
[% LxERP.t8("SEPA XML version") %]:
|
||
[% L.select_tag('sepa_xml_version', sepa_versions.versions, title_key='description', selected=sepa_versions.default) %]
|
||
</p>
|
||
[% END %]
|
templates/webpages/sepa/bank_transfer_created.html | ||
---|---|---|
[%- USE T8 %]
|
||
[% USE HTML %]
|
||
[% USE HTML %][%- USE LxERP -%][%- USE L -%]
|
||
<h1>[% title %]</h1>
|
||
|
||
<p>
|
||
<p>
|
||
[% 'The SEPA export has been created.' | $T8 %]
|
||
</p>
|
||
|
||
<p>
|
||
<ul>
|
||
<li>
|
||
<a href="sepa.pl?action=bank_transfer_download_sepa_xml&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]">
|
||
[% 'Download SEPA XML export file' | $T8 %]
|
||
</a>
|
||
</li>
|
||
[%- IF INSTANCE_CONF.get_doc_storage %]
|
||
<li><a href="sepa.pl?action=bank_transfer_download_sepa_docs&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]"> [% 'Download Documents for exported bookings' | $T8 %] </a></li>
|
||
[% END %]
|
||
<li>
|
||
<a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]">
|
||
[% 'List open SEPA exports' | $T8 %]
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</p>
|
||
</p>
|
||
|
||
<h2>[% LxERP.t8('Download SEPA XML export file') %]</h2>
|
||
|
||
<form method="post" action="sepa.pl">
|
||
<p>
|
||
[% LxERP.t8("SEPA XML version") %]:
|
||
[% L.select_tag('sepa_xml_version', sepa_versions.versions, title_key='description', selected=sepa_versions.default) %]
|
||
</p>
|
||
|
||
<p>
|
||
[% L.hidden_tag('action', 'bank_transfer_download_sepa_xml') %]
|
||
[% L.hidden_tag('id', id) %]
|
||
[% L.hidden_tag('vc', vc) %]
|
||
|
||
[% L.submit_tag('dummy', LxERP.t8('Download')) %]
|
||
</p>
|
||
</form>
|
||
[%- IF INSTANCE_CONF.get_doc_storage %]
|
||
|
||
<h2>[% LxERP.t8('Download Documents for exported bookings') %]</h2>
|
||
|
||
<p>
|
||
<a href="sepa.pl?action=bank_transfer_download_sepa_docs&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]"> [% 'Download Documents for exported bookings' | $T8 %]</a>
|
||
</p>
|
||
[% END %]
|
||
|
||
<h2>[% LxERP.t8('List open SEPA exports') %]</h2>
|
||
|
||
<p>
|
||
<a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]">[% 'List open SEPA exports' | $T8 %]</a>
|
||
</p>
|
templates/webpages/sepa/bank_transfer_list_top.html | ||
---|---|---|
[%- USE LxERP -%][%- USE L -%]
|
||
<form action="sepa.pl" method="post" id="form">
|
||
[% IF show_buttons %]
|
||
|
||
<p>
|
||
[% LxERP.t8("SEPA XML version") %]:
|
||
[% L.select_tag('sepa_xml_version', sepa_versions.versions, title_key='description', selected=sepa_versions.default) %]
|
||
</p>
|
||
[% END %]
|
Auch abrufbar als: Unified diff
SEPA: XML-Version beim Download auswählbar