Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 1c603341

Von Jan Büren vor etwa 15 Jahren hinzugefügt

  • ID 1c603341fc02e3a5a7b5126cd7df6478d2e34700
  • Vorgänger c97830b0
  • Nachfolger 94632453

Änderungen für den SEPA-Export

Unterschiede anzeigen:

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&uuml;r Geb&uuml;hren',
... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.

Auch abrufbar als: Unified diff