Revision 1c603341
Von Jan Büren vor etwa 15 Jahren hinzugefügt
SL/Chart.pm | ||
---|---|---|
1 |
package SL::Chart; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use SL::Form; |
|
6 |
use SL::DBUtils; |
|
7 |
|
|
8 |
sub list { |
|
9 |
$main::lxdebug->enter_sub(); |
|
10 |
|
|
11 |
my $self = shift; |
|
12 |
my %params = @_; |
|
13 |
|
|
14 |
my $myconfig = \%main::myconfig; |
|
15 |
my $form = $main::form; |
|
16 |
|
|
17 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
18 |
|
|
19 |
my @values = (); |
|
20 |
my @where = (); |
|
21 |
|
|
22 |
if ($params{link}) { |
|
23 |
if ($params{link} =~ '%') { |
|
24 |
push @where, "c.link LIKE ?"; |
|
25 |
push @values, $params{link}; |
|
26 |
|
|
27 |
} else { |
|
28 |
push @where, "(c.link = ?) OR (c.link LIKE ?) OR (c.link LIKE ?) OR (c.link LIKE ?)"; |
|
29 |
push @values, $params{link}, '%:' . $params{link} . ':%', '%:' . $params{link}, $params{link} . ':%'; |
|
30 |
} |
|
31 |
} |
|
32 |
|
|
33 |
my $where = scalar @where ? 'WHERE ' . join(' AND ', map { "($_)" } @where) : ''; |
|
34 |
|
|
35 |
my $query = |
|
36 |
qq|SELECT c.id, c.accno, c.description, c.link |
|
37 |
FROM chart c |
|
38 |
$where |
|
39 |
ORDER BY c.accno|; |
|
40 |
|
|
41 |
my $charts = selectall_hashref_query($form, $dbh, $query, @values); |
|
42 |
|
|
43 |
$main::lxdebug->leave_sub(); |
|
44 |
|
|
45 |
return $charts; |
|
46 |
} |
|
47 |
|
|
48 |
1; |
|
49 |
|
SL/SEPA.pm | ||
---|---|---|
1 |
package SL::SEPA; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use POSIX qw(strftime); |
|
6 |
|
|
7 |
use SL::DBUtils; |
|
8 |
|
|
9 |
sub retrieve_open_invoices { |
|
10 |
$main::lxdebug->enter_sub(); |
|
11 |
|
|
12 |
my $self = shift; |
|
13 |
my %params = @_; |
|
14 |
|
|
15 |
my $myconfig = \%main::myconfig; |
|
16 |
my $form = $main::form; |
|
17 |
|
|
18 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
19 |
|
|
20 |
my $query = |
|
21 |
qq| |
|
22 |
SELECT ap.id, ap.invnumber, ap.vendor_id, ap.amount AS invoice_amount, ap.invoice, |
|
23 |
v.name AS vendorname, |
|
24 |
|
|
25 |
COALESCE(v.iban, '') <> '' AND COALESCE(v.bic, '') <> '' AS vendor_bank_info_ok, |
|
26 |
|
|
27 |
ap.amount - ap.paid - COALESCE(open_transfers.amount, 0) AS open_amount |
|
28 |
|
|
29 |
FROM ap |
|
30 |
LEFT JOIN vendor v ON (ap.vendor_id = v.id) |
|
31 |
LEFT JOIN (SELECT sei.ap_id, SUM(sei.amount) AS amount |
|
32 |
FROM sepa_export_items sei |
|
33 |
LEFT JOIN sepa_export se ON (sei.sepa_export_id = se.id) |
|
34 |
WHERE NOT se.closed |
|
35 |
GROUP BY sei.ap_id) |
|
36 |
AS open_transfers ON (ap.id = open_transfers.ap_id) |
|
37 |
|
|
38 |
WHERE ap.amount > (COALESCE(open_transfers.amount, 0) + ap.paid) |
|
39 |
|
|
40 |
ORDER BY lower(v.name) ASC, lower(ap.invnumber) ASC |
|
41 |
|; |
|
42 |
|
|
43 |
my $results = selectall_hashref_query($form, $dbh, $query); |
|
44 |
|
|
45 |
$main::lxdebug->leave_sub(); |
|
46 |
|
|
47 |
return $results; |
|
48 |
} |
|
49 |
|
|
50 |
sub create_export { |
|
51 |
$main::lxdebug->enter_sub(); |
|
52 |
|
|
53 |
my $self = shift; |
|
54 |
my %params = @_; |
|
55 |
|
|
56 |
Common::check_params(\%params, qw(employee bank_transfers)); |
|
57 |
|
|
58 |
my $myconfig = \%main::myconfig; |
|
59 |
my $form = $main::form; |
|
60 |
|
|
61 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
62 |
|
|
63 |
my ($export_id) = selectfirst_array_query($form, $dbh, qq|SELECT nextval('sepa_export_id_seq')|); |
|
64 |
my $query = |
|
65 |
qq|INSERT INTO sepa_export (id, employee_id) |
|
66 |
VALUES (?, (SELECT id |
|
67 |
FROM employee |
|
68 |
WHERE login = ?))|; |
|
69 |
do_query($form, $dbh, $query, $export_id, $params{employee}); |
|
70 |
|
|
71 |
my $q_item_id = qq|SELECT nextval('id')|; |
|
72 |
my $h_item_id = prepare_query($form, $dbh, $q_item_id); |
|
73 |
|
|
74 |
my $q_insert = |
|
75 |
qq|INSERT INTO sepa_export_items (id, sepa_export_id, ap_id, chart_id, |
|
76 |
amount, requested_execution_date, reference, end_to_end_id, |
|
77 |
our_iban, our_bic, vendor_iban, vendor_bic) |
|
78 |
VALUES (?, ?, ?, ?, |
|
79 |
?, ?, ?, ?, |
|
80 |
?, ?, ?, ?)|; |
|
81 |
my $h_insert = prepare_query($form, $dbh, $q_insert); |
|
82 |
|
|
83 |
my $q_reference = |
|
84 |
qq|SELECT ap.invnumber, |
|
85 |
(SELECT COUNT(at.*) |
|
86 |
FROM acc_trans at |
|
87 |
LEFT JOIN chart c ON (at.chart_id = c.id) |
|
88 |
WHERE (at.trans_id = ?) |
|
89 |
AND (c.link LIKE '%AP_paid%')) |
|
90 |
+ |
|
91 |
(SELECT COUNT(sei.*) |
|
92 |
FROM sepa_export_items sei |
|
93 |
WHERE (sei.ap_id = ?)) |
|
94 |
AS num_payments |
|
95 |
FROM ap |
|
96 |
WHERE id = ?|; |
|
97 |
my $h_reference = prepare_query($form, $dbh, $q_reference); |
|
98 |
|
|
99 |
my @now = localtime; |
|
100 |
|
|
101 |
foreach my $transfer (@{ $params{bank_transfers} }) { |
|
102 |
if (!$transfer->{reference}) { |
|
103 |
do_statement($form, $h_reference, $q_reference, (conv_i($transfer->{ap_id})) x 3); |
|
104 |
|
|
105 |
my ($invnumber, $num_payments) = $h_reference->fetchrow_array(); |
|
106 |
$num_payments++; |
|
107 |
|
|
108 |
$transfer->{reference} = "${invnumber}-${num_payments}"; |
|
109 |
} |
|
110 |
|
|
111 |
$h_item_id->execute(); |
|
112 |
my ($item_id) = $h_item_id->fetchrow_array(); |
|
113 |
|
|
114 |
my $end_to_end_id = strftime "LXO%Y%m%d%H%M%S", localtime; |
|
115 |
my $item_id_len = length "$item_id"; |
|
116 |
my $num_zeroes = 35 - $item_id_len - length $end_to_end_id; |
|
117 |
$end_to_end_id .= '0' x $num_zeroes if (0 < $num_zeroes); |
|
118 |
$end_to_end_id .= $item_id; |
|
119 |
$end_to_end_id = substr $end_to_end_id, 0, 35; |
|
120 |
|
|
121 |
my @values = ($item_id, $export_id, |
|
122 |
conv_i($transfer->{ap_id}), conv_i($transfer->{chart_id}), |
|
123 |
$transfer->{amount}, conv_date($transfer->{requested_execution_date}), |
|
124 |
$transfer->{reference}, $end_to_end_id, |
|
125 |
map { my $pfx = $_; map { $transfer->{"${pfx}_${_}"} } qw(iban bic) } qw(our vendor)); |
|
126 |
|
|
127 |
do_statement($form, $h_insert, $q_insert, @values); |
|
128 |
} |
|
129 |
|
|
130 |
$h_insert->finish(); |
|
131 |
$h_item_id->finish(); |
|
132 |
|
|
133 |
$dbh->commit() unless ($params{dbh}); |
|
134 |
|
|
135 |
$main::lxdebug->leave_sub(); |
|
136 |
|
|
137 |
return $export_id; |
|
138 |
} |
|
139 |
|
|
140 |
sub retrieve_export { |
|
141 |
$main::lxdebug->enter_sub(); |
|
142 |
|
|
143 |
my $self = shift; |
|
144 |
my %params = @_; |
|
145 |
|
|
146 |
Common::check_params(\%params, qw(id)); |
|
147 |
|
|
148 |
my $myconfig = \%main::myconfig; |
|
149 |
my $form = $main::form; |
|
150 |
|
|
151 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
152 |
|
|
153 |
my ($joins, $columns); |
|
154 |
|
|
155 |
if ($params{details}) { |
|
156 |
$columns = ', ap.invoice'; |
|
157 |
$joins = 'LEFT JOIN ap ON (se.ap_id = ap.id)'; |
|
158 |
} |
|
159 |
|
|
160 |
my $query = |
|
161 |
qq|SELECT se.*, |
|
162 |
CASE WHEN COALESCE(e.name, '') <> '' THEN e.name ELSE e.login END AS employee |
|
163 |
FROM sepa_export se |
|
164 |
LEFT JOIN employee e ON (se.employee_id = e.id) |
|
165 |
WHERE se.id = ?|; |
|
166 |
|
|
167 |
my $export = selectfirst_hashref_query($form, $dbh, $query, conv_i($params{id})); |
|
168 |
|
|
169 |
if ($export->{id}) { |
|
170 |
my ($columns, $joins); |
|
171 |
|
|
172 |
if ($params{details}) { |
|
173 |
$columns = qq|, ap.invnumber, ap.invoice, v.name AS vendor_name, c.accno AS chart_accno, c.description AS chart_description|; |
|
174 |
$joins = qq|LEFT JOIN ap ON (sei.ap_id = ap.id) |
|
175 |
LEFT JOIN vendor v ON (ap.vendor_id = v.id) |
|
176 |
LEFT JOIN chart c ON (sei.chart_id = c.id)|; |
|
177 |
} |
|
178 |
|
|
179 |
$query = qq|SELECT sei.* |
|
180 |
$columns |
|
181 |
FROM sepa_export_items sei |
|
182 |
$joins |
|
183 |
WHERE sei.sepa_export_id = ?|; |
|
184 |
$export->{items} = selectall_hashref_query($form, $dbh, $query, conv_i($params{id})); |
|
185 |
|
|
186 |
} else { |
|
187 |
$export->{items} = []; |
|
188 |
} |
|
189 |
|
|
190 |
$main::lxdebug->leave_sub(); |
|
191 |
|
|
192 |
return $export; |
|
193 |
} |
|
194 |
|
|
195 |
sub close_export { |
|
196 |
$main::lxdebug->enter_sub(); |
|
197 |
|
|
198 |
my $self = shift; |
|
199 |
my %params = @_; |
|
200 |
|
|
201 |
Common::check_params(\%params, qw(id)); |
|
202 |
|
|
203 |
my $myconfig = \%main::myconfig; |
|
204 |
my $form = $main::form; |
|
205 |
|
|
206 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
207 |
|
|
208 |
my @ids = ref $params{id} eq 'ARRAY' ? @{ $params{id} } : ($params{id}); |
|
209 |
my $placeholders = join ', ', ('?') x scalar @ids; |
|
210 |
my $query = qq|UPDATE sepa_export SET closed = TRUE WHERE id IN ($placeholders)|; |
|
211 |
|
|
212 |
do_query($form, $dbh, $query, map { conv_i($_) } @ids); |
|
213 |
|
|
214 |
$dbh->commit() unless ($params{dbh}); |
|
215 |
|
|
216 |
$main::lxdebug->leave_sub(); |
|
217 |
} |
|
218 |
|
|
219 |
sub list_exports { |
|
220 |
$main::lxdebug->enter_sub(); |
|
221 |
|
|
222 |
my $self = shift; |
|
223 |
my %params = @_; |
|
224 |
|
|
225 |
my $myconfig = \%main::myconfig; |
|
226 |
my $form = $main::form; |
|
227 |
|
|
228 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
229 |
|
|
230 |
my %sort_columns = ( |
|
231 |
'id' => [ 'se.id', ], |
|
232 |
'export_date' => [ 'se.itime', ], |
|
233 |
'employee' => [ 'e.name', 'se.id', ], |
|
234 |
'executed' => [ 'se.executed', 'se.id', ], |
|
235 |
'closed' => [ 'se.closed', 'se.id', ], |
|
236 |
); |
|
237 |
|
|
238 |
my %sort_spec = create_sort_spec('defs' => \%sort_columns, 'default' => 'id', 'column' => $params{sortorder}, 'dir' => $params{sortdir}); |
|
239 |
|
|
240 |
my (@where, @values, @where_sub, @values_sub, %joins_sub); |
|
241 |
|
|
242 |
my $filter = $params{filter} || { }; |
|
243 |
|
|
244 |
foreach (qw(executed closed)) { |
|
245 |
push @where, $filter->{$_} ? "se.$_" : "NOT se.$_" if (exists $filter->{$_}); |
|
246 |
} |
|
247 |
|
|
248 |
my %operators = ('from' => '>=', |
|
249 |
'to' => '<='); |
|
250 |
|
|
251 |
foreach my $dir (qw(from to)) { |
|
252 |
next unless ($filter->{"export_date_${dir}"}); |
|
253 |
push @where, "se.itime $operators{$dir} ?::date"; |
|
254 |
push @values, $filter->{"export_date_${dir}"}; |
|
255 |
} |
|
256 |
|
|
257 |
if ($filter->{invnumber}) { |
|
258 |
push @where_sub, "ap.invnumber ILIKE ?"; |
|
259 |
push @values_sub, '%' . $filter->{invnumber} . '%'; |
|
260 |
$joins_sub{ap} = 1; |
|
261 |
} |
|
262 |
|
|
263 |
if ($filter->{vendor}) { |
|
264 |
push @where_sub, "v.name ILIKE ?"; |
|
265 |
push @values_sub, '%' . $filter->{vendor} . '%'; |
|
266 |
$joins_sub{ap} = 1; |
|
267 |
$joins_sub{vendor} = 1; |
|
268 |
} |
|
269 |
|
|
270 |
foreach my $type (qw(requested_execution execution)) { |
|
271 |
foreach my $dir (qw(from to)) { |
|
272 |
next unless ($filter->{"${type}_date_${dir}"}); |
|
273 |
push @where_sub, "(items.${type}_date IS NOT NULL) AND (items.${type}_date $operators{$dir} ?)"; |
|
274 |
push @values_sub, $filter->{"${type}_date_${_}"}; |
|
275 |
} |
|
276 |
} |
|
277 |
|
|
278 |
if (@where_sub) { |
|
279 |
my $joins_sub = ''; |
|
280 |
$joins_sub .= ' LEFT JOIN ap ON (items.ap_id = ap.id)' if ($joins_sub{ap}); |
|
281 |
$joins_sub .= ' LEFT JOIN vendor v ON (ap.vendor_id = v.id)' if ($joins_sub{vendor}); |
|
282 |
|
|
283 |
my $where_sub = join(' AND ', map { "(${_})" } @where_sub); |
|
284 |
|
|
285 |
my $query_sub = qq|se.id IN (SELECT items.sepa_export_id |
|
286 |
FROM sepa_export_items items |
|
287 |
$joins_sub |
|
288 |
WHERE $where_sub)|; |
|
289 |
|
|
290 |
push @where, $query_sub; |
|
291 |
push @values, @values_sub; |
|
292 |
} |
|
293 |
|
|
294 |
my $where = ' WHERE ' . join(' AND ', map { "(${_})" } @where) if (@where); |
|
295 |
|
|
296 |
my $query = |
|
297 |
qq|SELECT se.id, se.employee_id, se.executed, se.closed, itime::date AS export_date, |
|
298 |
e.name AS employee |
|
299 |
FROM sepa_export se |
|
300 |
LEFT JOIN ( |
|
301 |
SELECT emp.id, |
|
302 |
CASE WHEN COALESCE(emp.name, '') <> '' THEN emp.name ELSE emp.login END AS name |
|
303 |
FROM employee emp |
|
304 |
) AS e ON (se.employee_id = e.id) |
|
305 |
$where |
|
306 |
ORDER BY $sort_spec{sql}|; |
|
307 |
|
|
308 |
my $results = selectall_hashref_query($form, $dbh, $query, @values); |
|
309 |
|
|
310 |
$main::lxdebug->leave_sub(); |
|
311 |
|
|
312 |
return $results; |
|
313 |
} |
|
314 |
|
|
315 |
sub post_payment { |
|
316 |
$main::lxdebug->enter_sub(); |
|
317 |
|
|
318 |
my $self = shift; |
|
319 |
my %params = @_; |
|
320 |
|
|
321 |
Common::check_params(\%params, qw(items)); |
|
322 |
|
|
323 |
my $myconfig = \%main::myconfig; |
|
324 |
my $form = $main::form; |
|
325 |
|
|
326 |
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig); |
|
327 |
|
|
328 |
my @items = ref $params{items} eq 'ARRAY' ? @{ $params{items} } : ($params{items}); |
|
329 |
|
|
330 |
my %handles = ( |
|
331 |
'get_item' => [ qq|SELECT sei.* |
|
332 |
FROM sepa_export_items sei |
|
333 |
WHERE sei.id = ?| ], |
|
334 |
|
|
335 |
'get_ap' => [ qq|SELECT at.chart_id |
|
336 |
FROM acc_trans at |
|
337 |
LEFT JOIN chart c ON (at.chart_id = c.id) |
|
338 |
WHERE (trans_id = ?) |
|
339 |
AND ((c.link LIKE '%:AP') OR (c.link LIKE 'AP:%') OR (c.link = 'AP')) |
|
340 |
LIMIT 1| ], |
|
341 |
|
|
342 |
'add_acc_trans' => [ qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo) |
|
343 |
VALUES (?, ?, ?, ?, current_date, ?, '')| ], |
|
344 |
|
|
345 |
'update_ap' => [ qq|UPDATE ap |
|
346 |
SET paid = paid + ? |
|
347 |
WHERE id = ?| ], |
|
348 |
|
|
349 |
'finish_item' => [ qq|UPDATE sepa_export_items |
|
350 |
SET execution_date = ?, executed = TRUE |
|
351 |
WHERE id = ?| ], |
|
352 |
|
|
353 |
'has_unexecuted' => [ qq|SELECT sei1.id |
|
354 |
FROM sepa_export_items sei1 |
|
355 |
WHERE (sei1.sepa_export_id = (SELECT sei2.sepa_export_id |
|
356 |
FROM sepa_export_items sei2 |
|
357 |
WHERE sei2.id = ?)) |
|
358 |
AND NOT COALESCE(sei1.executed, FALSE) |
|
359 |
LIMIT 1| ], |
|
360 |
|
|
361 |
'do_close' => [ qq|UPDATE sepa_export |
|
362 |
SET executed = TRUE, closed = TRUE |
|
363 |
WHERE (id = ?)| ], |
|
364 |
); |
|
365 |
|
|
366 |
map { unshift @{ $_ }, prepare_query($form, $dbh, $_->[0]) } values %handles; |
|
367 |
|
|
368 |
foreach my $item (@items) { |
|
369 |
my $item_id = conv_i($item->{id}); |
|
370 |
|
|
371 |
# Retrieve the item data belonging to the ID. |
|
372 |
do_statement($form, @{ $handles{get_item} }, $item_id); |
|
373 |
my $orig_item = $handles{get_item}->[0]->fetchrow_hashref(); |
|
374 |
|
|
375 |
next if (!$orig_item); |
|
376 |
|
|
377 |
# Retrieve the invoice's AP chart ID. |
|
378 |
do_statement($form, @{ $handles{get_ap} }, $orig_item->{ap_id}); |
|
379 |
my ($ap_chart_id) = $handles{get_ap}->[0]->fetchrow_array(); |
|
380 |
|
|
381 |
# Record the payment in acc_trans offsetting AP. |
|
382 |
do_statement($form, @{ $handles{add_acc_trans} }, $orig_item->{ap_id}, $ap_chart_id, -1 * $orig_item->{amount}, $item->{execution_date}, ''); |
|
383 |
do_statement($form, @{ $handles{add_acc_trans} }, $orig_item->{ap_id}, $orig_item->{chart_id}, $orig_item->{amount}, $item->{execution_date}, $orig_item->{reference}); |
|
384 |
|
|
385 |
# Update the invoice to reflect the new paid amount. |
|
386 |
do_statement($form, @{ $handles{update_ap} }, $orig_item->{amount}, $orig_item->{ap_id}); |
|
387 |
|
|
388 |
# Update the item to reflect that it has been posted. |
|
389 |
do_statement($form, @{ $handles{finish_item} }, $item->{execution_date}, $item_id); |
|
390 |
|
|
391 |
# Check whether or not we can close the export itself if there are no unexecuted items left. |
|
392 |
do_statement($form, @{ $handles{has_unexecuted} }, $item_id); |
|
393 |
my ($has_unexecuted) = $handles{has_unexecuted}->[0]->fetchrow_array(); |
|
394 |
|
|
395 |
if (!$has_unexecuted) { |
|
396 |
do_statement($form, @{ $handles{do_close} }, $orig_item->{sepa_export_id}); |
|
397 |
} |
|
398 |
} |
|
399 |
|
|
400 |
map { $_->[0]->finish() } values %handles; |
|
401 |
|
|
402 |
$dbh->commit() unless ($params{dbh}); |
|
403 |
|
|
404 |
$main::lxdebug->leave_sub(); |
|
405 |
} |
|
406 |
|
|
407 |
1; |
SL/SEPA/XML.pm | ||
---|---|---|
1 |
package SL::SEPA::XML; |
|
2 |
|
|
3 |
use strict; |
|
4 |
use utf8; |
|
5 |
|
|
6 |
use Carp; |
|
7 |
use Encode; |
|
8 |
use List::Util qw(first sum); |
|
9 |
use List::MoreUtils qw(any); |
|
10 |
use POSIX qw(strftime); |
|
11 |
use Text::Iconv; |
|
12 |
use XML::Writer; |
|
13 |
|
|
14 |
use SL::SEPA::XML::Transaction; |
|
15 |
|
|
16 |
sub new { |
|
17 |
my $class = shift; |
|
18 |
my $self = {}; |
|
19 |
|
|
20 |
bless $self, $class; |
|
21 |
|
|
22 |
$self->_init(@_); |
|
23 |
|
|
24 |
return $self; |
|
25 |
} |
|
26 |
|
|
27 |
sub _init { |
|
28 |
my $self = shift; |
|
29 |
my %params = @_; |
|
30 |
|
|
31 |
$self->{transactions} = []; |
|
32 |
$self->{src_charset} = 'UTF-8'; |
|
33 |
$self->{grouped} = 0; |
|
34 |
|
|
35 |
map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company message_id grouped); |
|
36 |
|
|
37 |
$self->{iconv} = Text::Iconv->new($self->{src_charset}, "UTF-8") || croak "Unsupported source charset $self->{src_charset}."; |
|
38 |
|
|
39 |
my $missing_parameter = first { !$self->{$_} } qw(company message_id); |
|
40 |
croak "Missing parameter: $missing_parameter" if ($missing_parameter); |
|
41 |
|
|
42 |
map { $self->{$_} = $self->_replace_special_chars(decode('UTF-8', $self->{iconv}->convert($self->{$_}))) } qw(company message_id); |
|
43 |
} |
|
44 |
|
|
45 |
sub add_transaction { |
|
46 |
my $self = shift; |
|
47 |
|
|
48 |
foreach my $transaction (@_) { |
|
49 |
croak "Expecting hash reference." if (ref $transaction ne 'HASH'); |
|
50 |
push @{ $self->{transactions} }, SL::SEPA::XML::Transaction->new(%{ $transaction }, 'sepa' => $self); |
|
51 |
} |
|
52 |
|
|
53 |
return 1; |
|
54 |
} |
|
55 |
|
|
56 |
sub _replace_special_chars { |
|
57 |
my $self = shift; |
|
58 |
my $text = shift; |
|
59 |
|
|
60 |
my %special_chars = ( |
|
61 |
'ä' => 'ae', |
|
62 |
'ö' => 'oe', |
|
63 |
'ü' => 'ue', |
|
64 |
'Ä' => 'Ae', |
|
65 |
'Ö' => 'Oe', |
|
66 |
'Ü' => 'Ue', |
|
67 |
'ß' => 'ss', |
|
68 |
'&' => '+', |
|
69 |
); |
|
70 |
|
|
71 |
map { $text =~ s/$_/$special_chars{$_}/g; } keys %special_chars; |
|
72 |
|
|
73 |
return $text; |
|
74 |
} |
|
75 |
|
|
76 |
sub _format_amount { |
|
77 |
my $self = shift; |
|
78 |
my $amount = shift; |
|
79 |
|
|
80 |
return sprintf '%d.%02d', int($amount), int($amount * 100) % 100; |
|
81 |
} |
|
82 |
|
|
83 |
sub _group_transactions { |
|
84 |
my $self = shift; |
|
85 |
|
|
86 |
my $grouped = { |
|
87 |
'sum_amount' => 0, |
|
88 |
'groups' => { }, |
|
89 |
}; |
|
90 |
|
|
91 |
foreach my $transaction (@{ $self->{transactions} }) { |
|
92 |
my $key = $self->{grouped} ? join("\t", map { $transaction->get($_) } qw(src_bic src_iban execution_date)) : 'all'; |
|
93 |
$grouped->{groups}->{$key} ||= { |
|
94 |
'sum_amount' => 0, |
|
95 |
'transactions' => [ ], |
|
96 |
}; |
|
97 |
|
|
98 |
push @{ $grouped->{groups}->{$key}->{transactions} }, $transaction; |
|
99 |
|
|
100 |
$grouped->{groups}->{$key}->{sum_amount} += $transaction->{amount}; |
|
101 |
$grouped->{sum_amount} += $transaction->{amount}; |
|
102 |
} |
|
103 |
|
|
104 |
return $grouped; |
|
105 |
} |
|
106 |
|
|
107 |
sub to_xml { |
|
108 |
my $self = shift; |
|
109 |
|
|
110 |
croak "No transactions added yet." if (!@{ $self->{transactions} }); |
|
111 |
|
|
112 |
my $output = ''; |
|
113 |
|
|
114 |
my $xml = XML::Writer->new(OUTPUT => \$output, |
|
115 |
DATA_MODE => 1, |
|
116 |
DATA_INDENT => 2, |
|
117 |
ENCODING => 'utf-8'); |
|
118 |
|
|
119 |
my @now = localtime; |
|
120 |
my $time_zone = strftime "%z", @now; |
|
121 |
my $now_str = strftime('%Y-%m-%dT%H:%M:%S', @now) . substr($time_zone, 0, 3) . ':' . substr($time_zone, 3, 2); |
|
122 |
|
|
123 |
my $grouped_transactions = $self->_group_transactions(); |
|
124 |
|
|
125 |
$xml->xmlDecl(); |
|
126 |
$xml->startTag('Document', |
|
127 |
'xmlns' => 'urn:sepade:xsd:pain.001.001.02.grp', |
|
128 |
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', |
|
129 |
'xsi:schemaLocation' => 'urn:sepade:xsd:pain.001.001.02.grp pain.001.001.02.grp.xsd'); |
|
130 |
|
|
131 |
$xml->startTag('pain.001.001.02'); |
|
132 |
|
|
133 |
$xml->startTag('GrpHdr'); |
|
134 |
$xml->dataElement('MsgId', encode('UTF-8', substr($self->{message_id}, 0, 35))); |
|
135 |
$xml->dataElement('CreDtTm', $now_str); |
|
136 |
$xml->dataElement('NbOfTxs', scalar @{ $self->{transactions} }); |
|
137 |
$xml->dataElement('CtrlSum', $self->_format_amount($grouped_transactions->{sum_amount})); |
|
138 |
$xml->dataElement('Grpg', 'MIXD'); |
|
139 |
|
|
140 |
$xml->startTag('InitgPty'); |
|
141 |
$xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70))); |
|
142 |
$xml->endTag('InitgPty'); |
|
143 |
|
|
144 |
$xml->endTag('GrpHdr'); |
|
145 |
|
|
146 |
foreach my $key (keys %{ $grouped_transactions->{groups} }) { |
|
147 |
my $transaction_group = $grouped_transactions->{groups}->{$key}; |
|
148 |
my $master_transaction = $transaction_group->{transactions}->[0]; |
|
149 |
|
|
150 |
$xml->startTag('PmtInf'); |
|
151 |
$xml->dataElement('PmtMtd', 'TRF'); |
|
152 |
|
|
153 |
$xml->startTag('PmtTpInf'); |
|
154 |
$xml->startTag('SvcLvl'); |
|
155 |
$xml->dataElement('Cd', 'SEPA'); |
|
156 |
$xml->endTag('SvcLvl'); |
|
157 |
$xml->endTag('PmtTpInf'); |
|
158 |
|
|
159 |
$xml->dataElement('ReqdExctnDt', $master_transaction->get('execution_date')); |
|
160 |
$xml->startTag('Dbtr'); |
|
161 |
$xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70))); |
|
162 |
$xml->endTag('Dbtr'); |
|
163 |
|
|
164 |
$xml->startTag('DbtrAcct'); |
|
165 |
$xml->startTag('Id'); |
|
166 |
$xml->dataElement('IBAN', $master_transaction->get('src_iban', 34)); |
|
167 |
$xml->endTag('Id'); |
|
168 |
$xml->endTag('DbtrAcct'); |
|
169 |
|
|
170 |
$xml->startTag('DbtrAgt'); |
|
171 |
$xml->startTag('FinInstnId'); |
|
172 |
$xml->dataElement('BIC', $master_transaction->get('src_bic', 20)); |
|
173 |
$xml->endTag('FinInstnId'); |
|
174 |
$xml->endTag('DbtrAgt'); |
|
175 |
|
|
176 |
$xml->dataElement('ChrgBr', 'SLEV'); |
|
177 |
|
|
178 |
foreach my $transaction (@{ $transaction_group->{transactions} }) { |
|
179 |
$xml->startTag('CdtTrfTxInf'); |
|
180 |
|
|
181 |
$xml->startTag('PmtId'); |
|
182 |
$xml->dataElement('EndToEndId', $transaction->get('end_to_end_id', 35)); |
|
183 |
$xml->endTag('PmtId'); |
|
184 |
|
|
185 |
$xml->startTag('Amt'); |
|
186 |
$xml->startTag('InstdAmt', 'Ccy' => 'EUR'); |
|
187 |
$xml->characters($self->_format_amount($transaction->{amount})); |
|
188 |
$xml->endTag('InstdAmt'); |
|
189 |
$xml->endTag('Amt'); |
|
190 |
|
|
191 |
$xml->startTag('CdtrAgt'); |
|
192 |
$xml->startTag('FinInstnId'); |
|
193 |
$xml->dataElement('BIC', $transaction->get('dst_bic', 20)); |
|
194 |
$xml->endTag('FinInstnId'); |
|
195 |
$xml->endTag('CdtrAgt'); |
|
196 |
|
|
197 |
$xml->startTag('Cdtr'); |
|
198 |
$xml->dataElement('Nm', $transaction->get('recipient', 70)); |
|
199 |
$xml->endTag('Cdtr'); |
|
200 |
|
|
201 |
$xml->startTag('CdtrAcct'); |
|
202 |
$xml->startTag('Id'); |
|
203 |
$xml->dataElement('IBAN', $transaction->get('dst_iban', 34)); |
|
204 |
$xml->endTag('Id'); |
|
205 |
$xml->endTag('CdtrAcct'); |
|
206 |
|
|
207 |
$xml->startTag('RmtInf'); |
|
208 |
$xml->dataElement('Ustrd', $transaction->get('reference', 140)); |
|
209 |
$xml->endTag('RmtInf'); |
|
210 |
|
|
211 |
$xml->endTag('CdtTrfTxInf'); |
|
212 |
} |
|
213 |
|
|
214 |
$xml->endTag('PmtInf'); |
|
215 |
} |
|
216 |
|
|
217 |
$xml->endTag('pain.001.001.02'); |
|
218 |
$xml->endTag('Document'); |
|
219 |
|
|
220 |
return $output; |
|
221 |
} |
|
222 |
|
|
223 |
1; |
|
224 |
|
|
225 |
# Local Variables: |
|
226 |
# coding: utf-8 |
|
227 |
# End: |
SL/SEPA/XML/Transaction.pm | ||
---|---|---|
1 |
package SL::SEPA::XML::Transaction; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use Carp; |
|
6 |
use Encode; |
|
7 |
use List::Util qw(first); |
|
8 |
use POSIX qw(strftime); |
|
9 |
use Text::Iconv; |
|
10 |
|
|
11 |
sub new { |
|
12 |
my $class = shift; |
|
13 |
my $self = {}; |
|
14 |
|
|
15 |
bless $self, $class; |
|
16 |
|
|
17 |
$self->_init(@_); |
|
18 |
|
|
19 |
return $self; |
|
20 |
} |
|
21 |
|
|
22 |
sub _init { |
|
23 |
my $self = shift; |
|
24 |
my %params = @_; |
|
25 |
|
|
26 |
$self->{sepa} = $params{sepa}; |
|
27 |
delete $params{sepa}; |
|
28 |
|
|
29 |
my $missing_parameter = first { !$params{$_} } qw(src_iban src_bic dst_iban dst_bic recipient reference amount end_to_end_id); |
|
30 |
croak "Missing parameter: $missing_parameter" if ($missing_parameter); |
|
31 |
|
|
32 |
$params{end_to_end_id} ||= 'NOTPROVIDED'; |
|
33 |
$params{execution_date} ||= strftime "%Y-%m-%d", localtime; |
|
34 |
|
|
35 |
croak "Execution date format wrong for '$params{execution_date}': not YYYY-MM-DD." if ($params{execution_date} !~ /^\d{4}-\d{2}-\d{2}$/); |
|
36 |
|
|
37 |
map { $self->{$_} = decode('UTF-8', $self->{sepa}->{iconv}->convert($params{$_})) } keys %params; |
|
38 |
map { $self->{$_} =~ s/\s+//g } qw(src_iban src_bic dst_iban dst_bic); |
|
39 |
map { $self->{$_} = $self->{sepa}->_replace_special_chars($self->{$_}) } qw(recipient reference end_to_end_id); |
|
40 |
} |
|
41 |
|
|
42 |
sub get { |
|
43 |
my $self = shift; |
|
44 |
my $key = shift; |
|
45 |
my $max_len = shift; |
|
46 |
|
|
47 |
return undef if (!defined $self->{$key}); |
|
48 |
|
|
49 |
my $str = $max_len ? substr($self->{$key}, 0, $max_len) : $self->{$key}; |
|
50 |
|
|
51 |
return encode('UTF-8', $str); |
|
52 |
} |
|
53 |
|
|
54 |
1; |
bankaccounts.pl | ||
---|---|---|
1 |
am.pl |
bin/mozilla/bankaccounts.pl | ||
---|---|---|
1 |
use strict; |
|
2 |
|
|
3 |
use POSIX qw(strftime); |
|
4 |
|
|
5 |
use SL::BankAccount; |
|
6 |
use SL::Chart; |
|
7 |
use SL::Form; |
|
8 |
use SL::ReportGenerator; |
|
9 |
|
|
10 |
require "bin/mozilla/common.pl"; |
|
11 |
require "bin/mozilla/reportgenerator.pl"; |
|
12 |
|
|
13 |
sub bank_account_add { |
|
14 |
$main::lxdebug->enter_sub(); |
|
15 |
|
|
16 |
bank_account_display_form('account' => {}); |
|
17 |
|
|
18 |
$main::lxdebug->leave_sub(); |
|
19 |
} |
|
20 |
|
|
21 |
sub bank_account_edit { |
|
22 |
$main::lxdebug->enter_sub(); |
|
23 |
|
|
24 |
my %params = @_; |
|
25 |
my $form = $main::form; |
|
26 |
|
|
27 |
my $account = SL::BankAccount->retrieve('id' => $params{id} || $form->{id}); |
|
28 |
|
|
29 |
bank_account_display_form('account' => $account); |
|
30 |
|
|
31 |
$main::lxdebug->leave_sub(); |
|
32 |
} |
|
33 |
|
|
34 |
sub bank_account_display_form { |
|
35 |
$main::lxdebug->enter_sub(); |
|
36 |
|
|
37 |
my %params = @_; |
|
38 |
my $account = $params{account} || {}; |
|
39 |
my $form = $main::form; |
|
40 |
my $locale = $main::locale; |
|
41 |
|
|
42 |
my $charts = SL::Chart->list('link' => 'AP_paid'); |
|
43 |
my $label_sub = sub { join '--', map { $_[0]->{$_} } qw(accno description) }; |
|
44 |
|
|
45 |
$form->{title} = $account->{id} ? $locale->text('Edit bank account') : $locale->text('Add bank account'); |
|
46 |
|
|
47 |
$form->header(); |
|
48 |
print $form->parse_html_template('bankaccounts/bank_account_display_form', |
|
49 |
{ 'CHARTS' => $charts, |
|
50 |
'account' => $account, |
|
51 |
'chart_label' => $label_sub, |
|
52 |
'params' => \%params }); |
|
53 |
|
|
54 |
$main::lxdebug->leave_sub(); |
|
55 |
} |
|
56 |
|
|
57 |
sub bank_account_save { |
|
58 |
$main::lxdebug->enter_sub(); |
|
59 |
|
|
60 |
my $form = $main::form; |
|
61 |
my $locale = $main::locale; |
|
62 |
|
|
63 |
my $account = $form->{account} && (ref $form->{account} eq 'HASH') ? $form->{account} : { }; |
|
64 |
|
|
65 |
if (any { !$account->{$_} } qw(account_number bank_code iban bic)) { |
|
66 |
bank_account_display_form('account' => $account, |
|
67 |
'error' => $locale->text('You have to fill in at least an account number, the bank code, the IBAN and the BIC.')); |
|
68 |
|
|
69 |
$main::lxdebug->leave_sub(); |
|
70 |
return; |
|
71 |
} |
|
72 |
|
|
73 |
my $id = SL::BankAccount->save(%{ $account }); |
|
74 |
|
|
75 |
if ($form->{callback}) { |
|
76 |
$form->redirect(); |
|
77 |
|
|
78 |
} else { |
|
79 |
bank_account_edit('id' => $id); |
|
80 |
} |
|
81 |
|
|
82 |
$main::lxdebug->leave_sub(); |
|
83 |
} |
|
84 |
|
|
85 |
|
|
86 |
sub bank_account_list { |
|
87 |
$main::lxdebug->enter_sub(); |
|
88 |
|
|
89 |
my $form = $main::form; |
|
90 |
my $locale = $main::locale; |
|
91 |
|
|
92 |
$form->{title} = $locale->text('List of bank accounts'); |
|
93 |
|
|
94 |
$form->{sort} ||= 'account_number'; |
|
95 |
$form->{sortdir} = '1' if (!defined $form->{sortdir}); |
|
96 |
|
|
97 |
$form->{callback} = build_std_url('action=bank_account_list', 'sort', 'sortdir'); |
|
98 |
|
|
99 |
my $accounts = SL::BankAccount->list('sortorder' => $form->{sort}, |
|
100 |
'sortdir' => $form->{sortdir}); |
|
101 |
|
|
102 |
my $report = SL::ReportGenerator->new(\%main::myconfig, $form); |
|
103 |
|
|
104 |
my $href = build_std_url('action=bank_account_list'); |
|
105 |
|
|
106 |
my %column_defs = ( |
|
107 |
'account_number' => { 'text' => $locale->text('Account number'), }, |
|
108 |
'bank_code' => { 'text' => $locale->text('Bank code'), }, |
|
109 |
'bank' => { 'text' => $locale->text('Bank'), }, |
|
110 |
'bic' => { 'text' => $locale->text('BIC'), }, |
|
111 |
'iban' => { 'text' => $locale->text('IBAN'), }, |
|
112 |
); |
|
113 |
|
|
114 |
my @columns = qw(account_number bank bank_code bic iban); |
|
115 |
|
|
116 |
foreach my $name (@columns) { |
|
117 |
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir}; |
|
118 |
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir"; |
|
119 |
} |
|
120 |
|
|
121 |
$report->set_options('raw_bottom_info_text' => $form->parse_html_template('bankaccounts/bank_account_list_bottom'), |
|
122 |
'std_column_visibility' => 1, |
|
123 |
'output_format' => 'HTML', |
|
124 |
'title' => $form->{title}, |
|
125 |
'attachment_basename' => $locale->text('bankaccounts') . strftime('_%Y%m%d', localtime time), |
|
126 |
); |
|
127 |
$report->set_options_from_form(); |
|
128 |
|
|
129 |
$report->set_columns(%column_defs); |
|
130 |
$report->set_column_order(@columns); |
|
131 |
$report->set_export_options('bank_account_list'); |
|
132 |
$report->set_sort_indicator($form->{sort}, $form->{sortdir}); |
|
133 |
|
|
134 |
my $edit_url = build_std_url('action=bank_account_edit', 'callback'); |
|
135 |
|
|
136 |
foreach my $account (@{ $accounts }) { |
|
137 |
my $row = { map { $_ => { 'data' => $account->{$_} } } keys %{ $account } }; |
|
138 |
|
|
139 |
$row->{account_number}->{link} = $edit_url . '&id=' . E($account->{id}); |
|
140 |
|
|
141 |
$report->add_data($row); |
|
142 |
} |
|
143 |
|
|
144 |
$report->generate_with_headers(); |
|
145 |
|
|
146 |
$main::lxdebug->leave_sub(); |
|
147 |
} |
|
148 |
|
|
149 |
sub dispatcher { |
|
150 |
my $form = $main::form; |
|
151 |
|
|
152 |
foreach my $action (qw(bank_account_save bank_account_delete)) { |
|
153 |
if ($form->{"action_${action}"}) { |
|
154 |
call_sub($action); |
|
155 |
return; |
|
156 |
} |
|
157 |
} |
|
158 |
|
|
159 |
$form->error($main::locale->text('No action defined.')); |
|
160 |
} |
|
161 |
|
|
162 |
1; |
bin/mozilla/sepa.pl | ||
---|---|---|
1 |
use strict; |
|
2 |
|
|
3 |
use List::MoreUtils qw(any none uniq); |
|
4 |
use List::Util qw(first); |
|
5 |
use POSIX qw(strftime); |
|
6 |
|
|
7 |
use SL::BankAccount; |
|
8 |
use SL::Chart; |
|
9 |
use SL::CT; |
|
10 |
use SL::Form; |
|
11 |
use SL::ReportGenerator; |
|
12 |
use SL::SEPA; |
|
13 |
use SL::SEPA::XML; |
|
14 |
|
|
15 |
require "bin/mozilla/common.pl"; |
|
16 |
require "bin/mozilla/reportgenerator.pl"; |
|
17 |
|
|
18 |
sub bank_transfer_add { |
|
19 |
$main::lxdebug->enter_sub(); |
|
20 |
|
|
21 |
my $form = $main::form; |
|
22 |
my $locale = $main::locale; |
|
23 |
|
|
24 |
$form->{title} = $locale->text('Prepare bank transfer via SEPA XML'); |
|
25 |
|
|
26 |
my $bank_accounts = SL::BankAccount->list(); |
|
27 |
|
|
28 |
if (!scalar @{ $bank_accounts }) { |
|
29 |
$form->error($locale->text('You have not added bank accounts yet.')); |
|
30 |
} |
|
31 |
|
|
32 |
my $invoices = SL::SEPA->retrieve_open_invoices(); |
|
33 |
|
|
34 |
if (!scalar @{ $invoices }) { |
|
35 |
$form->show_generic_information($locale->text('Either there are no open invoices, or you have already initiated bank transfers ' . |
|
36 |
'with the open amounts for those that are still open.')); |
|
37 |
$main::lxdebug->leave_sub(); |
|
38 |
return; |
|
39 |
} |
|
40 |
|
|
41 |
my $bank_account_label_sub = sub { $locale->text('Account number #1, bank code #2, #3', $_[0]->{account_number}, $_[0]->{bank_code}, $_[0]->{bank}) }; |
|
42 |
|
|
43 |
$form->header(); |
|
44 |
print $form->parse_html_template('sepa/bank_transfer_add', |
|
45 |
{ 'INVOICES' => $invoices, |
|
46 |
'BANK_ACCOUNTS' => $bank_accounts, |
|
47 |
'bank_account_label' => $bank_account_label_sub, }); |
|
48 |
|
|
49 |
$main::lxdebug->leave_sub(); |
|
50 |
} |
|
51 |
|
|
52 |
sub bank_transfer_create { |
|
53 |
$main::lxdebug->enter_sub(); |
|
54 |
|
|
55 |
my $form = $main::form; |
|
56 |
my $locale = $main::locale; |
|
57 |
my $myconfig = \%main::myconfig; |
|
58 |
|
|
59 |
$form->{title} = $locale->text('Create bank transfer via SEPA XML'); |
|
60 |
|
|
61 |
my $bank_accounts = SL::BankAccount->list(); |
|
62 |
|
|
63 |
if (!scalar @{ $bank_accounts }) { |
|
64 |
$form->error($locale->text('You have not added bank accounts yet.')); |
|
65 |
} |
|
66 |
|
|
67 |
my $bank_account = first { $form->{bank_account}->{id} == $_->{id} } @{ $bank_accounts }; |
|
68 |
|
|
69 |
if (!$bank_account) { |
|
70 |
$form->error($locale->text('The selected bank account does not exist anymore.')); |
|
71 |
} |
|
72 |
|
|
73 |
my $invoices = SL::SEPA->retrieve_open_invoices(); |
|
74 |
my %invoices_map = map { $_->{id} => $_ } @{ $invoices }; |
|
75 |
my @bank_transfers = |
|
76 |
map +{ %{ $invoices_map{ $_->{ap_id} } }, %{ $_ } }, |
|
77 |
grep { $_->{selected} && (0 < $_->{amount}) && $invoices_map{ $_->{ap_id} } } |
|
78 |
map { $_->{amount} = $form->parse_amount($myconfig, $_->{amount}); $_ } |
|
79 |
@{ $form->{bank_transfers} || [] }; |
|
80 |
|
|
81 |
if (!scalar @bank_transfers) { |
|
82 |
$form->error($locale->text('You have selected none of the invoices.')); |
|
83 |
} |
|
84 |
|
|
85 |
my ($vendor_bank_info); |
|
86 |
my $error_message; |
|
87 |
|
|
88 |
if ($form->{confirmation}) { |
|
89 |
$vendor_bank_info = { map { $_->{id} => $_ } @{ $form->{vendor_bank_info} || [] } }; |
|
90 |
|
|
91 |
foreach my $info (values %{ $vendor_bank_info }) { |
|
92 |
if (any { !$info->{$_} } qw(iban bic)) { |
|
93 |
$error_message = $locale->text('The bank information must not be empty.'); |
|
94 |
last; |
|
95 |
} |
|
96 |
} |
|
97 |
} |
|
98 |
|
|
99 |
if ($error_message || !$form->{confirmation}) { |
|
100 |
my @vendor_ids = uniq map { $_->{vendor_id} } @bank_transfers; |
|
101 |
$vendor_bank_info ||= CT->get_bank_info('vc' => 'vendor', |
|
102 |
'id' => \@vendor_ids); |
|
103 |
my @vendor_bank_info = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vendor_bank_info }; |
|
104 |
|
|
105 |
my $bank_account_label_sub = sub { $locale->text('Account number #1, bank code #2, #3', $_[0]->{account_number}, $_[0]->{bank_code}, $_[0]->{bank}) }; |
|
106 |
|
|
107 |
$form->{jsscript} = 1; |
|
108 |
|
|
109 |
$form->header(); |
|
110 |
print $form->parse_html_template('sepa/bank_transfer_create', |
|
111 |
{ 'BANK_TRANSFERS' => \@bank_transfers, |
|
112 |
'BANK_ACCOUNTS' => $bank_accounts, |
|
113 |
'VENDOR_BANK_INFO' => \@vendor_bank_info, |
|
114 |
'bank_account' => $bank_account, |
|
115 |
'bank_account_label' => $bank_account_label_sub, |
|
116 |
'error_message' => $error_message, |
|
117 |
}); |
|
118 |
|
|
119 |
} else { |
|
120 |
foreach my $bank_transfer (@bank_transfers) { |
|
121 |
foreach (qw(iban bic)) { |
|
122 |
$bank_transfer->{"vendor_${_}"} = $vendor_bank_info->{ $bank_transfer->{vendor_id} }->{$_}; |
|
123 |
$bank_transfer->{"our_${_}"} = $bank_account->{$_}; |
|
124 |
} |
|
125 |
|
|
126 |
$bank_transfer->{chart_id} = $bank_account->{chart_id}; |
|
127 |
} |
|
128 |
|
|
129 |
my $id = SL::SEPA->create_export('employee' => $form->{login}, |
|
130 |
'bank_transfers' => \@bank_transfers); |
|
131 |
|
|
132 |
$form->header(); |
|
133 |
print $form->parse_html_template('sepa/bank_transfer_created', { 'id' => $id }); |
|
134 |
} |
|
135 |
|
|
136 |
$main::lxdebug->leave_sub(); |
|
137 |
} |
|
138 |
|
|
139 |
sub bank_transfer_search { |
|
140 |
$main::lxdebug->enter_sub(); |
|
141 |
|
|
142 |
my $form = $main::form; |
|
143 |
my $locale = $main::locale; |
|
144 |
|
|
145 |
$form->{title} = $locale->text('List of bank transfers'); |
|
146 |
$form->{jsscript} = 1; |
|
147 |
|
|
148 |
$form->header(); |
|
149 |
print $form->parse_html_template('sepa/bank_transfer_search'); |
|
150 |
|
|
151 |
$main::lxdebug->leave_sub(); |
|
152 |
} |
|
153 |
|
|
154 |
|
|
155 |
sub bank_transfer_list { |
|
156 |
$main::lxdebug->enter_sub(); |
|
157 |
|
|
158 |
my $form = $main::form; |
|
159 |
my $locale = $main::locale; |
|
160 |
my $cgi = $main::cgi; |
|
161 |
|
|
162 |
$form->{title} = $locale->text('List of bank transfers'); |
|
163 |
|
|
164 |
$form->{sort} ||= 'id'; |
|
165 |
$form->{sortdir} = '1' if (!defined $form->{sortdir}); |
|
166 |
|
|
167 |
$form->{callback} = build_std_url('action=bank_transfer_list', 'sort', 'sortdir'); |
|
168 |
|
|
169 |
my %filter = map +( $_ => $form->{"f_${_}"} ), |
|
170 |
grep { $form->{"f_${_}"} } |
|
171 |
(qw(vendor invnumber), |
|
172 |
map { ("${_}_date_from", "${_}_date_to") } |
|
173 |
qw(export requested_execution execution)); |
|
174 |
$filter{executed} = $form->{l_executed} ? 1 : 0 if ($form->{l_executed} != $form->{l_not_executed}); |
|
175 |
$filter{closed} = $form->{l_closed} ? 1 : 0 if ($form->{l_open} != $form->{l_closed}); |
|
176 |
|
|
177 |
my $exports = SL::SEPA->list_exports('filter' => \%filter, |
|
178 |
'sortorder' => $form->{sort}, |
|
179 |
'sortdir' => $form->{sortdir}); |
|
180 |
|
|
181 |
my $open_available = any { !$_->{closed} } @{ $exports }; |
|
182 |
|
|
183 |
my $report = SL::ReportGenerator->new(\%main::myconfig, $form); |
|
184 |
|
|
185 |
my @hidden_vars = grep { m/^[fl]_/ && $form->{$_} } keys %{ $form }; |
|
186 |
|
|
187 |
my $href = build_std_url('action=bank_transfer_list', @hidden_vars); |
|
188 |
|
|
189 |
my %column_defs = ( |
|
190 |
'selected' => { 'text' => $cgi->checkbox(-name => 'select_all', -id => 'select_all', -label => ''), }, |
|
191 |
'id' => { 'text' => $locale->text('Number'), }, |
|
192 |
'export_date' => { 'text' => $locale->text('Export date'), }, |
|
193 |
'employee' => { 'text' => $locale->text('Employee'), }, |
|
194 |
'executed' => { 'text' => $locale->text('Executed'), }, |
|
195 |
'closed' => { 'text' => $locale->text('Closed'), }, |
|
196 |
); |
|
197 |
|
|
198 |
my @columns = qw(selected id export_date employee executed closed); |
|
199 |
|
|
200 |
foreach my $name (qw(id export_date employee executed closed)) { |
|
201 |
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir}; |
|
202 |
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir"; |
|
203 |
} |
|
204 |
|
|
205 |
$column_defs{selected}->{visible} = $open_available ? 1 : 0; |
|
206 |
$column_defs{executed}->{visible} = $form->{l_executed} && $form->{l_not_executed} ? 1 : 0; |
|
207 |
$column_defs{closed}->{visible} = $form->{l_closed} && $form->{l_open} ? 1 : 0; |
|
208 |
|
|
209 |
my @options = (); |
|
210 |
push @options, $locale->text('Vendor') . ' : ' . $form->{f_vendor} if ($form->{f_vendor}); |
|
211 |
push @options, $locale->text('Invoice number') . ' : ' . $form->{f_invnumber} if ($form->{f_invnumber}); |
|
212 |
push @options, $locale->text('Export date from') . ' : ' . $form->{f_export_date_from} if ($form->{f_export_date_from}); |
|
213 |
push @options, $locale->text('Export date to') . ' : ' . $form->{f_export_date_to} if ($form->{f_export_date_to}); |
|
214 |
push @options, $locale->text('Requested execution date from') . ' : ' . $form->{f_requested_execution_date_from} if ($form->{f_requested_execution_date_from}); |
|
215 |
push @options, $locale->text('Requested execution date to') . ' : ' . $form->{f_requested_execution_date_to} if ($form->{f_requested_execution_date_to}); |
|
216 |
push @options, $locale->text('Execution date from') . ' : ' . $form->{f_execution_date_from} if ($form->{f_execution_date_from}); |
|
217 |
push @options, $locale->text('Execution date to') . ' : ' . $form->{f_execution_date_to} if ($form->{f_execution_date_to}); |
|
218 |
push @options, $form->{l_executed} ? $locale->text('executed') : $locale->text('not yet executed') if ($form->{l_executed} != $form->{l_not_executed}); |
|
219 |
push @options, $form->{l_closed} ? $locale->text('closed') : $locale->text('open') if ($form->{l_open} != $form->{l_closed}); |
|
220 |
|
|
221 |
$report->set_options('top_info_text' => join("\n", @options), |
|
222 |
'raw_top_info_text' => $form->parse_html_template('sepa/bank_transfer_list_top'), |
|
223 |
'raw_bottom_info_text' => $form->parse_html_template('sepa/bank_transfer_list_bottom', { 'show_buttons' => $open_available }), |
|
224 |
'std_column_visibility' => 1, |
|
225 |
'output_format' => 'HTML', |
|
226 |
'title' => $form->{title}, |
|
227 |
'attachment_basename' => $locale->text('banktransfers') . strftime('_%Y%m%d', localtime time), |
|
228 |
); |
|
229 |
$report->set_options_from_form(); |
|
230 |
|
|
231 |
$report->set_columns(%column_defs); |
|
232 |
$report->set_column_order(@columns); |
|
233 |
$report->set_export_options('bank_transfer_list', @hidden_vars); |
|
234 |
$report->set_sort_indicator($form->{sort}, $form->{sortdir}); |
|
235 |
|
|
236 |
my $edit_url = build_std_url('action=bank_transfer_edit', 'callback'); |
|
237 |
|
|
238 |
foreach my $export (@{ $exports }) { |
|
239 |
my $row = { map { $_ => { 'data' => $export->{$_} } } keys %{ $export } }; |
|
240 |
|
|
241 |
map { $row->{$_}->{data} = $export->{$_} ? $locale->text('yes') : $locale->text('no') } qw(executed closed); |
|
242 |
|
|
243 |
$row->{id}->{link} = $edit_url . '&id=' . E($export->{id}); |
|
244 |
|
|
245 |
if (!$export->{closed}) { |
|
246 |
$row->{selected}->{raw_data} = |
|
247 |
$cgi->hidden(-name => "exports[+].id", -value => $export->{id}) |
|
248 |
. $cgi->checkbox(-name => "exports[].selected", -value => 1, -label => ''); |
|
249 |
} |
|
250 |
|
|
251 |
$report->add_data($row); |
|
252 |
} |
|
253 |
|
|
254 |
$report->generate_with_headers(); |
|
255 |
|
|
256 |
$main::lxdebug->leave_sub(); |
|
257 |
} |
|
258 |
|
|
259 |
sub bank_transfer_edit { |
|
260 |
$main::lxdebug->enter_sub(); |
|
261 |
|
|
262 |
my $form = $main::form; |
|
263 |
my $locale = $main::locale; |
|
264 |
|
|
265 |
my @ids = (); |
|
266 |
if (!$form->{mode} || ($form->{mode} eq 'single')) { |
|
267 |
push @ids, $form->{id}; |
|
268 |
} else { |
|
269 |
@ids = map $_->{id}, grep { $_->{selected} } @{ $form->{exports} || [] }; |
|
270 |
|
|
271 |
if (!@ids) { |
|
272 |
$form->show_generic_error($locale->text('You have not selected any export.'), 'back_button' => 1); |
|
273 |
} |
|
274 |
} |
|
275 |
|
|
276 |
my $export; |
|
277 |
|
|
278 |
foreach my $id (@ids) { |
|
279 |
my $curr_export = SL::SEPA->retrieve_export('id' => $id, 'details' => 1); |
|
280 |
|
|
281 |
foreach my $item (@{ $curr_export->{items} }) { |
|
282 |
map { $item->{"export_${_}"} = $curr_export->{$_} } grep { !ref $curr_export->{$_} } keys %{ $curr_export }; |
|
283 |
} |
|
284 |
|
|
285 |
if (!$export) { |
|
286 |
$export = $curr_export; |
|
287 |
} else { |
|
288 |
push @{ $export->{items} }, @{ $curr_export->{items} }; |
|
289 |
} |
|
290 |
} |
|
291 |
|
|
292 |
if ($form->{mode} && ($form->{mode} eq 'multi')) { |
|
293 |
$export->{items} = [ grep { !$_->{export_closed} && !$_->{executed} } @{ $export->{items} } ]; |
|
294 |
|
|
295 |
if (!@{ $export->{items} }) { |
|
296 |
$form->show_generic_error($locale->text('All the selected exports have already been closed, or all of their items have already been executed.'), 'back_button' => 1); |
|
297 |
} |
|
298 |
|
|
299 |
} elsif (!$export) { |
|
300 |
$form->error($locale->text('That export does not exist.')); |
|
301 |
} |
|
302 |
|
|
303 |
$form->{jsscript} = 1; |
|
304 |
$form->{title} = $locale->text('View SEPA export'); |
|
305 |
$form->header(); |
|
306 |
print $form->parse_html_template('sepa/bank_transfer_edit', |
|
307 |
{ 'ids' => \@ids, |
|
308 |
'export' => $export, |
|
309 |
'current_date' => $form->current_date(\%main::myconfig), |
|
310 |
'show_post_payments_button' => any { !$_->{export_closed} && !$_->{executed} } @{ $export->{items} }, |
|
311 |
}); |
|
312 |
|
|
313 |
$main::lxdebug->leave_sub(); |
|
314 |
} |
|
315 |
|
|
316 |
sub bank_transfer_post_payments { |
|
317 |
$main::lxdebug->enter_sub(); |
|
318 |
|
|
319 |
my $form = $main::form; |
|
320 |
my $locale = $main::locale; |
|
321 |
|
|
322 |
my @items = grep { $_->{selected} } @{ $form->{items} || [] }; |
|
323 |
|
|
324 |
if (!@items) { |
|
325 |
$form->show_generic_error($locale->text('You have not selected any item.'), 'back_button' => 1); |
|
326 |
} |
|
327 |
my @export_ids = uniq map { $_->{sepa_export_id} } @items; |
|
328 |
my %exports = map { $_ => SL::SEPA->retrieve_export('id' => $_, 'details' => 1) } @export_ids; |
|
329 |
my @items_to_post = (); |
|
330 |
|
|
331 |
foreach my $item (@items) { |
|
332 |
my $export = $exports{ $item->{sepa_export_id} }; |
|
333 |
next if (!$export || $export->{closed} || $export->{executed}); |
|
334 |
|
|
335 |
push @items_to_post, $item if (none { ($_->{id} == $item->{id}) && $_->{executed} } @{ $export->{items} }); |
|
336 |
} |
|
337 |
|
|
338 |
if (!@items_to_post) { |
|
339 |
$form->show_generic_error($locale->text('All the selected exports have already been closed, or all of their items have already been executed.'), 'back_button' => 1); |
|
340 |
} |
|
341 |
|
|
342 |
if (any { !$_->{execution_date} } @items_to_post) { |
|
343 |
$form->show_generic_error($locale->text('You have to specify an execution date for each antry.'), 'back_button' => 1); |
|
344 |
} |
|
345 |
|
|
346 |
SL::SEPA->post_payment('items' => \@items_to_post); |
|
347 |
|
|
348 |
$form->show_generic_information($locale->text('The payments have been posted.')); |
|
349 |
|
|
350 |
$main::lxdebug->leave_sub(); |
|
351 |
} |
|
352 |
|
|
353 |
sub bank_transfer_payment_list_as_pdf { |
|
354 |
$main::lxdebug->enter_sub(); |
|
355 |
|
|
356 |
my $form = $main::form; |
|
357 |
my %myconfig = %main::myconfig; |
|
358 |
my $locale = $main::locale; |
|
359 |
|
|
360 |
my @ids = @{ $form->{items} || [] }; |
|
361 |
my @export_ids = uniq map { $_->{export_id} } @ids; |
|
362 |
|
|
363 |
$form->show_generic_error($locale->text('Multi mode not supported.'), 'back_button' => 1) if 1 != scalar @export_ids; |
|
364 |
|
|
365 |
my $export = SL::SEPA->retrieve_export('id' => $export_ids[0], 'details' => 1); |
|
366 |
my @items = (); |
|
367 |
|
|
368 |
foreach my $id (@ids) { |
|
369 |
my $item = first { $_->{id} == $id->{id} } @{ $export->{items} }; |
|
370 |
push @items, $item if $item; |
|
371 |
} |
|
372 |
|
|
373 |
$form->show_generic_error($locale->text('No transfers were executed in this export.'), 'back_button' => 1) if 1 > scalar @items; |
|
374 |
|
|
375 |
my $report = SL::ReportGenerator->new(\%main::myconfig, $form); |
|
376 |
|
|
377 |
my %column_defs = ( |
|
378 |
'invnumber' => { 'text' => $locale->text('Invoice'), }, |
|
379 |
'vendor_name' => { 'text' => $locale->text('Vendor'), }, |
|
380 |
'our_iban' => { 'text' => $locale->text('Source IBAN'), }, |
|
381 |
'our_bic' => { 'text' => $locale->text('Source BIC'), }, |
|
382 |
'vendor_iban' => { 'text' => $locale->text('Destination IBAN'), }, |
|
383 |
'vendor_bic' => { 'text' => $locale->text('Destination BIC'), }, |
|
384 |
'amount' => { 'text' => $locale->text('Amount'), }, |
|
385 |
'reference' => { 'text' => $locale->text('Reference'), }, |
|
386 |
'execution_date' => { 'text' => $locale->text('Execution date'), }, |
|
387 |
); |
|
388 |
|
|
389 |
map { $column_defs{$_}->{align} = 'right' } qw(amount execution_date); |
|
390 |
|
|
391 |
my @columns = qw(invnumber vendor_name our_iban our_bic vendor_iban vendor_bic amount reference execution_date); |
|
392 |
|
|
393 |
$report->set_options('std_column_visibility' => 1, |
|
394 |
'output_format' => 'PDF', |
|
395 |
'title' => $locale->text('Bank transfer payment list for export #1', $export->{id}), |
|
396 |
'attachment_basename' => $locale->text('bank_transfer_payment_list_#1', $export->{id}) . strftime('_%Y%m%d', localtime time), |
|
397 |
); |
|
398 |
|
|
399 |
$report->set_columns(%column_defs); |
|
400 |
$report->set_column_order(@columns); |
|
401 |
|
|
402 |
foreach my $item (@items) { |
|
403 |
my $row = { map { $_ => { 'data' => $item->{$_} } } @columns }; |
|
404 |
$row->{amount}->{data} = $form->format_amount(\%myconfig, $item->{amount}, 2); |
|
405 |
|
|
406 |
$report->add_data($row); |
|
407 |
} |
|
408 |
|
|
409 |
$report->generate_with_headers(); |
|
410 |
|
|
411 |
$main::lxdebug->leave_sub(); |
|
412 |
} |
|
413 |
|
|
414 |
sub bank_transfer_download_sepa_xml { |
|
415 |
$main::lxdebug->enter_sub(); |
|
416 |
|
|
417 |
my $form = $main::form; |
|
418 |
my $myconfig = \%main::myconfig; |
|
419 |
my $locale = $main::locale; |
|
420 |
my $cgi = $main::cgi; |
|
421 |
|
|
422 |
if (!$myconfig->{company}) { |
|
423 |
$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); |
|
424 |
} |
|
425 |
|
|
426 |
my @ids; |
|
427 |
if ($form->{mode} && ($form->{mode} eq 'multi')) { |
|
428 |
@ids = map $_->{id}, grep { $_->{selected} } @{ $form->{exports} || [] }; |
|
429 |
|
|
430 |
} else { |
|
431 |
@ids = ($form->{id}); |
|
432 |
} |
|
433 |
|
|
434 |
if (!@ids) { |
|
435 |
$form->show_generic_error($locale->text('You have not selected any export.'), 'back_button' => 1); |
|
436 |
} |
|
437 |
|
|
438 |
my @items = (); |
|
439 |
|
|
440 |
foreach my $id (@ids) { |
|
441 |
my $export = SL::SEPA->retrieve_export('id' => $id, 'details' => 1); |
|
442 |
push @items, grep { !$_->{executed} } @{ $export->{items} } if ($export && !$export->{closed}); |
|
443 |
} |
|
444 |
|
|
445 |
if (!@items) { |
|
446 |
$form->show_generic_error($locale->text('All the selected exports have already been closed, or all of their items have already been executed.'), 'back_button' => 1); |
|
447 |
} |
|
448 |
|
|
449 |
my $message_id = strftime('MSG%Y%m%d%H%M%S', localtime) . sprintf('%06d', $$); |
|
450 |
|
|
451 |
my $sepa_xml = SL::SEPA::XML->new('company' => $myconfig->{company}, |
|
452 |
'src_charset' => $main::dbcharset || 'ISO-8859-15', |
|
453 |
'message_id' => $message_id, |
|
454 |
'grouped' => 1, |
|
455 |
); |
|
456 |
|
|
457 |
foreach my $item (@items) { |
|
458 |
my $requested_execution_date; |
|
459 |
if ($item->{requested_execution_date}) { |
|
460 |
my ($yy, $mm, $dd) = $locale->parse_date($myconfig, $item->{requested_execution_date}); |
|
461 |
$requested_execution_date = sprintf '%04d-%02d-%02d', $yy, $mm, $dd; |
|
462 |
} |
|
463 |
|
|
464 |
$sepa_xml->add_transaction({ 'src_iban' => $item->{our_iban}, |
|
465 |
'src_bic' => $item->{our_bic}, |
|
466 |
'dst_iban' => $item->{vendor_iban}, |
|
467 |
'dst_bic' => $item->{vendor_bic}, |
|
468 |
'recipient' => $item->{vendor_name}, |
|
469 |
'amount' => $item->{amount}, |
|
470 |
'reference' => $item->{reference}, |
|
471 |
'execution_date' => $requested_execution_date, |
|
472 |
'end_to_end_id' => $item->{end_to_end_id} }); |
|
473 |
} |
|
474 |
|
|
475 |
my $xml = $sepa_xml->to_xml(); |
|
476 |
|
|
477 |
print $cgi->header('-type' => 'application/octet-stream', |
|
478 |
'-content-disposition' => 'attachment; filename="SEPA_' . $message_id . '.cct"', |
|
479 |
'-content-length' => length $xml); |
|
480 |
print $xml; |
|
481 |
|
|
482 |
$main::lxdebug->leave_sub(); |
|
483 |
} |
|
484 |
|
|
485 |
sub bank_transfer_mark_as_closed_step1 { |
|
486 |
$main::lxdebug->enter_sub(); |
|
487 |
|
|
488 |
my $form = $main::form; |
|
489 |
my $locale = $main::locale; |
|
490 |
|
|
491 |
my @export_ids = map { $_->{id} } grep { $_->{selected} } @{ $form->{exports} || [] }; |
|
492 |
|
|
493 |
if (!@export_ids) { |
|
494 |
$form->show_generic_error($locale->text('You have not selected any export.'), 'back_button' => 1); |
|
495 |
} |
|
496 |
|
|
497 |
my @open_export_ids = (); |
|
498 |
foreach my $id (@export_ids) { |
|
499 |
my $export = SL::SEPA->retrieve_export('id' => $id); |
|
500 |
push @open_export_ids, $id if (!$export->{closed}); |
|
501 |
} |
|
502 |
|
|
503 |
if (!@open_export_ids) { |
|
504 |
$form->show_generic_error($locale->text('All of the exports you have selected were already closed.'), 'back_button' => 1); |
|
505 |
} |
|
506 |
|
|
507 |
$form->{title} = $locale->text('Close SEPA exports'); |
|
508 |
$form->header(); |
|
509 |
print $form->parse_html_template('sepa/bank_transfer_mark_as_closed_step1', { 'OPEN_EXPORT_IDS' => \@open_export_ids }); |
|
510 |
|
|
511 |
$main::lxdebug->leave_sub(); |
|
512 |
} |
|
513 |
|
|
514 |
sub bank_transfer_mark_as_closed_step2 { |
|
515 |
$main::lxdebug->enter_sub(); |
|
516 |
|
|
517 |
my $form = $main::form; |
|
518 |
my $locale = $main::locale; |
|
519 |
|
|
520 |
map { SL::SEPA->close_export('id' => $_); } @{ $form->{open_export_ids} || [] }; |
|
521 |
|
|
522 |
$form->{title} = $locale->text('Close SEPA exports'); |
|
523 |
$form->header(); |
|
524 |
$form->show_generic_information($locale->text('The selected exports have been closed.')); |
|
525 |
|
|
526 |
$main::lxdebug->leave_sub(); |
|
527 |
} |
|
528 |
|
|
529 |
sub dispatcher { |
|
530 |
my $form = $main::form; |
|
531 |
|
|
532 |
foreach my $action (qw(bank_transfer_create bank_transfer_edit bank_transfer_list |
|
533 |
bank_transfer_post_payments bank_transfer_download_sepa_xml |
|
534 |
bank_transfer_mark_as_closed_step1 bank_transfer_mark_as_closed_step2 |
|
535 |
bank_transfer_payment_list_as_pdf)) { |
|
536 |
if ($form->{"action_${action}"}) { |
|
537 |
call_sub($action); |
|
538 |
return; |
|
539 |
} |
|
540 |
} |
|
541 |
|
|
542 |
$form->error($main::locale->text('No action defined.')); |
|
543 |
} |
|
544 |
|
|
545 |
1; |
locale/de/all | ||
---|---|---|
93 | 93 |
'Account deleted!' => 'Konto gel?scht!', |
94 | 94 |
'Account for fees' => 'Konto für Gebühren', |
Auch abrufbar als: Unified diff
Änderungen für den SEPA-Export