Revision 1d6b9c08
Von Cem Aydin vor etwa 1 Jahr hinzugefügt
SL/SEPA.pm | ||
---|---|---|
31 | 31 |
|
32 | 32 |
my $is_sepa_blocked = $params{vc} eq 'customer' ? 'FALSE' : "${arap}.is_sepa_blocked"; |
33 | 33 |
|
34 |
my $swiss_data = $::instance_conf->get_sepa_swiss_xml_export eq 1 && $arap eq 'ap' ? |
|
35 |
" vc.iban AS vc_iban, vc.bic AS vc_bic, ap.qrbill_data AS qrbill_data, " : ''; |
|
36 |
|
|
34 | 37 |
# open_amount is not the current open amount according to bookkeeping, but |
35 | 38 |
# the open amount minus the SEPA transfer amounts that haven't been closed yet |
36 | 39 |
my $query = |
... | ... | |
39 | 42 |
(${arap}.transdate + pt.terms_skonto) as skonto_date, (pt.percent_skonto * 100) as percent_skonto, |
40 | 43 |
(${arap}.amount - (${arap}.amount * pt.percent_skonto)) as amount_less_skonto, |
41 | 44 |
(${arap}.amount * pt.percent_skonto) as skonto_amount, |
42 |
vc.name AS vcname, vc.language_id, ${arap}.duedate as duedate, ${arap}.direct_debit, |
|
45 |
vc.name AS vcname, vc.language_id, vc.iban AS vc_iban, vc.bic AS vc_bic, |
|
46 |
${arap}.duedate as duedate, ${arap}.direct_debit, |
|
43 | 47 |
${is_sepa_blocked} AS is_sepa_blocked, |
44 | 48 |
vc.${vc_vc_id} as vc_vc_id, |
49 |
$swiss_data |
|
45 | 50 |
|
46 | 51 |
COALESCE(vc.iban, '') <> '' AND COALESCE(vc.bic, '') <> '' ${mandate} AS vc_bank_info_ok, |
47 | 52 |
|
... | ... | |
237 | 242 |
my $mandator_id = $params{vc} eq 'customer' ? ', mandator_id, mandate_date_of_signature' : ''; |
238 | 243 |
|
239 | 244 |
if ($params{details}) { |
240 |
$columns = qq|, arap.invnumber, arap.invoice, arap.transdate AS reference_date, vc.name AS vc_name, vc.${vc}number AS vc_number, c.accno AS chart_accno, c.description AS chart_description ${mandator_id}|; |
|
245 |
$columns = qq|, arap.invnumber, arap.invoice, arap.transdate AS reference_date, |
|
246 |
arap.qrbill_data AS qrbill_data, vc.name AS vc_name, |
|
247 |
vc.${vc}number AS vc_number, vc.street AS vc_street, |
|
248 |
vc.zipcode AS vc_zipcode, vc.city AS vc_city, vc.country AS vc_country, |
|
249 |
c.accno AS chart_accno, c.description AS chart_description ${mandator_id}, |
|
250 |
cur.name AS currency_name|; |
|
241 | 251 |
$joins = qq|LEFT JOIN ${arap} arap ON (sei.${arap}_id = arap.id) |
242 | 252 |
LEFT JOIN ${vc} vc ON (arap.${vc}_id = vc.id) |
243 |
LEFT JOIN chart c ON (sei.chart_id = c.id)|; |
|
253 |
LEFT JOIN chart c ON (sei.chart_id = c.id) |
|
254 |
LEFT JOIN currencies cur ON (arap.currency_id = cur.id) |
|
255 |
|; |
|
244 | 256 |
} |
245 | 257 |
|
246 | 258 |
$query = qq|SELECT sei.* |
SL/SEPA/SwissXML.pm | ||
---|---|---|
1 |
package SL::SEPA::SwissXML; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use utf8; |
|
5 |
|
|
6 |
use parent qw(SL::SEPA::XML); |
|
7 |
|
|
8 |
use Carp; |
|
9 |
use Encode; |
|
10 |
use POSIX qw(strftime); |
|
11 |
use XML::Writer; |
|
12 |
|
|
13 |
use SL::Iconv; |
|
14 |
use SL::SEPA::XML::SwissTransaction; |
|
15 |
|
|
16 |
sub add_transaction { |
|
17 |
my $self = shift; |
|
18 |
|
|
19 |
foreach my $transaction (@_) { |
|
20 |
croak "Expecting hash reference." if (ref $transaction ne 'HASH'); |
|
21 |
push @{ $self->{transactions} }, SL::SEPA::XML::SwissTransaction->new(%{ $transaction }, 'sepa' => $self); |
|
22 |
} |
|
23 |
|
|
24 |
return 1; |
|
25 |
} |
|
26 |
|
|
27 |
sub _group_transactions { |
|
28 |
my $self = shift; |
|
29 |
|
|
30 |
my $grouped = { |
|
31 |
'sum_amount' => 0, |
|
32 |
'groups' => { }, |
|
33 |
}; |
|
34 |
|
|
35 |
foreach my $transaction (@{ $self->{transactions} }) { |
|
36 |
my $key = $self->{grouped} ? join("\t", map { $transaction->get($_) } qw(src_bic src_iban execution_date is_sepa_payment)) : 'all'; |
|
37 |
$grouped->{groups}->{$key} ||= { |
|
38 |
'sum_amount' => 0, |
|
39 |
'transactions' => [ ], |
|
40 |
}; |
|
41 |
|
|
42 |
push @{ $grouped->{groups}->{$key}->{transactions} }, $transaction; |
|
43 |
|
|
44 |
$grouped->{groups}->{$key}->{sum_amount} += $transaction->{amount}; |
|
45 |
$grouped->{sum_amount} += $transaction->{amount}; |
|
46 |
} |
|
47 |
|
|
48 |
return $grouped; |
|
49 |
} |
|
50 |
|
|
51 |
sub to_xml { |
|
52 |
my $self = shift; |
|
53 |
|
|
54 |
croak "No transactions added yet." if (!@{ $self->{transactions} }); |
|
55 |
|
|
56 |
my $output = ''; |
|
57 |
|
|
58 |
my $xml = XML::Writer->new(OUTPUT => \$output, |
|
59 |
DATA_MODE => 1, |
|
60 |
DATA_INDENT => 2, |
|
61 |
ENCODING => 'utf-8'); |
|
62 |
|
|
63 |
my @now = localtime; |
|
64 |
# (removed time zone stuff in accordance with SIX examples) |
|
65 |
my $now_str = strftime('%Y-%m-%dT%H:%M:%S', @now); |
|
66 |
|
|
67 |
my $payment_inf_id_counter = 1; |
|
68 |
my $transaction_id_counter = 1; |
|
69 |
|
|
70 |
my $grouped_transactions = $self->_group_transactions(); |
|
71 |
|
|
72 |
# Note: the transaction->get function, respectively the encode function |
|
73 |
# that is used there produces garbled characters for example: |
|
74 |
# encode('UTF-8', '& & äöü <123>') -> & & äöü <123> |
|
75 |
# not fully clear why, for now I'll omit the function in some places |
|
76 |
|
|
77 |
$xml->xmlDecl(); |
|
78 |
|
|
79 |
$xml->startTag('Document', |
|
80 |
'xmlns' => "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09", |
|
81 |
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', |
|
82 |
'xsi:schemaLocation' => "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.ch.03.xsd"); |
|
83 |
|
|
84 |
$xml->startTag('CstmrCdtTrfInitn'); |
|
85 |
|
|
86 |
$xml->startTag('GrpHdr'); |
|
87 |
$xml->dataElement('MsgId', $self->_restricted_identification_sepa1($self->{message_id})); |
|
88 |
$xml->dataElement('CreDtTm', $now_str); |
|
89 |
$xml->dataElement('NbOfTxs', scalar @{ $self->{transactions} }); |
|
90 |
$xml->dataElement('CtrlSum', $self->_format_amount($grouped_transactions->{sum_amount})); |
|
91 |
|
|
92 |
$xml->startTag('InitgPty'); |
|
93 |
$xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70))); |
|
94 |
# (details about the software) |
|
95 |
$xml->startTag('CtctDtls'); |
|
96 |
$xml->startTag('Othr'); |
|
97 |
$xml->dataElement('ChanlTp', 'NAME'); |
|
98 |
$xml->dataElement('Id', 'Kivitendo'); |
|
99 |
$xml->endTag('Othr'); |
|
100 |
$xml->endTag('CtctDtls'); |
|
101 |
# TODO (optional): get current kivitendo version |
|
102 |
# $xml->startTag('Othr'); |
|
103 |
# $xml->dataElement('ChanlTp', 'VRSN'); |
|
104 |
# $xml->dataElement('Id', '...'); |
|
105 |
# $xml->endTag('Othr'); |
|
106 |
$xml->endTag('InitgPty'); |
|
107 |
|
|
108 |
$xml->endTag('GrpHdr'); |
|
109 |
|
|
110 |
foreach my $key (keys %{ $grouped_transactions->{groups} }) { |
|
111 |
my $transaction_group = $grouped_transactions->{groups}->{$key}; |
|
112 |
my $master_transaction = $transaction_group->{transactions}->[0]; |
|
113 |
|
|
114 |
$xml->startTag('PmtInf'); |
|
115 |
|
|
116 |
my $payment_inf_id = 'PMTINF-' . sprintf('%010d', $payment_inf_id_counter); |
|
117 |
$xml->dataElement('PmtInfId', $payment_inf_id); |
|
118 |
|
|
119 |
$xml->dataElement('PmtMtd', 'TRF'); |
|
120 |
|
|
121 |
# if batch booking true there will be "1 booking per group" |
|
122 |
# (according to swiss business rules specification) |
|
123 |
# I don't fully understand how there can be 1 booking for multiple transactions |
|
124 |
# furthermore in that case we should make sure there is only 1 currency in the group |
|
125 |
# (function _group_transactions) |
|
126 |
# so for now I'm setting this to false |
|
127 |
$xml->dataElement('BtchBookg', 'false'); |
|
128 |
|
|
129 |
if ($master_transaction->get('is_sepa_payment')) { |
|
130 |
$xml->startTag('PmtTpInf'); |
|
131 |
$xml->startTag('SvcLvl'); |
|
132 |
$xml->dataElement('Cd', 'SEPA'); |
|
133 |
$xml->endTag('SvcLvl'); |
|
134 |
$xml->endTag('PmtTpInf'); |
|
135 |
} |
|
136 |
|
|
137 |
$xml->startTag('ReqdExctnDt'); |
|
138 |
$xml->dataElement('Dt', $master_transaction->get('execution_date')); |
|
139 |
$xml->endTag('ReqdExctnDt'); |
|
140 |
$xml->startTag('Dbtr'); |
|
141 |
# not using get here because it's garbling the characters |
|
142 |
$xml->dataElement('Nm', $self->{company}); |
|
143 |
$xml->endTag('Dbtr'); |
|
144 |
|
|
145 |
$xml->startTag('DbtrAcct'); |
|
146 |
$xml->startTag('Id'); |
|
147 |
$xml->dataElement('IBAN', $master_transaction->get('src_iban', 34)); |
|
148 |
$xml->endTag('Id'); |
|
149 |
$xml->endTag('DbtrAcct'); |
|
150 |
|
|
151 |
$xml->startTag('DbtrAgt'); |
|
152 |
$xml->startTag('FinInstnId'); |
|
153 |
$xml->dataElement('BICFI', $master_transaction->get('src_bic', 20)); |
|
154 |
$xml->endTag('FinInstnId'); |
|
155 |
$xml->endTag('DbtrAgt'); |
|
156 |
|
|
157 |
foreach my $transaction (@{ $transaction_group->{transactions} }) { |
|
158 |
$xml->startTag('CdtTrfTxInf'); |
|
159 |
|
|
160 |
$xml->startTag('PmtId'); |
|
161 |
|
|
162 |
# using a simple counter here, analog to examples from SIX |
|
163 |
my $instr_id = 'INSTRID-' . |
|
164 |
sprintf('%010d', $payment_inf_id_counter) . '-' . |
|
165 |
sprintf('%010d', $transaction_id_counter); |
|
166 |
$xml->dataElement('InstrId', $instr_id); |
|
167 |
|
|
168 |
$xml->dataElement('EndToEndId', |
|
169 |
$self->_restricted_identification_sepa1($transaction->get('end_to_end_id'))); |
|
170 |
$xml->endTag('PmtId'); |
|
171 |
|
|
172 |
$xml->startTag('Amt'); |
|
173 |
$xml->startTag('InstdAmt', 'Ccy' => $transaction->{currency}); |
|
174 |
$xml->characters($self->_format_amount($transaction->{amount})); |
|
175 |
$xml->endTag('InstdAmt'); |
|
176 |
$xml->endTag('Amt'); |
|
177 |
|
|
178 |
if ($transaction->{is_sepa_payment}) { |
|
179 |
$xml->startTag('CdtrAgt'); |
|
180 |
$xml->startTag('FinInstnId'); |
|
181 |
$xml->dataElement('BICFI', $transaction->{dst_bic}); |
|
182 |
$xml->endTag('FinInstnId'); |
|
183 |
$xml->endTag('CdtrAgt'); |
|
184 |
} |
|
185 |
|
|
186 |
$xml->startTag('Cdtr'); |
|
187 |
if ($transaction->{is_qrbill}) { |
|
188 |
$xml->dataElement('Nm', $transaction->{creditor_name}); |
|
189 |
$xml->startTag('PstlAdr'); |
|
190 |
# don't use empty elements here, according to SIX validator |
|
191 |
$xml->dataElement('StrtNm', $transaction->{creditor_street_name}) if $transaction->{creditor_street_name}; |
|
192 |
$xml->dataElement('BldgNb', $transaction->{creditor_building_number}) if $transaction->{creditor_building_number}; |
|
193 |
$xml->dataElement('PstCd', $transaction->{creditor_postal_code}); |
|
194 |
$xml->dataElement('TwnNm', $transaction->{creditor_town_name}); |
|
195 |
$xml->dataElement('Ctry', $transaction->{creditor_country}); |
|
196 |
$xml->endTag('PstlAdr'); |
|
197 |
} else { |
|
198 |
$xml->dataElement('Nm', $transaction->{company}); |
|
199 |
if ($transaction->{has_creditor_address}) { |
|
200 |
$xml->startTag('PstlAdr'); |
|
201 |
$xml->dataElement('StrtNm', $transaction->{creditor_street_name}) if $transaction->{creditor_street_name}; |
|
202 |
$xml->dataElement('BldgNb', $transaction->{creditor_building_number}) if $transaction->{creditor_building_number}; |
|
203 |
$xml->dataElement('PstCd', $transaction->{creditor_postal_code}); |
|
204 |
$xml->dataElement('TwnNm', $transaction->{creditor_town_name}); |
|
205 |
$xml->dataElement('Ctry', $transaction->{creditor_country}); |
|
206 |
$xml->endTag('PstlAdr'); |
|
207 |
} |
|
208 |
} |
|
209 |
$xml->endTag('Cdtr'); |
|
210 |
|
|
211 |
$xml->startTag('CdtrAcct'); |
|
212 |
$xml->startTag('Id'); |
|
213 |
$xml->dataElement('IBAN', $transaction->get('dst_iban', 34)); |
|
214 |
$xml->endTag('Id'); |
|
215 |
$xml->endTag('CdtrAcct'); |
|
216 |
|
|
217 |
$xml->startTag('RmtInf'); |
|
218 |
if ($transaction->{end_to_end_id} =~ /^ENDTOENDID-(QRR|SCOR)$/) { |
|
219 |
$xml->startTag('Strd'); |
|
220 |
$xml->startTag('CdtrRefInf'); |
|
221 |
$xml->startTag('Tp'); |
|
222 |
$xml->startTag('CdOrPrtry'); |
|
223 |
if ($transaction->{end_to_end_id} eq 'ENDTOENDID-QRR') { |
|
224 |
$xml->dataElement('Prtry', 'QRR'); |
|
225 |
} else { |
|
226 |
$xml->dataElement('Cd', 'SCOR'); |
|
227 |
} |
|
228 |
$xml->endTag('CdOrPrtry'); |
|
229 |
$xml->endTag('Tp'); |
|
230 |
$xml->dataElement('Ref', $transaction->get('reference')); |
|
231 |
$xml->endTag('CdtrRefInf'); |
|
232 |
if ($transaction->{unstructured_message}) { |
|
233 |
$xml->dataElement('AddtlRmtInf', $transaction->get('unstructured_message')); |
|
234 |
} |
|
235 |
$xml->endTag('Strd'); |
|
236 |
} else { |
|
237 |
if ($transaction->{reference}) { |
|
238 |
$xml->dataElement('Ustrd', $transaction->get('reference', 140)); |
|
239 |
} |
|
240 |
} |
|
241 |
$xml->endTag('RmtInf'); |
|
242 |
|
|
243 |
$xml->endTag('CdtTrfTxInf'); |
|
244 |
|
|
245 |
$transaction_id_counter++; |
|
246 |
} |
|
247 |
|
|
248 |
$xml->endTag('PmtInf'); |
|
249 |
|
|
250 |
$payment_inf_id_counter++; |
|
251 |
} |
|
252 |
|
|
253 |
$xml->endTag('CstmrCdtTrfInitn'); |
|
254 |
$xml->endTag('Document'); |
|
255 |
$xml->end(); |
|
256 |
|
|
257 |
return $output; |
|
258 |
} |
|
259 |
|
|
260 |
1; |
|
261 |
|
|
262 |
# Local Variables: |
|
263 |
# coding: utf-8 |
|
264 |
# End: |
SL/SEPA/XML/SwissTransaction.pm | ||
---|---|---|
1 |
package SL::SEPA::XML::SwissTransaction; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::SEPA::XML::Transaction); |
|
6 |
|
|
7 |
use Carp; |
|
8 |
use Encode; |
|
9 |
use List::Util qw(first); |
|
10 |
use POSIX qw(strftime); |
|
11 |
|
|
12 |
sub _init { |
|
13 |
my $self = shift; |
|
14 |
my %params = @_; |
|
15 |
|
|
16 |
$self->{sepa} = $params{sepa}; |
|
17 |
delete $params{sepa}; |
|
18 |
|
|
19 |
my $missing_parameter = first { !$params{$_} } qw(src_iban src_bic dst_iban company reference amount end_to_end_id); |
|
20 |
croak "Missing parameter: $missing_parameter" if ($missing_parameter); |
|
21 |
|
|
22 |
$params{end_to_end_id} ||= 'NOTPROVIDED'; |
|
23 |
$params{execution_date} ||= strftime "%Y-%m-%d", localtime; |
|
24 |
|
|
25 |
croak "Execution date format wrong for '$params{execution_date}': not YYYY-MM-DD." if ($params{execution_date} !~ /^\d{4}-\d{2}-\d{2}$/); |
|
26 |
|
|
27 |
map { $self->{$_} = $self->{sepa}->{iconv}->convert($params{$_}) } keys %params; |
|
28 |
map { $self->{$_} =~ s/\s+//g } qw(src_iban src_bic dst_iban); |
|
29 |
map { $self->{$_} = $self->{sepa}->_replace_special_chars($self->{$_}) } qw(company reference end_to_end_id); |
|
30 |
} |
|
31 |
|
|
32 |
1; |
bin/mozilla/sepa.pl | ||
---|---|---|
14 | 14 |
use SL::ReportGenerator; |
15 | 15 |
use SL::SEPA; |
16 | 16 |
use SL::SEPA::XML; |
17 |
use SL::SEPA::SwissXML; |
|
18 |
use SL::Helper::QrBillParser; |
|
19 |
use SL::Helper::ISO3166; |
|
20 |
|
|
21 |
use SL::Helper::QrBillFunctions qw( |
|
22 |
get_street_name_from_address_line |
|
23 |
get_building_number_from_address_line |
|
24 |
); |
|
17 | 25 |
|
18 | 26 |
require "bin/mozilla/common.pl"; |
19 | 27 |
require "bin/mozilla/reportgenerator.pl"; |
... | ... | |
26 | 34 |
my $vc = $form->{vc} eq 'customer' ? 'customer' : 'vendor'; |
27 | 35 |
my $vc_no = $form->{vc} eq 'customer' ? $::locale->text('VN') : $::locale->text('CN'); |
28 | 36 |
|
29 |
$form->{title} = $vc eq 'customer' ? $::locale->text('Prepare bank collection via SEPA XML') : $locale->text('Prepare bank transfer via SEPA XML'); |
|
37 |
my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export; |
|
38 |
|
|
39 |
$form->{title} = $vc eq 'customer' ? |
|
40 |
$::locale->text('Prepare bank collection via SEPA XML') : |
|
41 |
$swiss_export ? |
|
42 |
$locale->text('Prepare bank transfer via swiss XML') : |
|
43 |
$locale->text('Prepare bank transfer via SEPA XML'); |
|
30 | 44 |
|
31 | 45 |
my $bank_accounts = SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] ); |
32 | 46 |
|
... | ... | |
66 | 80 |
$invoice->{reference_prefix_vc} = ' ' . $prefix_vc_number unless $prefix_vc_number =~ m/^ /; |
67 | 81 |
} |
68 | 82 |
|
83 |
# for swiss export override database check because of different cases |
|
84 |
if ($swiss_export) { |
|
85 |
foreach my $invoice (@{ $invoices }) { |
|
86 |
# determine invoice type |
|
87 |
if ($invoice->{qrbill_data}) { |
|
88 |
$invoice->{type} = 'QRBILL'; |
|
89 |
|
|
90 |
# vendor iban comes from qrbill data |
|
91 |
# no further checks needed |
|
92 |
$invoice->{vc_bank_info_ok} = 1; |
|
93 |
|
|
94 |
} elsif ($invoice->{vc_iban} =~ m/^(CH|LI)/) { |
|
95 |
$invoice->{type} = 'DOMESTIC'; |
|
96 |
|
|
97 |
# vendor iban is needed |
|
98 |
$invoice->{vc_bank_info_ok} = $invoice->{vc_iban} ? 1 : 0; |
|
99 |
|
|
100 |
} else { |
|
101 |
$invoice->{type} = 'SEPA'; |
|
102 |
|
|
103 |
# vendor iban and bic are needed |
|
104 |
$invoice->{vc_bank_info_ok} = $invoice->{vc_iban} && $invoice->{vc_bic} ? 1 : 0 |
|
105 |
} |
|
106 |
} |
|
107 |
} |
|
108 |
|
|
69 | 109 |
setup_sepa_add_transfer_action_bar(); |
70 | 110 |
|
71 | 111 |
$form->header(); |
... | ... | |
86 | 126 |
my $myconfig = \%main::myconfig; |
87 | 127 |
my $vc = $form->{vc} eq 'customer' ? 'customer' : 'vendor'; |
88 | 128 |
|
89 |
$form->{title} = $vc eq 'customer' ? $::locale->text('Create bank collection via SEPA XML') : $locale->text('Create bank transfer via SEPA XML'); |
|
129 |
my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export; |
|
130 |
|
|
131 |
$form->{title} = $vc eq 'customer' ? |
|
132 |
$::locale->text('Create bank collection via SEPA XML') : |
|
133 |
$swiss_export ? |
|
134 |
$locale->text('Create bank transfer via swiss XML') : |
|
135 |
$locale->text('Create bank transfer via SEPA XML'); |
|
90 | 136 |
|
91 | 137 |
my $bank_accounts = SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] ); |
92 | 138 |
if (!scalar @{ $bank_accounts }) { |
... | ... | |
149 | 195 |
|
150 | 196 |
my ($vc_bank_info); |
151 | 197 |
my $error_message; |
152 |
|
|
153 | 198 |
my @bank_columns = qw(iban bic); |
154 |
push @bank_columns, qw(mandator_id mandate_date_of_signature) if $vc eq 'customer'; |
|
155 | 199 |
|
156 |
if ($form->{confirmation}) { |
|
157 |
$vc_bank_info = { map { $_->{id} => $_ } @{ $form->{vc_bank_info} || [] } }; |
|
200 |
# separate validation for swiss export |
|
201 |
if (!$swiss_export) { |
|
202 |
push @bank_columns, qw(mandator_id mandate_date_of_signature) if $vc eq 'customer'; |
|
158 | 203 |
|
159 |
foreach my $info (values %{ $vc_bank_info }) { |
|
160 |
if (any { !$info->{$_} } @bank_columns) { |
|
161 |
$error_message = $locale->text('The bank information must not be empty.'); |
|
162 |
last; |
|
204 |
if ($form->{confirmation}) { |
|
205 |
$vc_bank_info = { map { $_->{id} => $_ } @{ $form->{vc_bank_info} || [] } }; |
|
206 |
|
|
207 |
foreach my $info (values %{ $vc_bank_info }) { |
|
208 |
if (any { !$info->{$_} } @bank_columns) { |
|
209 |
$error_message = $locale->text('The bank information must not be empty.'); |
|
210 |
last; |
|
211 |
} |
|
163 | 212 |
} |
164 | 213 |
} |
214 |
} else { |
|
215 |
($error_message, $vc_bank_info) = validate_vendors_swiss_export(\@bank_transfers); |
|
165 | 216 |
} |
166 | 217 |
|
167 | 218 |
if ($error_message || !$form->{confirmation}) { |
168 |
my @vc_ids = uniq map { $_->{vc_id} } @bank_transfers; |
|
169 |
$vc_bank_info ||= CT->get_bank_info('vc' => $vc, |
|
170 |
'id' => \@vc_ids); |
|
171 |
my @vc_bank_info = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vc_bank_info }; |
|
219 |
if (!$swiss_export) { |
|
220 |
my @vc_ids = uniq map { $_->{vc_id} } @bank_transfers; |
|
221 |
|
|
222 |
$vc_bank_info ||= CT->get_bank_info('vc' => $vc, 'id' => \@vc_ids); |
|
223 |
} |
|
224 |
|
|
225 |
my @vc_bank_info = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vc_bank_info }; |
|
172 | 226 |
|
173 | 227 |
setup_sepa_create_transfer_action_bar(is_vendor => $vc eq 'vendor'); |
174 | 228 |
|
... | ... | |
198 | 252 |
'vc' => $vc); |
199 | 253 |
|
200 | 254 |
$form->header(); |
201 |
print $form->parse_html_template('sepa/bank_transfer_created', { 'id' => $id, 'vc' => $vc }); |
|
255 |
print $form->parse_html_template('sepa/bank_transfer_created', |
|
256 |
{ |
|
257 |
'id' => $id, |
|
258 |
'vc' => $vc, |
|
259 |
}); |
|
202 | 260 |
} |
203 | 261 |
|
204 | 262 |
$main::lxdebug->leave_sub(); |
205 | 263 |
} |
206 | 264 |
|
265 |
sub validate_vendors_swiss_export { |
|
266 |
my ($bank_transfers) = @_; |
|
267 |
|
|
268 |
my $form = $main::form; |
|
269 |
my $locale = $main::locale; |
|
270 |
my $myconfig = \%main::myconfig; |
|
271 |
|
|
272 |
# determine unique vendor types |
|
273 |
my %unique_vendor_types; |
|
274 |
for my $bt (@$bank_transfers) { |
|
275 |
my $uid = "$bt->{vc_id}_$bt->{type}"; |
|
276 |
$unique_vendor_types{$uid} = { |
|
277 |
vc_id => $bt->{vc_id}, |
|
278 |
type => $bt->{type} |
|
279 |
} unless defined $unique_vendor_types{$uid}; |
|
280 |
} |
|
281 |
|
|
282 |
# get bank info for unique vendor types |
|
283 |
my $vendors = $form->{vc_bank_info} ? |
|
284 |
{ map { $_->{id} => $_ } @{ $form->{vc_bank_info} } } : |
|
285 |
CT->get_bank_info('vc' => 'vendor', 'id' => [ map { $_->{vc_id} } values %unique_vendor_types ]); |
|
286 |
|
|
287 |
# combine bank info with unique vendor types |
|
288 |
for my $unique_vendor (values %unique_vendor_types) { |
|
289 |
$vendors->{$unique_vendor->{vc_id}}->{type} = $unique_vendor->{type}; |
|
290 |
} |
|
291 |
|
|
292 |
# validate bank info for unique vendor types |
|
293 |
my $error_message; |
|
294 |
for my $vendor (values %$vendors) { |
|
295 |
#my $bank_info = $unique_vendor->{bank_info}; |
|
296 |
next if ($vendor->{type} eq 'QRBILL'); |
|
297 |
if ($vendor->{type} eq 'DOMESTIC' && !$vendor->{iban}) { |
|
298 |
$error_message = $locale->text('The bank information must not be empty.'); |
|
299 |
last; |
|
300 |
} elsif ($vendor->{type} eq 'SEPA' && (!$vendor->{iban} || !$vendor->{bic})) { |
|
301 |
$error_message = $locale->text('The bank information must not be empty.'); |
|
302 |
last; |
|
303 |
} |
|
304 |
} |
|
305 |
|
|
306 |
return $error_message, $vendors; |
|
307 |
} |
|
308 |
|
|
207 | 309 |
sub bank_transfer_search { |
208 | 310 |
$main::lxdebug->enter_sub(); |
209 | 311 |
|
... | ... | |
267 | 369 |
'closed' => { 'text' => $locale->text('Closed'), }, |
268 | 370 |
num_invoices => { 'text' => $locale->text('Number of invoices'), }, |
269 | 371 |
sum_amounts => { 'text' => $locale->text('Sum of all amounts'), }, |
270 |
message_ids => { 'text' => $locale->text('SEPA message IDs'), },
|
|
372 |
message_ids => { 'text' => !$::instance_conf->get_sepa_swiss_xml_export ? $locale->text('SEPA message IDs') : $locale->text('Message IDs'), },
|
|
271 | 373 |
); |
272 | 374 |
|
273 | 375 |
my @columns = qw(selected id export_date employee executed closed num_invoices sum_amounts message_ids); |
... | ... | |
390 | 492 |
has_executed => $has_executed, |
391 | 493 |
); |
392 | 494 |
|
393 |
$form->{title} = $locale->text('View SEPA export'); |
|
495 |
$form->{title} = !$::instance_conf->get_sepa_swiss_xml_export ? |
|
496 |
$locale->text('View SEPA export') : |
|
497 |
$locale->text('View bank transfer'); |
|
394 | 498 |
$form->header(); |
395 | 499 |
print $form->parse_html_template('sepa/bank_transfer_edit', |
396 | 500 |
{ ids => \@ids, |
... | ... | |
514 | 618 |
my $vc = $form->{vc} eq 'customer' ? 'customer' : 'vendor'; |
515 | 619 |
my $defaults = SL::DB::Default->get; |
516 | 620 |
|
621 |
my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export; |
|
622 |
|
|
517 | 623 |
if (!$defaults->company) { |
518 | 624 |
$form->show_generic_error($locale->text('You have to enter a company name in the client configuration.')); |
519 | 625 |
} |
... | ... | |
547 | 653 |
|
548 | 654 |
my $message_id = strftime('MSG%Y%m%d%H%M%S', localtime) . sprintf('%06d', $$); |
549 | 655 |
|
550 |
my $sepa_xml = SL::SEPA::XML->new('company' => $defaults->company, |
|
551 |
'creditor_id' => $defaults->sepa_creditor_id, |
|
552 |
'src_charset' => 'UTF-8', |
|
553 |
'message_id' => $message_id, |
|
554 |
'grouped' => 1, |
|
555 |
'collection' => $vc eq 'customer', |
|
556 |
); |
|
656 |
my $sepa_xml; |
|
657 |
my %sepa_xml_params = ( |
|
658 |
'company' => $defaults->company, |
|
659 |
'creditor_id' => $defaults->sepa_creditor_id, |
|
660 |
'src_charset' => 'UTF-8', |
|
661 |
'message_id' => $message_id, |
|
662 |
'grouped' => 1, |
|
663 |
'collection' => $vc eq 'customer', |
|
664 |
); |
|
665 |
if (!$swiss_export) { |
|
666 |
$sepa_xml = SL::SEPA::XML->new(%sepa_xml_params); |
|
667 |
} else { |
|
668 |
$sepa_xml = SL::SEPA::SwissXML->new(%sepa_xml_params); |
|
669 |
} |
|
557 | 670 |
|
558 | 671 |
foreach my $item (@items) { |
559 | 672 |
my $requested_execution_date; |
... | ... | |
573 | 686 |
} |
574 | 687 |
} |
575 | 688 |
|
576 |
$sepa_xml->add_transaction({ 'src_iban' => $item->{our_iban}, |
|
577 |
'src_bic' => $item->{our_bic}, |
|
578 |
'dst_iban' => $item->{vc_iban}, |
|
579 |
'dst_bic' => $item->{vc_bic}, |
|
580 |
'company' => $item->{vc_name}, |
|
581 |
'company_number' => $item->{vc_number}, |
|
582 |
'amount' => $item->{amount}, |
|
583 |
'reference' => $item->{reference}, |
|
584 |
'mandator_id' => $mandator_id, |
|
585 |
'reference_date' => $item->{reference_date}, |
|
586 |
'execution_date' => $requested_execution_date, |
|
587 |
'end_to_end_id' => $item->{end_to_end_id}, |
|
588 |
'date_of_signature' => $item->{mandate_date_of_signature}, }); |
|
689 |
my $transaction_data = { |
|
690 |
'src_iban' => $item->{our_iban}, |
|
691 |
'src_bic' => $item->{our_bic}, |
|
692 |
'dst_iban' => $item->{vc_iban}, |
|
693 |
'dst_bic' => $item->{vc_bic}, |
|
694 |
'company' => $item->{vc_name}, |
|
695 |
'company_number' => $item->{vc_number}, |
|
696 |
'amount' => $item->{amount}, |
|
697 |
'reference' => $item->{reference}, |
|
698 |
'mandator_id' => $mandator_id, |
|
699 |
'reference_date' => $item->{reference_date}, |
|
700 |
'execution_date' => $requested_execution_date, |
|
701 |
'end_to_end_id' => $item->{end_to_end_id}, |
|
702 |
'date_of_signature' => $item->{mandate_date_of_signature}, |
|
703 |
}; |
|
704 |
|
|
705 |
# set data for swiss xml export |
|
706 |
if ($swiss_export) { |
|
707 |
|
|
708 |
$transaction_data->{currency} = $item->{currency_name}; |
|
709 |
$transaction_data->{is_qrbill} = 0; |
|
710 |
$transaction_data->{is_sepa_payment} = 0; |
|
711 |
|
|
712 |
if ($item->{qrbill_data}) { |
|
713 |
my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data}); |
|
714 |
# check if valid qr-bill |
|
715 |
if (!$qr_obj->is_valid) { |
|
716 |
$form->show_generic_error($locale->text('QR bill data invalid.')); |
|
717 |
} |
|
718 |
$transaction_data->{is_qrbill} = 1; |
|
719 |
|
|
720 |
# set qr reference type |
|
721 |
# setting these according to example pain_001_Example_PT_D_QRR_SCOR.xml |
|
722 |
# 'QRR' |
|
723 |
if ($qr_obj->{payment_reference}->{reference_type} eq 'QRR') { |
|
724 |
$transaction_data->{end_to_end_id} = 'ENDTOENDID-QRR'; |
|
725 |
} |
|
726 |
# 'SCOR' |
|
727 |
elsif ($qr_obj->{payment_reference}->{reference_type} eq 'SCOR') { |
|
728 |
$transaction_data->{end_to_end_id} = 'ENDTOENDID-SCOR'; |
|
729 |
} |
|
730 |
# 'NON' |
|
731 |
# (using the default end_to_end_id given above) |
|
732 |
|
|
733 |
# data for remittance information |
|
734 |
# this contains the reference for 'QRR' or 'SCOR' |
|
735 |
$transaction_data->{reference} = $qr_obj->{payment_reference}->{reference}; |
|
736 |
# this can be used in any case |
|
737 |
$transaction_data->{unstructured_message} = $qr_obj->{additional_information}->{unstructured_message}; |
|
738 |
|
|
739 |
# set currency and amount |
|
740 |
$transaction_data->{currency} = $qr_obj->{payment_amount_information}->{currency}; |
|
741 |
$transaction_data->{amount} = $qr_obj->{payment_amount_information}->{amount}; |
|
742 |
|
|
743 |
# set creditor name and address from qr data |
|
744 |
$transaction_data->{creditor_name} = $qr_obj->{creditor}->{name}; |
|
745 |
$transaction_data->{creditor_street_name} = $qr_obj->get_creditor_street_name; |
|
746 |
$transaction_data->{creditor_building_number} = $qr_obj->get_creditor_building_number; |
|
747 |
$transaction_data->{creditor_postal_code} = $qr_obj->get_creditor_post_code; |
|
748 |
$transaction_data->{creditor_town_name} = $qr_obj->get_creditor_town_name; |
|
749 |
$transaction_data->{creditor_country} = $qr_obj->{creditor}->{country}; |
|
750 |
|
|
751 |
# set creditor iban |
|
752 |
$transaction_data->{dst_iban} = $qr_obj->{creditor_information}->{iban}; |
|
753 |
} else { |
|
754 |
# if no qr-bill data is given, we want to set the creditor address from the vc data |
|
755 |
# this is not needed for the creditor name as it is set above |
|
756 |
|
|
757 |
$transaction_data->{has_creditor_address} = 0; |
|
758 |
|
|
759 |
if ($item->{vc_zipcode} && $item->{vc_city} && $item->{vc_country}) { |
|
760 |
$transaction_data->{has_creditor_address} = 1; |
|
761 |
|
|
762 |
$transaction_data->{creditor_street_name} = get_street_name_from_address_line($item->{vc_street}); |
|
763 |
$transaction_data->{creditor_building_number} = get_building_number_from_address_line($item->{vc_building_number}); |
|
764 |
|
|
765 |
$transaction_data->{creditor_postal_code} = $item->{vc_zipcode}; |
|
766 |
$transaction_data->{creditor_town_name} = $item->{vc_city}; |
|
767 |
# use ISO 3166-1 alpha-2 country code |
|
768 |
$transaction_data->{creditor_country} = SL::Helper::ISO3166::map_name_to_alpha_2_code($item->{vc_country}); |
|
769 |
} |
|
770 |
|
|
771 |
# if the Iban does not start with 'CH' or 'LI' we assume it is a SEPA payment |
|
772 |
if ($item->{vc_iban} !~ /^(CH|LI)/) { |
|
773 |
$transaction_data->{is_sepa_payment} = 1; |
|
774 |
|
|
775 |
# check if currency is EUR |
|
776 |
if ($transaction_data->{currency} ne 'EUR') { |
|
777 |
$form->show_generic_error($locale->text('SEPA payments must be in EUR.')); |
|
778 |
} |
|
779 |
|
|
780 |
# check if destination BIC is set |
|
781 |
if (!$transaction_data->{dst_bic}) { |
|
782 |
$form->show_generic_error($locale->text('SEPA payments require a destination BIC.')); |
|
783 |
} |
|
784 |
# TODO: set reference (unstructured or SCOR) |
|
785 |
# -> where should the data come from? |
|
786 |
} |
|
787 |
} |
|
788 |
} |
|
789 |
|
|
790 |
$sepa_xml->add_transaction($transaction_data); |
|
589 | 791 |
} |
590 | 792 |
|
591 | 793 |
# Store the message ID used in each of the entries in order to |
... | ... | |
599 | 801 |
|
600 | 802 |
my $xml = $sepa_xml->to_xml(); |
601 | 803 |
|
602 |
print $cgi->header('-type' => 'application/octet-stream', |
|
603 |
'-content-disposition' => 'attachment; filename="SEPA_' . $message_id . ($vc eq 'customer' ? '.cdd' : '.cct') . '"', |
|
604 |
'-content-length' => length $xml); |
|
804 |
my $header; |
|
805 |
if ($swiss_export) { |
|
806 |
# I had to use encode for the length here, otherwise the xml output was cut off |
|
807 |
$header = { |
|
808 |
'-type' => 'text/xml; charset=utf-8', |
|
809 |
'-content-disposition' => 'attachment; filename="SWISS_' . $message_id . '.xml' . '"', |
|
810 |
'-content-length' => length Encode::encode('utf-8', $xml), |
|
811 |
}; |
|
812 |
} else { |
|
813 |
$header = { |
|
814 |
'-type' => 'application/octet-stream', |
|
815 |
'-content-disposition' => 'attachment; filename="SEPA_' . $message_id . ($vc eq 'customer' ? '.cdd' : '.cct') . '"', |
|
816 |
'-content-length' => length $xml, |
|
817 |
}; |
|
818 |
} |
|
819 |
|
|
820 |
print $cgi->header($header); |
|
605 | 821 |
print $xml; |
606 | 822 |
|
607 | 823 |
$main::lxdebug->leave_sub(); |
... | ... | |
711 | 927 |
combobox => [ |
712 | 928 |
action => [ t8('Actions') ], |
713 | 929 |
action => [ |
714 |
t8('SEPA XML download'),
|
|
930 |
!$::instance_conf->get_sepa_swiss_xml_export ? t8('SEPA XML download') : t8('Swiss XML download'),
|
|
715 | 931 |
submit => [ '#form', { action => 'bank_transfer_download_sepa_xml' } ], |
716 | 932 |
checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ], |
717 | 933 |
], |
... | ... | |
724 | 940 |
t8('Mark as closed'), |
725 | 941 |
submit => [ '#form', { action => 'bank_transfer_mark_as_closed' } ], |
726 | 942 |
checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ], |
727 |
confirm => [ $params{is_vendor} ? t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.') |
|
728 |
: t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.') ], |
|
943 |
confirm => [ !$::instance_conf->get_sepa_swiss_xml_export ? |
|
944 |
$params{is_vendor} ? t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.') |
|
945 |
: t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.') |
|
946 |
: t8('Do you really want to close the selected Swiss XML exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.') ], |
|
729 | 947 |
], |
730 | 948 |
action => [ |
731 |
t8('Undo SEPA exports'),
|
|
949 |
!$::instance_conf->get_sepa_swiss_xml_export ? t8('Undo SEPA exports') : t8('Undo Swiss XML exports'),
|
|
732 | 950 |
submit => [ '#form', { action => 'bank_transfer_undo_sepa_xml' } ], |
733 | 951 |
checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ], |
734 |
confirm => [ t8('Do you really want to undo the selected SEPA exports? You have to reassign the export again.') ], |
|
952 |
confirm => [ !$::instance_conf->get_sepa_swiss_xml_export ? |
|
953 |
t8('Do you really want to undo the selected SEPA exports? You have to reassign the export again.') : |
|
954 |
t8('Do you really want to undo the selected Swiss XML exports? You have to reassign the export again.') ], |
|
735 | 955 |
], |
736 | 956 |
], # end of combobox "Actions" |
737 | 957 |
); |
menus/user/00-erp.yaml | ||
---|---|---|
772 | 772 |
id: cash_bank_transfer_via_sepa |
773 | 773 |
name: Bank transfer via SEPA |
774 | 774 |
order: 400 |
775 |
access: cash |
|
775 |
access: cash & !client/sepa_swiss_xml_export |
|
776 |
module: sepa.pl |
|
777 |
params: |
|
778 |
action: bank_transfer_add |
|
779 |
vc: vendor |
|
780 |
- parent: cash |
|
781 |
id: cash_bank_transfer_via_swiss_xml |
|
782 |
name: Bank transfer via swiss XML |
|
783 |
order: 450 |
|
784 |
access: cash & client/sepa_swiss_xml_export |
|
776 | 785 |
module: sepa.pl |
777 | 786 |
params: |
778 | 787 |
action: bank_transfer_add |
... | ... | |
856 | 865 |
id: cash_reports_bank_transfers_via_sepa |
857 | 866 |
name: Bank transfers via SEPA |
858 | 867 |
order: 400 |
859 |
access: cash |
|
868 |
access: cash & !client/sepa_swiss_xml_export |
|
869 |
module: sepa.pl |
|
870 |
params: |
|
871 |
action: bank_transfer_search |
|
872 |
vc: vendor |
|
873 |
- parent: cash_reports |
|
874 |
id: cash_reports_bank_transfers_via_swiss_xml |
|
875 |
name: Bank transfers via swiss XML |
|
876 |
order: 400 |
|
877 |
access: cash & client/sepa_swiss_xml_export |
|
860 | 878 |
module: sepa.pl |
861 | 879 |
params: |
862 | 880 |
action: bank_transfer_search |
templates/design40_webpages/client_config/_features.html | ||
---|---|---|
238 | 238 |
<td>[% L.input_tag('defaults.sepa_set_skonto_date_buffer_in_days_as_number', SELF.defaults.sepa_set_skonto_date_buffer_in_days_as_number, size=10, class='wi-small' ) %]</td> |
239 | 239 |
<td class="long-desc">[% LxERP.t8('In addition to the above date functions, subtract the following amount of days from the calculated date as a buffer.') %]</td> |
240 | 240 |
</tr> |
241 |
<tr> |
|
242 |
<th>[% 'Activate Swiss Bank Transfer XML Export' | $T8 %]</th> |
|
243 |
<td>[% L.yes_no_tag('defaults.sepa_swiss_xml_export', SELF.defaults.sepa_swiss_xml_export) %]</td> |
|
244 |
<td class="long-desc">[% LxERP.t8('Bank transfer via SEPA is replaced by Bank transfer via swiss XML. According to Swiss Payment Standards, Customer Credit Transfer Initiation (pain.001).') %]</td> |
|
245 |
</tr> |
|
241 | 246 |
|
242 | 247 |
<tr> |
243 | 248 |
<th class="caption" colspan="3">[% LxERP.t8("Experimental Features") %]</th> |
templates/design40_webpages/sepa/bank_transfer_add.html | ||
---|---|---|
59 | 59 |
<input type="hidden" id="amount_less_skonto_[% loop.count %]" name="amount_less_skonto_[% loop.count %]" value="[% LxERP.format_amount(invoice.amount_less_skonto, 2) %]"> |
60 | 60 |
<input type="hidden" id="invoice_open_amount_[% loop.count %]" name="invoice_open_amount_[% loop.count %]" value="[% LxERP.format_amount(invoice.open_amount - invoice.transfer_amount, 2) %]"> |
61 | 61 |
<input type="hidden" id="skonto_amount_[% loop.count %]" name="skonto_amount_[% loop.count %]" value="[% LxERP.format_amount(invoice.skonto_amount, 2) %]"> |
62 |
[% L.hidden_tag('bank_transfers[].type', invoice.type) %] |
|
62 | 63 |
[% IF invoice.vc_bank_info_ok && !invoice.is_sepa_blocked %] |
63 | 64 |
[% L.checkbox_tag("ids[]", value=invoice.id, checked=invoice.checked) %] |
64 | 65 |
[% END %] |
... | ... | |
124 | 125 |
<p><sup>(1)</sup> |
125 | 126 |
[% IF is_vendor %] |
126 | 127 |
[% 'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' | $T8 %] |
127 |
[% "The required information consists of the IBAN and the BIC." | $T8 %] |
|
128 |
[% IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
129 |
[% "The required information consists of the IBAN and the BIC." | $T8 %] |
|
130 |
[% ELSE %] |
|
131 |
[% "The required information consists of the IBAN for domestic payments. And IBAN and the BIC for SEPA payments." | $T8 %] |
|
132 |
[% END %] |
|
128 | 133 |
[% ELSE %] |
129 | 134 |
[% 'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' | $T8 %] |
130 | 135 |
[% "The required information consists of the IBAN, the BIC, the mandator ID and the mandate's date of signature." | $T8 %] |
templates/design40_webpages/sepa/bank_transfer_create.html | ||
---|---|---|
50 | 50 |
<th>[% IF is_vendor %][% 'Vendor' | $T8 %][% ELSE %][% LxERP.t8('Customer') %][% END %]</th> |
51 | 51 |
<th>[% 'IBAN' | $T8 %]</th> |
52 | 52 |
<th>[% 'BIC' | $T8 %]</th> |
53 |
<th>[% 'Bank' | $T8 %]</th> |
|
53 |
[% IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
54 |
<th>[% 'Bank' | $T8 %]</th> |
|
55 |
[% END %] |
|
54 | 56 |
[% IF vc == 'customer' %] |
55 | 57 |
<th>[% 'Mandator ID' | $T8 %]</th> |
56 | 58 |
<th>[% 'Mandate Date of Signature' | $T8 %]</th> |
... | ... | |
60 | 62 |
<tbody> |
61 | 63 |
[% FOREACH vbi = VC_BANK_INFO %] |
62 | 64 |
<tr class="listrow[% loop.count % 1 %]"> |
63 |
<td> <input type="hidden" name="vc_bank_info[+].id" value="[% HTML.escape(vbi.id) %]"> <input type="hidden" name="vc_bank_info[].name" value="[% HTML.escape(vbi.name) %]"> [% HTML.escape(vbi.name) %] </td> |
|
64 |
<td><input type="text" name="vc_bank_info[].iban" size="34" value="[% HTML.escape(vbi.iban.substr(0, 34)) %]" maxlength="34"></td> |
|
65 |
<td><input type="text" name="vc_bank_info[].bic" size="20" value="[% HTML.escape(vbi.bic.substr(0, 20)) %]" maxlength="20"></td> |
|
66 |
<td><input type="text" name="vc_bank_info[].bank" size="30" value="[% HTML.escape(vbi.bank) %]"></td> |
|
67 |
[% IF vc == 'customer' %] |
|
68 |
<td><input type="text" name="vc_bank_info[].mandator_id" size="30" value="[% HTML.escape(vbi.mandator_id) %]"></td> |
|
69 |
<td>[% L.date_tag("vc_bank_info[].mandate_date_of_signature", vbi.mandate_date_of_signature) %]</td> |
|
65 |
[% IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
66 |
<td> <input type="hidden" name="vc_bank_info[+].id" value="[% HTML.escape(vbi.id) %]"> <input type="hidden" name="vc_bank_info[].name" value="[% HTML.escape(vbi.name) %]"> [% HTML.escape(vbi.name) %] </td> |
|
67 |
<td><input type="text" name="vc_bank_info[].iban" size="34" value="[% HTML.escape(vbi.iban.substr(0, 34)) %]" maxlength="34"></td> |
|
68 |
<td><input type="text" name="vc_bank_info[].bic" size="20" value="[% HTML.escape(vbi.bic.substr(0, 20)) %]" maxlength="20"></td> |
|
69 |
<td><input type="text" name="vc_bank_info[].bank" size="30" value="[% HTML.escape(vbi.bank) %]"></td> |
|
70 |
[% IF vc == 'customer' %] |
|
71 |
<td><input type="text" name="vc_bank_info[].mandator_id" size="30" value="[% HTML.escape(vbi.mandator_id) %]"></td> |
|
72 |
<td>[% L.date_tag("vc_bank_info[].mandate_date_of_signature", vbi.mandate_date_of_signature) %]</td> |
|
73 |
[% END %] |
|
74 |
[% ELSE %] |
|
75 |
[% L.hidden_tag('vc_bank_info[+].id', vbi.id) %] |
|
76 |
[% L.hidden_tag('vc_bank_info[].type', vbi.type) %] |
|
77 |
[% IF vbi.type == 'QRBILL' %] |
|
78 |
[% L.hidden_tag('vc_bank_info[].name', vbi.name) %] |
|
79 |
[% ELSIF vbi.type == 'DOMESTIC' %] |
|
80 |
<td> |
|
81 |
[% HTML.escape(vbi.name) %] |
|
82 |
[% L.hidden_tag('vc_bank_info[].name', vbi.name) %] |
|
83 |
</td> |
|
84 |
<td>[% L.input_tag('vc_bank_info[].iban', vbi.iban) %]</td> |
|
85 |
[% ELSE %] |
|
86 |
<td> |
|
87 |
[% HTML.escape(vbi.name) %] |
|
88 |
[% L.hidden_tag('vc_bank_info[].name', vbi.name) %] |
|
89 |
</td> |
|
90 |
<td>[% L.input_tag('vc_bank_info[].iban', vbi.iban) %]</td> |
|
91 |
<td>[% L.input_tag('vc_bank_info[].bic', vbi.bic) %]</td> |
|
92 |
[% END %] |
|
70 | 93 |
[% END %] |
71 | 94 |
</tr> |
72 | 95 |
[% END %] |
... | ... | |
99 | 122 |
<input type="hidden" id="amount_less_skonto_[% loop.count %]" name="amount_less_skonto_[% loop.count %]" value="[% LxERP.format_amount(bank_transfer.amount_less_skonto, 2) %]"> |
100 | 123 |
<input type="hidden" id="skonto_amount_[% loop.count %]" name="skonto_amount_[% loop.count %]" value="[% LxERP.format_amount(bank_transfer.skonto_amount, 2) %]"> |
101 | 124 |
<input type="hidden" id="invoice_open_amount_[% loop.count %]" name="invoice_open_amount_[% loop.count %]" value="[% LxERP.format_amount(bank_transfer.open_amount, 2) %]"> |
125 |
[% IF INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
126 |
[% L.hidden_tag('bank_transfers[].type', bank_transfer.type) %] |
|
127 |
[% END %] |
|
102 | 128 |
[% IF loop.first || (previous_vcname != bank_transfer.vcname) %] |
103 | 129 |
<a href="controller.pl?action=CustomerVendor/edit&db=[% vc %]&id=[% HTML.url(bank_transfer.vc_id) %]&callback=[% HTML.url('sepa.pl?action=bank_transfer_add&vc=' _ vc) %]"> |
104 | 130 |
[% GET HTML.escape(bank_transfer.vcname); SET previous_vcname = bank_transfer.vcname; %] |
templates/design40_webpages/sepa/bank_transfer_created.html | ||
---|---|---|
3 | 3 |
|
4 | 4 |
<h1>[% title %]</h1> |
5 | 5 |
|
6 |
<p>[% 'The SEPA export has been created.' | $T8 %]</p> |
|
7 |
<ul> |
|
8 |
<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> |
|
9 |
<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> |
|
10 |
</ul> |
|
6 |
[% IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
7 |
<p>[% 'The SEPA export has been created.' | $T8 %]</p> |
|
8 |
<ul> |
|
9 |
<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> |
|
10 |
<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> |
|
11 |
</ul> |
|
12 |
[% ELSE %] |
|
13 |
<p>[% 'The swiss XML export has been created.' | $T8 %]</p> |
|
14 |
<ul> |
|
15 |
<li><a href="sepa.pl?action=bank_transfer_download_sepa_xml&id=[% HTML.url(id) %]"> [% 'Download swiss XML export file' | $T8 %] </a></li> |
|
16 |
<li><a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]"> [% 'List open bank exports' | $T8 %] </a></li> |
|
17 |
</ul> |
|
18 |
[% END %] |
templates/design40_webpages/sepa/bank_transfer_search.html | ||
---|---|---|
22 | 22 |
</td> |
23 | 23 |
</tr> |
24 | 24 |
<tr> |
25 |
<th>[% LxERP.t8("SEPA message ID") %]</th> |
|
25 |
<th> |
|
26 |
[% IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
27 |
[% LxERP.t8("SEPA message ID") %] |
|
28 |
[% ELSE %] |
|
29 |
[% LxERP.t8("Message ID") %] |
|
30 |
[% END %] |
|
31 |
</th> |
|
26 | 32 |
<td>[% L.input_tag("f_message_id") %]</td> |
27 | 33 |
</tr> |
28 | 34 |
<tr> |
templates/webpages/client_config/_features.html | ||
---|---|---|
528 | 528 |
<td align="right">[% LxERP.t8('In addition to the above date functions, subtract the following amount of days from the calculated date as a buffer.') %]</td> |
529 | 529 |
<td>[% L.input_tag('defaults.sepa_set_skonto_date_buffer_in_days_as_number', SELF.defaults.sepa_set_skonto_date_buffer_in_days_as_number, style=style) %]</td> |
530 | 530 |
</tr> |
531 |
<tr> |
|
532 |
<td align="right">[% LxERP.t8('Bank transfer via SEPA is replaced by Bank transfer via swiss XML. According to Swiss Payment Standards, Customer Credit Transfer Initiation (pain.001).') %]</td> |
|
533 |
<td>[% L.yes_no_tag('defaults.sepa_swiss_xml_export', SELF.defaults.sepa_swiss_xml_export) %]</td> |
|
534 |
</tr> |
|
531 | 535 |
<tr><td class="listheading" colspan="4">[% LxERP.t8("Experimental Features") %]</td></tr> |
532 | 536 |
<tr> |
533 | 537 |
<td align="right">[% LxERP.t8('new order controller') %]</td> |
templates/webpages/sepa/bank_transfer_add.html | ||
---|---|---|
51 | 51 |
<input type="hidden" id="amount_less_skonto_[% loop.count %]" name="amount_less_skonto_[% loop.count %]" value="[% LxERP.format_amount(invoice.amount_less_skonto, 2) %]"> |
52 | 52 |
<input type="hidden" id="invoice_open_amount_[% loop.count %]" name="invoice_open_amount_[% loop.count %]" value="[% LxERP.format_amount(invoice.open_amount - invoice.transfer_amount, 2) %]"> |
53 | 53 |
<input type="hidden" id="skonto_amount_[% loop.count %]" name="skonto_amount_[% loop.count %]" value="[% LxERP.format_amount(invoice.skonto_amount, 2) %]"> |
54 |
|
|
54 |
[% L.hidden_tag('bank_transfers[].type', invoice.type) %] |
|
55 | 55 |
|
56 | 56 |
<tr class="listrow[% IF (!invoice.vc_bank_info_ok && invoice.checked) || invoice.is_sepa_blocked %]_error[% END %]"> |
57 | 57 |
<td align="center"> |
... | ... | |
117 | 117 |
<sup>(1)</sup> |
118 | 118 |
[%- IF is_vendor %] |
119 | 119 |
[%- 'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' | $T8 %] |
120 |
[% "The required information consists of the IBAN and the BIC." | $T8 %] |
|
120 |
[%- IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
121 |
[% "The required information consists of the IBAN and the BIC." | $T8 %] |
|
122 |
[%- ELSE %] |
|
123 |
[% "The required information consists of the IBAN for domestic payments. And IBAN and the BIC for SEPA payments." | $T8 %] |
|
124 |
[%- END %] |
|
121 | 125 |
[%- ELSE %] |
122 | 126 |
[%- 'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' | $T8 %] |
123 | 127 |
[% "The required information consists of the IBAN, the BIC, the mandator ID and the mandate's date of signature." | $T8 %] |
templates/webpages/sepa/bank_transfer_create.html | ||
---|---|---|
47 | 47 |
<th class="listheading">[%- IF is_vendor %][% 'Vendor' | $T8 %][%- ELSE %][%- LxERP.t8('Customer') %][%- END %]</th> |
48 | 48 |
<th class="listheading">[% 'IBAN' | $T8 %]</th> |
49 | 49 |
<th class="listheading">[% 'BIC' | $T8 %]</th> |
50 |
<th class="listheading">[% 'Bank' | $T8 %]</th> |
|
50 |
[%- IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
51 |
<th class="listheading">[% 'Bank' | $T8 %]</th> |
|
52 |
[%- END %] |
|
51 | 53 |
[% IF vc == 'customer' %] |
52 | 54 |
<th class="listheading">[% 'Mandator ID' | $T8 %]</th> |
53 | 55 |
<th class="listheading">[% 'Mandate Date of Signature' | $T8 %]</th> |
... | ... | |
56 | 58 |
|
57 | 59 |
[%- FOREACH vbi = VC_BANK_INFO %] |
58 | 60 |
<tr class="listrow[% loop.count % 1 %]"> |
59 |
<td> |
|
60 |
<input type="hidden" name="vc_bank_info[+].id" value="[% HTML.escape(vbi.id) %]"> |
|
61 |
<input type="hidden" name="vc_bank_info[].name" value="[% HTML.escape(vbi.name) %]"> |
|
62 |
[% HTML.escape(vbi.name) %] |
|
63 |
</td> |
|
64 |
<td><input name="vc_bank_info[].iban" size="34" value="[% HTML.escape(vbi.iban.substr(0, 34)) %]" maxlength="34"></td> |
|
65 |
<td><input name="vc_bank_info[].bic" size="20" value="[% HTML.escape(vbi.bic.substr(0, 20)) %]" maxlength="20"></td> |
|
66 |
<td><input name="vc_bank_info[].bank" size="30" value="[% HTML.escape(vbi.bank) %]"></td> |
|
67 |
[% IF vc == 'customer' %] |
|
68 |
<td><input name="vc_bank_info[].mandator_id" size="30" value="[% HTML.escape(vbi.mandator_id) %]"></td> |
|
69 |
<td>[% L.date_tag("vc_bank_info[].mandate_date_of_signature", vbi.mandate_date_of_signature) %]</td> |
|
70 |
[%- END %] |
|
61 |
[%- IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
62 |
<td> |
|
63 |
<input type="hidden" name="vc_bank_info[+].id" value="[% HTML.escape(vbi.id) %]"> |
|
64 |
<input type="hidden" name="vc_bank_info[].name" value="[% HTML.escape(vbi.name) %]"> |
|
65 |
[% HTML.escape(vbi.name) %] |
|
66 |
</td> |
|
67 |
<td><input type="text" name="vc_bank_info[].iban" size="34" value="[% HTML.escape(vbi.iban.substr(0, 34)) %]" maxlength="34"></td> |
|
68 |
<td><input type="text" name="vc_bank_info[].bic" size="20" value="[% HTML.escape(vbi.bic.substr(0, 20)) %]" maxlength="20"></td> |
|
69 |
<td><input type="text" name="vc_bank_info[].bank" size="30" value="[% HTML.escape(vbi.bank) %]"></td> |
|
70 |
[%- IF vc == 'customer' %] |
|
71 |
<td><input type="text" name="vc_bank_info[].mandator_id" size="30" value="[% HTML.escape(vbi.mandator_id) %]"></td> |
|
72 |
<td>[% L.date_tag("vc_bank_info[].mandate_date_of_signature", vbi.mandate_date_of_signature) %]</td> |
|
73 |
[%- END %] |
|
74 |
[%- ELSE %] |
|
75 |
[% L.hidden_tag('vc_bank_info[+].id', vbi.id) %] |
|
76 |
[% L.hidden_tag('vc_bank_info[].type', vbi.type) %] |
|
77 |
[%- IF vbi.type == 'QRBILL' %] |
|
78 |
[% L.hidden_tag('vc_bank_info[].name', vbi.name) %] |
|
79 |
[%- ELSIF vbi.type == 'DOMESTIC' %] |
|
80 |
<td> |
|
81 |
[% HTML.escape(vbi.name) %] |
|
82 |
[% L.hidden_tag('vc_bank_info[].name', vbi.name) %] |
|
83 |
</td> |
|
84 |
<td>[% L.input_tag('vc_bank_info[].iban', vbi.iban) %]</td> |
|
85 |
[%- ELSE %] |
|
86 |
<td> |
|
87 |
[% HTML.escape(vbi.name) %] |
|
88 |
[% L.hidden_tag('vc_bank_info[].name', vbi.name) %] |
|
89 |
</td> |
|
90 |
<td>[% L.input_tag('vc_bank_info[].iban', vbi.iban) %]</td> |
|
91 |
<td>[% L.input_tag('vc_bank_info[].bic', vbi.bic) %]</td> |
|
92 |
[%- END %] |
|
93 |
[%- END %] |
|
71 | 94 |
</tr> |
72 | 95 |
[%- END %] |
73 | 96 |
</table> |
... | ... | |
96 | 119 |
<input type="hidden" id="amount_less_skonto_[% loop.count %]" name="amount_less_skonto_[% loop.count %]" value="[% LxERP.format_amount(bank_transfer.amount_less_skonto, 2) %]"> |
97 | 120 |
<input type="hidden" id="skonto_amount_[% loop.count %]" name="skonto_amount_[% loop.count %]" value="[% LxERP.format_amount(bank_transfer.skonto_amount, 2) %]"> |
98 | 121 |
<input type="hidden" id="invoice_open_amount_[% loop.count %]" name="invoice_open_amount_[% loop.count %]" value="[% LxERP.format_amount(bank_transfer.open_amount, 2) %]"> |
99 |
|
|
122 |
[%- IF INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
123 |
[% L.hidden_tag('bank_transfers[].type', bank_transfer.type) %] |
|
124 |
[%- END %] |
|
100 | 125 |
<tr class="listrow[% loop.count % 2 %]"> |
101 | 126 |
<td> |
102 | 127 |
[%- IF loop.first || (previous_vcname != bank_transfer.vcname) %] |
templates/webpages/sepa/bank_transfer_created.html | ||
---|---|---|
6 | 6 |
[% 'The SEPA export has been created.' | $T8 %] |
7 | 7 |
</p> |
8 | 8 |
|
9 |
<p> |
|
9 |
[% IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
10 |
<p>[% 'The SEPA export has been created.' | $T8 %]</p> |
|
10 | 11 |
<ul> |
11 |
<li> |
|
12 |
<a href="sepa.pl?action=bank_transfer_download_sepa_xml&id=[% HTML.url(id) %]&vc=[% HTML.url(vc) %]"> |
|
13 |
[% 'Download SEPA XML export file' | $T8 %] |
|
14 |
</a> |
|
15 |
</li> |
|
16 |
|
|
17 |
<li> |
|
18 |
<a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]"> |
|
19 |
[% 'List open SEPA exports' | $T8 %] |
|
20 |
</a> |
|
21 |
</li> |
|
12 |
<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> |
|
13 |
<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> |
|
22 | 14 |
</ul> |
23 |
</p> |
|
24 |
|
|
15 |
[% ELSE %] |
|
16 |
<p>[% 'The swiss XML export has been created.' | $T8 %]</p> |
|
17 |
<ul> |
|
18 |
<li><a href="sepa.pl?action=bank_transfer_download_sepa_xml&id=[% HTML.url(id) %]"> [% 'Download swiss XML export file' | $T8 %] </a></li> |
|
19 |
<li><a href="sepa.pl?action=bank_transfer_list&l_open=1&l_not_executed=1&vc=[% HTML.url(vc) %]"> [% 'List open bank exports' | $T8 %] </a></li> |
|
20 |
</ul> |
|
21 |
[% END %] |
templates/webpages/sepa/bank_transfer_search.html | ||
---|---|---|
18 | 18 |
</tr> |
19 | 19 |
|
20 | 20 |
<tr> |
21 |
<td align="right">[% LxERP.t8("SEPA message ID") %]</td> |
|
21 |
<td align="right"> |
|
22 |
[%- IF !INSTANCE_CONF.get_sepa_swiss_xml_export %] |
|
23 |
[% LxERP.t8("SEPA message ID") %] |
|
24 |
[%- ELSE %] |
|
25 |
[% LxERP.t8("Message ID") %] |
|
26 |
[%- END %] |
|
27 |
</td> |
|
22 | 28 |
<td>[% L.input_tag("f_message_id") %]</td> |
23 | 29 |
</tr> |
24 | 30 |
|
Auch abrufbar als: Unified diff
Schweizer Banküberweisung via XML implementiert
Analog zu "Überweisung via SEPA XML"
Das Feature kann in der Mandantenkonfiguration ein-/ausgeschaltet
werden. Wenn eingeschaltet, wird anstatt des SEPA export ein Export
gemäss dem schweizer Standard erstellt. Dementsprechend werden die
Texte im Menü und in den Templates ersetzt, und im Code mittels
Weiche auf den schweizer Export umgeschaltet.
Gemäss Standard:
Swiss Payment Standards
Customer Credit Transfer Initiation (pain.001)