Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 1c603341

Von Jan Büren vor mehr als 15 Jahren hinzugefügt

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

Änderungen für den SEPA-Export

Unterschiede anzeigen:

SL/Chart.pm
package SL::Chart;
use strict;
use SL::Form;
use SL::DBUtils;
sub list {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my @values = ();
my @where = ();
if ($params{link}) {
if ($params{link} =~ '%') {
push @where, "c.link LIKE ?";
push @values, $params{link};
} else {
push @where, "(c.link = ?) OR (c.link LIKE ?) OR (c.link LIKE ?) OR (c.link LIKE ?)";
push @values, $params{link}, '%:' . $params{link} . ':%', '%:' . $params{link}, $params{link} . ':%';
}
}
my $where = scalar @where ? 'WHERE ' . join(' AND ', map { "($_)" } @where) : '';
my $query =
qq|SELECT c.id, c.accno, c.description, c.link
FROM chart c
$where
ORDER BY c.accno|;
my $charts = selectall_hashref_query($form, $dbh, $query, @values);
$main::lxdebug->leave_sub();
return $charts;
}
1;
SL/SEPA.pm
package SL::SEPA;
use strict;
use POSIX qw(strftime);
use SL::DBUtils;
sub retrieve_open_invoices {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my $query =
qq|
SELECT ap.id, ap.invnumber, ap.vendor_id, ap.amount AS invoice_amount, ap.invoice,
v.name AS vendorname,
COALESCE(v.iban, '') <> '' AND COALESCE(v.bic, '') <> '' AS vendor_bank_info_ok,
ap.amount - ap.paid - COALESCE(open_transfers.amount, 0) AS open_amount
FROM ap
LEFT JOIN vendor v ON (ap.vendor_id = v.id)
LEFT JOIN (SELECT sei.ap_id, SUM(sei.amount) AS amount
FROM sepa_export_items sei
LEFT JOIN sepa_export se ON (sei.sepa_export_id = se.id)
WHERE NOT se.closed
GROUP BY sei.ap_id)
AS open_transfers ON (ap.id = open_transfers.ap_id)
WHERE ap.amount > (COALESCE(open_transfers.amount, 0) + ap.paid)
ORDER BY lower(v.name) ASC, lower(ap.invnumber) ASC
|;
my $results = selectall_hashref_query($form, $dbh, $query);
$main::lxdebug->leave_sub();
return $results;
}
sub create_export {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
Common::check_params(\%params, qw(employee bank_transfers));
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my ($export_id) = selectfirst_array_query($form, $dbh, qq|SELECT nextval('sepa_export_id_seq')|);
my $query =
qq|INSERT INTO sepa_export (id, employee_id)
VALUES (?, (SELECT id
FROM employee
WHERE login = ?))|;
do_query($form, $dbh, $query, $export_id, $params{employee});
my $q_item_id = qq|SELECT nextval('id')|;
my $h_item_id = prepare_query($form, $dbh, $q_item_id);
my $q_insert =
qq|INSERT INTO sepa_export_items (id, sepa_export_id, ap_id, chart_id,
amount, requested_execution_date, reference, end_to_end_id,
our_iban, our_bic, vendor_iban, vendor_bic)
VALUES (?, ?, ?, ?,
?, ?, ?, ?,
?, ?, ?, ?)|;
my $h_insert = prepare_query($form, $dbh, $q_insert);
my $q_reference =
qq|SELECT ap.invnumber,
(SELECT COUNT(at.*)
FROM acc_trans at
LEFT JOIN chart c ON (at.chart_id = c.id)
WHERE (at.trans_id = ?)
AND (c.link LIKE '%AP_paid%'))
+
(SELECT COUNT(sei.*)
FROM sepa_export_items sei
WHERE (sei.ap_id = ?))
AS num_payments
FROM ap
WHERE id = ?|;
my $h_reference = prepare_query($form, $dbh, $q_reference);
my @now = localtime;
foreach my $transfer (@{ $params{bank_transfers} }) {
if (!$transfer->{reference}) {
do_statement($form, $h_reference, $q_reference, (conv_i($transfer->{ap_id})) x 3);
my ($invnumber, $num_payments) = $h_reference->fetchrow_array();
$num_payments++;
$transfer->{reference} = "${invnumber}-${num_payments}";
}
$h_item_id->execute();
my ($item_id) = $h_item_id->fetchrow_array();
my $end_to_end_id = strftime "LXO%Y%m%d%H%M%S", localtime;
my $item_id_len = length "$item_id";
my $num_zeroes = 35 - $item_id_len - length $end_to_end_id;
$end_to_end_id .= '0' x $num_zeroes if (0 < $num_zeroes);
$end_to_end_id .= $item_id;
$end_to_end_id = substr $end_to_end_id, 0, 35;
my @values = ($item_id, $export_id,
conv_i($transfer->{ap_id}), conv_i($transfer->{chart_id}),
$transfer->{amount}, conv_date($transfer->{requested_execution_date}),
$transfer->{reference}, $end_to_end_id,
map { my $pfx = $_; map { $transfer->{"${pfx}_${_}"} } qw(iban bic) } qw(our vendor));
do_statement($form, $h_insert, $q_insert, @values);
}
$h_insert->finish();
$h_item_id->finish();
$dbh->commit() unless ($params{dbh});
$main::lxdebug->leave_sub();
return $export_id;
}
sub retrieve_export {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
Common::check_params(\%params, qw(id));
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my ($joins, $columns);
if ($params{details}) {
$columns = ', ap.invoice';
$joins = 'LEFT JOIN ap ON (se.ap_id = ap.id)';
}
my $query =
qq|SELECT se.*,
CASE WHEN COALESCE(e.name, '') <> '' THEN e.name ELSE e.login END AS employee
FROM sepa_export se
LEFT JOIN employee e ON (se.employee_id = e.id)
WHERE se.id = ?|;
my $export = selectfirst_hashref_query($form, $dbh, $query, conv_i($params{id}));
if ($export->{id}) {
my ($columns, $joins);
if ($params{details}) {
$columns = qq|, ap.invnumber, ap.invoice, v.name AS vendor_name, c.accno AS chart_accno, c.description AS chart_description|;
$joins = qq|LEFT JOIN ap ON (sei.ap_id = ap.id)
LEFT JOIN vendor v ON (ap.vendor_id = v.id)
LEFT JOIN chart c ON (sei.chart_id = c.id)|;
}
$query = qq|SELECT sei.*
$columns
FROM sepa_export_items sei
$joins
WHERE sei.sepa_export_id = ?|;
$export->{items} = selectall_hashref_query($form, $dbh, $query, conv_i($params{id}));
} else {
$export->{items} = [];
}
$main::lxdebug->leave_sub();
return $export;
}
sub close_export {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
Common::check_params(\%params, qw(id));
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my @ids = ref $params{id} eq 'ARRAY' ? @{ $params{id} } : ($params{id});
my $placeholders = join ', ', ('?') x scalar @ids;
my $query = qq|UPDATE sepa_export SET closed = TRUE WHERE id IN ($placeholders)|;
do_query($form, $dbh, $query, map { conv_i($_) } @ids);
$dbh->commit() unless ($params{dbh});
$main::lxdebug->leave_sub();
}
sub list_exports {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my %sort_columns = (
'id' => [ 'se.id', ],
'export_date' => [ 'se.itime', ],
'employee' => [ 'e.name', 'se.id', ],
'executed' => [ 'se.executed', 'se.id', ],
'closed' => [ 'se.closed', 'se.id', ],
);
my %sort_spec = create_sort_spec('defs' => \%sort_columns, 'default' => 'id', 'column' => $params{sortorder}, 'dir' => $params{sortdir});
my (@where, @values, @where_sub, @values_sub, %joins_sub);
my $filter = $params{filter} || { };
foreach (qw(executed closed)) {
push @where, $filter->{$_} ? "se.$_" : "NOT se.$_" if (exists $filter->{$_});
}
my %operators = ('from' => '>=',
'to' => '<=');
foreach my $dir (qw(from to)) {
next unless ($filter->{"export_date_${dir}"});
push @where, "se.itime $operators{$dir} ?::date";
push @values, $filter->{"export_date_${dir}"};
}
if ($filter->{invnumber}) {
push @where_sub, "ap.invnumber ILIKE ?";
push @values_sub, '%' . $filter->{invnumber} . '%';
$joins_sub{ap} = 1;
}
if ($filter->{vendor}) {
push @where_sub, "v.name ILIKE ?";
push @values_sub, '%' . $filter->{vendor} . '%';
$joins_sub{ap} = 1;
$joins_sub{vendor} = 1;
}
foreach my $type (qw(requested_execution execution)) {
foreach my $dir (qw(from to)) {
next unless ($filter->{"${type}_date_${dir}"});
push @where_sub, "(items.${type}_date IS NOT NULL) AND (items.${type}_date $operators{$dir} ?)";
push @values_sub, $filter->{"${type}_date_${_}"};
}
}
if (@where_sub) {
my $joins_sub = '';
$joins_sub .= ' LEFT JOIN ap ON (items.ap_id = ap.id)' if ($joins_sub{ap});
$joins_sub .= ' LEFT JOIN vendor v ON (ap.vendor_id = v.id)' if ($joins_sub{vendor});
my $where_sub = join(' AND ', map { "(${_})" } @where_sub);
my $query_sub = qq|se.id IN (SELECT items.sepa_export_id
FROM sepa_export_items items
$joins_sub
WHERE $where_sub)|;
push @where, $query_sub;
push @values, @values_sub;
}
my $where = ' WHERE ' . join(' AND ', map { "(${_})" } @where) if (@where);
my $query =
qq|SELECT se.id, se.employee_id, se.executed, se.closed, itime::date AS export_date,
e.name AS employee
FROM sepa_export se
LEFT JOIN (
SELECT emp.id,
CASE WHEN COALESCE(emp.name, '') <> '' THEN emp.name ELSE emp.login END AS name
FROM employee emp
) AS e ON (se.employee_id = e.id)
$where
ORDER BY $sort_spec{sql}|;
my $results = selectall_hashref_query($form, $dbh, $query, @values);
$main::lxdebug->leave_sub();
return $results;
}
sub post_payment {
$main::lxdebug->enter_sub();
my $self = shift;
my %params = @_;
Common::check_params(\%params, qw(items));
my $myconfig = \%main::myconfig;
my $form = $main::form;
my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
my @items = ref $params{items} eq 'ARRAY' ? @{ $params{items} } : ($params{items});
my %handles = (
'get_item' => [ qq|SELECT sei.*
FROM sepa_export_items sei
WHERE sei.id = ?| ],
'get_ap' => [ qq|SELECT at.chart_id
FROM acc_trans at
LEFT JOIN chart c ON (at.chart_id = c.id)
WHERE (trans_id = ?)
AND ((c.link LIKE '%:AP') OR (c.link LIKE 'AP:%') OR (c.link = 'AP'))
LIMIT 1| ],
'add_acc_trans' => [ qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo)
VALUES (?, ?, ?, ?, current_date, ?, '')| ],
'update_ap' => [ qq|UPDATE ap
SET paid = paid + ?
WHERE id = ?| ],
'finish_item' => [ qq|UPDATE sepa_export_items
SET execution_date = ?, executed = TRUE
WHERE id = ?| ],
'has_unexecuted' => [ qq|SELECT sei1.id
FROM sepa_export_items sei1
WHERE (sei1.sepa_export_id = (SELECT sei2.sepa_export_id
FROM sepa_export_items sei2
WHERE sei2.id = ?))
AND NOT COALESCE(sei1.executed, FALSE)
LIMIT 1| ],
'do_close' => [ qq|UPDATE sepa_export
SET executed = TRUE, closed = TRUE
WHERE (id = ?)| ],
);
map { unshift @{ $_ }, prepare_query($form, $dbh, $_->[0]) } values %handles;
foreach my $item (@items) {
my $item_id = conv_i($item->{id});
# Retrieve the item data belonging to the ID.
do_statement($form, @{ $handles{get_item} }, $item_id);
my $orig_item = $handles{get_item}->[0]->fetchrow_hashref();
next if (!$orig_item);
# Retrieve the invoice's AP chart ID.
do_statement($form, @{ $handles{get_ap} }, $orig_item->{ap_id});
my ($ap_chart_id) = $handles{get_ap}->[0]->fetchrow_array();
# Record the payment in acc_trans offsetting AP.
do_statement($form, @{ $handles{add_acc_trans} }, $orig_item->{ap_id}, $ap_chart_id, -1 * $orig_item->{amount}, $item->{execution_date}, '');
do_statement($form, @{ $handles{add_acc_trans} }, $orig_item->{ap_id}, $orig_item->{chart_id}, $orig_item->{amount}, $item->{execution_date}, $orig_item->{reference});
# Update the invoice to reflect the new paid amount.
do_statement($form, @{ $handles{update_ap} }, $orig_item->{amount}, $orig_item->{ap_id});
# Update the item to reflect that it has been posted.
do_statement($form, @{ $handles{finish_item} }, $item->{execution_date}, $item_id);
# Check whether or not we can close the export itself if there are no unexecuted items left.
do_statement($form, @{ $handles{has_unexecuted} }, $item_id);
my ($has_unexecuted) = $handles{has_unexecuted}->[0]->fetchrow_array();
if (!$has_unexecuted) {
do_statement($form, @{ $handles{do_close} }, $orig_item->{sepa_export_id});
}
}
map { $_->[0]->finish() } values %handles;
$dbh->commit() unless ($params{dbh});
$main::lxdebug->leave_sub();
}
1;
SL/SEPA/XML.pm
package SL::SEPA::XML;
use strict;
use utf8;
use Carp;
use Encode;
use List::Util qw(first sum);
use List::MoreUtils qw(any);
use POSIX qw(strftime);
use Text::Iconv;
use XML::Writer;
use SL::SEPA::XML::Transaction;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
$self->_init(@_);
return $self;
}
sub _init {
my $self = shift;
my %params = @_;
$self->{transactions} = [];
$self->{src_charset} = 'UTF-8';
$self->{grouped} = 0;
map { $self->{$_} = $params{$_} if (exists $params{$_}) } qw(src_charset company message_id grouped);
$self->{iconv} = Text::Iconv->new($self->{src_charset}, "UTF-8") || croak "Unsupported source charset $self->{src_charset}.";
my $missing_parameter = first { !$self->{$_} } qw(company message_id);
croak "Missing parameter: $missing_parameter" if ($missing_parameter);
map { $self->{$_} = $self->_replace_special_chars(decode('UTF-8', $self->{iconv}->convert($self->{$_}))) } qw(company message_id);
}
sub add_transaction {
my $self = shift;
foreach my $transaction (@_) {
croak "Expecting hash reference." if (ref $transaction ne 'HASH');
push @{ $self->{transactions} }, SL::SEPA::XML::Transaction->new(%{ $transaction }, 'sepa' => $self);
}
return 1;
}
sub _replace_special_chars {
my $self = shift;
my $text = shift;
my %special_chars = (
'ä' => 'ae',
'ö' => 'oe',
'ü' => 'ue',
'Ä' => 'Ae',
'Ö' => 'Oe',
'Ü' => 'Ue',
'ß' => 'ss',
'&' => '+',
);
map { $text =~ s/$_/$special_chars{$_}/g; } keys %special_chars;
return $text;
}
sub _format_amount {
my $self = shift;
my $amount = shift;
return sprintf '%d.%02d', int($amount), int($amount * 100) % 100;
}
sub _group_transactions {
my $self = shift;
my $grouped = {
'sum_amount' => 0,
'groups' => { },
};
foreach my $transaction (@{ $self->{transactions} }) {
my $key = $self->{grouped} ? join("\t", map { $transaction->get($_) } qw(src_bic src_iban execution_date)) : 'all';
$grouped->{groups}->{$key} ||= {
'sum_amount' => 0,
'transactions' => [ ],
};
push @{ $grouped->{groups}->{$key}->{transactions} }, $transaction;
$grouped->{groups}->{$key}->{sum_amount} += $transaction->{amount};
$grouped->{sum_amount} += $transaction->{amount};
}
return $grouped;
}
sub to_xml {
my $self = shift;
croak "No transactions added yet." if (!@{ $self->{transactions} });
my $output = '';
my $xml = XML::Writer->new(OUTPUT => \$output,
DATA_MODE => 1,
DATA_INDENT => 2,
ENCODING => 'utf-8');
my @now = localtime;
my $time_zone = strftime "%z", @now;
my $now_str = strftime('%Y-%m-%dT%H:%M:%S', @now) . substr($time_zone, 0, 3) . ':' . substr($time_zone, 3, 2);
my $grouped_transactions = $self->_group_transactions();
$xml->xmlDecl();
$xml->startTag('Document',
'xmlns' => 'urn:sepade:xsd:pain.001.001.02.grp',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation' => 'urn:sepade:xsd:pain.001.001.02.grp pain.001.001.02.grp.xsd');
$xml->startTag('pain.001.001.02');
$xml->startTag('GrpHdr');
$xml->dataElement('MsgId', encode('UTF-8', substr($self->{message_id}, 0, 35)));
$xml->dataElement('CreDtTm', $now_str);
$xml->dataElement('NbOfTxs', scalar @{ $self->{transactions} });
$xml->dataElement('CtrlSum', $self->_format_amount($grouped_transactions->{sum_amount}));
$xml->dataElement('Grpg', 'MIXD');
$xml->startTag('InitgPty');
$xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
$xml->endTag('InitgPty');
$xml->endTag('GrpHdr');
foreach my $key (keys %{ $grouped_transactions->{groups} }) {
my $transaction_group = $grouped_transactions->{groups}->{$key};
my $master_transaction = $transaction_group->{transactions}->[0];
$xml->startTag('PmtInf');
$xml->dataElement('PmtMtd', 'TRF');
$xml->startTag('PmtTpInf');
$xml->startTag('SvcLvl');
$xml->dataElement('Cd', 'SEPA');
$xml->endTag('SvcLvl');
$xml->endTag('PmtTpInf');
$xml->dataElement('ReqdExctnDt', $master_transaction->get('execution_date'));
$xml->startTag('Dbtr');
$xml->dataElement('Nm', encode('UTF-8', substr($self->{company}, 0, 70)));
$xml->endTag('Dbtr');
$xml->startTag('DbtrAcct');
$xml->startTag('Id');
$xml->dataElement('IBAN', $master_transaction->get('src_iban', 34));
$xml->endTag('Id');
$xml->endTag('DbtrAcct');
$xml->startTag('DbtrAgt');
$xml->startTag('FinInstnId');
$xml->dataElement('BIC', $master_transaction->get('src_bic', 20));
$xml->endTag('FinInstnId');
$xml->endTag('DbtrAgt');
$xml->dataElement('ChrgBr', 'SLEV');
foreach my $transaction (@{ $transaction_group->{transactions} }) {
$xml->startTag('CdtTrfTxInf');
$xml->startTag('PmtId');
$xml->dataElement('EndToEndId', $transaction->get('end_to_end_id', 35));
$xml->endTag('PmtId');
$xml->startTag('Amt');
$xml->startTag('InstdAmt', 'Ccy' => 'EUR');
$xml->characters($self->_format_amount($transaction->{amount}));
$xml->endTag('InstdAmt');
$xml->endTag('Amt');
$xml->startTag('CdtrAgt');
$xml->startTag('FinInstnId');
$xml->dataElement('BIC', $transaction->get('dst_bic', 20));
$xml->endTag('FinInstnId');
$xml->endTag('CdtrAgt');
$xml->startTag('Cdtr');
$xml->dataElement('Nm', $transaction->get('recipient', 70));
$xml->endTag('Cdtr');
$xml->startTag('CdtrAcct');
$xml->startTag('Id');
$xml->dataElement('IBAN', $transaction->get('dst_iban', 34));
$xml->endTag('Id');
$xml->endTag('CdtrAcct');
$xml->startTag('RmtInf');
$xml->dataElement('Ustrd', $transaction->get('reference', 140));
$xml->endTag('RmtInf');
$xml->endTag('CdtTrfTxInf');
}
$xml->endTag('PmtInf');
}
$xml->endTag('pain.001.001.02');
$xml->endTag('Document');
return $output;
}
1;
# Local Variables:
# coding: utf-8
# End:
SL/SEPA/XML/Transaction.pm
package SL::SEPA::XML::Transaction;
use strict;
use Carp;
use Encode;
use List::Util qw(first);
use POSIX qw(strftime);
use Text::Iconv;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
$self->_init(@_);
return $self;
}
sub _init {
my $self = shift;
my %params = @_;
$self->{sepa} = $params{sepa};
delete $params{sepa};
my $missing_parameter = first { !$params{$_} } qw(src_iban src_bic dst_iban dst_bic recipient reference amount end_to_end_id);
croak "Missing parameter: $missing_parameter" if ($missing_parameter);
$params{end_to_end_id} ||= 'NOTPROVIDED';
$params{execution_date} ||= strftime "%Y-%m-%d", localtime;
croak "Execution date format wrong for '$params{execution_date}': not YYYY-MM-DD." if ($params{execution_date} !~ /^\d{4}-\d{2}-\d{2}$/);
map { $self->{$_} = decode('UTF-8', $self->{sepa}->{iconv}->convert($params{$_})) } keys %params;
map { $self->{$_} =~ s/\s+//g } qw(src_iban src_bic dst_iban dst_bic);
map { $self->{$_} = $self->{sepa}->_replace_special_chars($self->{$_}) } qw(recipient reference end_to_end_id);
}
sub get {
my $self = shift;
my $key = shift;
my $max_len = shift;
return undef if (!defined $self->{$key});
my $str = $max_len ? substr($self->{$key}, 0, $max_len) : $self->{$key};
return encode('UTF-8', $str);
}
1;
bankaccounts.pl
am.pl
bin/mozilla/bankaccounts.pl
use strict;
use POSIX qw(strftime);
use SL::BankAccount;
use SL::Chart;
use SL::Form;
use SL::ReportGenerator;
require "bin/mozilla/common.pl";
require "bin/mozilla/reportgenerator.pl";
sub bank_account_add {
$main::lxdebug->enter_sub();
bank_account_display_form('account' => {});
$main::lxdebug->leave_sub();
}
sub bank_account_edit {
$main::lxdebug->enter_sub();
my %params = @_;
my $form = $main::form;
my $account = SL::BankAccount->retrieve('id' => $params{id} || $form->{id});
bank_account_display_form('account' => $account);
$main::lxdebug->leave_sub();
}
sub bank_account_display_form {
$main::lxdebug->enter_sub();
my %params = @_;
my $account = $params{account} || {};
my $form = $main::form;
my $locale = $main::locale;
my $charts = SL::Chart->list('link' => 'AP_paid');
my $label_sub = sub { join '--', map { $_[0]->{$_} } qw(accno description) };
$form->{title} = $account->{id} ? $locale->text('Edit bank account') : $locale->text('Add bank account');
$form->header();
print $form->parse_html_template('bankaccounts/bank_account_display_form',
{ 'CHARTS' => $charts,
'account' => $account,
'chart_label' => $label_sub,
'params' => \%params });
$main::lxdebug->leave_sub();
}
sub bank_account_save {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
my $account = $form->{account} && (ref $form->{account} eq 'HASH') ? $form->{account} : { };
if (any { !$account->{$_} } qw(account_number bank_code iban bic)) {
bank_account_display_form('account' => $account,
'error' => $locale->text('You have to fill in at least an account number, the bank code, the IBAN and the BIC.'));
$main::lxdebug->leave_sub();
return;
}
my $id = SL::BankAccount->save(%{ $account });
if ($form->{callback}) {
$form->redirect();
} else {
bank_account_edit('id' => $id);
}
$main::lxdebug->leave_sub();
}
sub bank_account_list {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
$form->{title} = $locale->text('List of bank accounts');
$form->{sort} ||= 'account_number';
$form->{sortdir} = '1' if (!defined $form->{sortdir});
$form->{callback} = build_std_url('action=bank_account_list', 'sort', 'sortdir');
my $accounts = SL::BankAccount->list('sortorder' => $form->{sort},
'sortdir' => $form->{sortdir});
my $report = SL::ReportGenerator->new(\%main::myconfig, $form);
my $href = build_std_url('action=bank_account_list');
my %column_defs = (
'account_number' => { 'text' => $locale->text('Account number'), },
'bank_code' => { 'text' => $locale->text('Bank code'), },
'bank' => { 'text' => $locale->text('Bank'), },
'bic' => { 'text' => $locale->text('BIC'), },
'iban' => { 'text' => $locale->text('IBAN'), },
);
my @columns = qw(account_number bank bank_code bic iban);
foreach my $name (@columns) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
$report->set_options('raw_bottom_info_text' => $form->parse_html_template('bankaccounts/bank_account_list_bottom'),
'std_column_visibility' => 1,
'output_format' => 'HTML',
'title' => $form->{title},
'attachment_basename' => $locale->text('bankaccounts') . strftime('_%Y%m%d', localtime time),
);
$report->set_options_from_form();
$report->set_columns(%column_defs);
$report->set_column_order(@columns);
$report->set_export_options('bank_account_list');
$report->set_sort_indicator($form->{sort}, $form->{sortdir});
my $edit_url = build_std_url('action=bank_account_edit', 'callback');
foreach my $account (@{ $accounts }) {
my $row = { map { $_ => { 'data' => $account->{$_} } } keys %{ $account } };
$row->{account_number}->{link} = $edit_url . '&id=' . E($account->{id});
$report->add_data($row);
}
$report->generate_with_headers();
$main::lxdebug->leave_sub();
}
sub dispatcher {
my $form = $main::form;
foreach my $action (qw(bank_account_save bank_account_delete)) {
if ($form->{"action_${action}"}) {
call_sub($action);
return;
}
}
$form->error($main::locale->text('No action defined.'));
}
1;
bin/mozilla/sepa.pl
use strict;
use List::MoreUtils qw(any none uniq);
use List::Util qw(first);
use POSIX qw(strftime);
use SL::BankAccount;
use SL::Chart;
use SL::CT;
use SL::Form;
use SL::ReportGenerator;
use SL::SEPA;
use SL::SEPA::XML;
require "bin/mozilla/common.pl";
require "bin/mozilla/reportgenerator.pl";
sub bank_transfer_add {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
$form->{title} = $locale->text('Prepare bank transfer via SEPA XML');
my $bank_accounts = SL::BankAccount->list();
if (!scalar @{ $bank_accounts }) {
$form->error($locale->text('You have not added bank accounts yet.'));
}
my $invoices = SL::SEPA->retrieve_open_invoices();
if (!scalar @{ $invoices }) {
$form->show_generic_information($locale->text('Either there are no open invoices, or you have already initiated bank transfers ' .
'with the open amounts for those that are still open.'));
$main::lxdebug->leave_sub();
return;
}
my $bank_account_label_sub = sub { $locale->text('Account number #1, bank code #2, #3', $_[0]->{account_number}, $_[0]->{bank_code}, $_[0]->{bank}) };
$form->header();
print $form->parse_html_template('sepa/bank_transfer_add',
{ 'INVOICES' => $invoices,
'BANK_ACCOUNTS' => $bank_accounts,
'bank_account_label' => $bank_account_label_sub, });
$main::lxdebug->leave_sub();
}
sub bank_transfer_create {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
my $myconfig = \%main::myconfig;
$form->{title} = $locale->text('Create bank transfer via SEPA XML');
my $bank_accounts = SL::BankAccount->list();
if (!scalar @{ $bank_accounts }) {
$form->error($locale->text('You have not added bank accounts yet.'));
}
my $bank_account = first { $form->{bank_account}->{id} == $_->{id} } @{ $bank_accounts };
if (!$bank_account) {
$form->error($locale->text('The selected bank account does not exist anymore.'));
}
my $invoices = SL::SEPA->retrieve_open_invoices();
my %invoices_map = map { $_->{id} => $_ } @{ $invoices };
my @bank_transfers =
map +{ %{ $invoices_map{ $_->{ap_id} } }, %{ $_ } },
grep { $_->{selected} && (0 < $_->{amount}) && $invoices_map{ $_->{ap_id} } }
map { $_->{amount} = $form->parse_amount($myconfig, $_->{amount}); $_ }
@{ $form->{bank_transfers} || [] };
if (!scalar @bank_transfers) {
$form->error($locale->text('You have selected none of the invoices.'));
}
my ($vendor_bank_info);
my $error_message;
if ($form->{confirmation}) {
$vendor_bank_info = { map { $_->{id} => $_ } @{ $form->{vendor_bank_info} || [] } };
foreach my $info (values %{ $vendor_bank_info }) {
if (any { !$info->{$_} } qw(iban bic)) {
$error_message = $locale->text('The bank information must not be empty.');
last;
}
}
}
if ($error_message || !$form->{confirmation}) {
my @vendor_ids = uniq map { $_->{vendor_id} } @bank_transfers;
$vendor_bank_info ||= CT->get_bank_info('vc' => 'vendor',
'id' => \@vendor_ids);
my @vendor_bank_info = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vendor_bank_info };
my $bank_account_label_sub = sub { $locale->text('Account number #1, bank code #2, #3', $_[0]->{account_number}, $_[0]->{bank_code}, $_[0]->{bank}) };
$form->{jsscript} = 1;
$form->header();
print $form->parse_html_template('sepa/bank_transfer_create',
{ 'BANK_TRANSFERS' => \@bank_transfers,
'BANK_ACCOUNTS' => $bank_accounts,
'VENDOR_BANK_INFO' => \@vendor_bank_info,
'bank_account' => $bank_account,
'bank_account_label' => $bank_account_label_sub,
'error_message' => $error_message,
});
} else {
foreach my $bank_transfer (@bank_transfers) {
foreach (qw(iban bic)) {
$bank_transfer->{"vendor_${_}"} = $vendor_bank_info->{ $bank_transfer->{vendor_id} }->{$_};
$bank_transfer->{"our_${_}"} = $bank_account->{$_};
}
$bank_transfer->{chart_id} = $bank_account->{chart_id};
}
my $id = SL::SEPA->create_export('employee' => $form->{login},
'bank_transfers' => \@bank_transfers);
$form->header();
print $form->parse_html_template('sepa/bank_transfer_created', { 'id' => $id });
}
$main::lxdebug->leave_sub();
}
sub bank_transfer_search {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
$form->{title} = $locale->text('List of bank transfers');
$form->{jsscript} = 1;
$form->header();
print $form->parse_html_template('sepa/bank_transfer_search');
$main::lxdebug->leave_sub();
}
sub bank_transfer_list {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
my $cgi = $main::cgi;
$form->{title} = $locale->text('List of bank transfers');
$form->{sort} ||= 'id';
$form->{sortdir} = '1' if (!defined $form->{sortdir});
$form->{callback} = build_std_url('action=bank_transfer_list', 'sort', 'sortdir');
my %filter = map +( $_ => $form->{"f_${_}"} ),
grep { $form->{"f_${_}"} }
(qw(vendor invnumber),
map { ("${_}_date_from", "${_}_date_to") }
qw(export requested_execution execution));
$filter{executed} = $form->{l_executed} ? 1 : 0 if ($form->{l_executed} != $form->{l_not_executed});
$filter{closed} = $form->{l_closed} ? 1 : 0 if ($form->{l_open} != $form->{l_closed});
my $exports = SL::SEPA->list_exports('filter' => \%filter,
'sortorder' => $form->{sort},
'sortdir' => $form->{sortdir});
my $open_available = any { !$_->{closed} } @{ $exports };
my $report = SL::ReportGenerator->new(\%main::myconfig, $form);
my @hidden_vars = grep { m/^[fl]_/ && $form->{$_} } keys %{ $form };
my $href = build_std_url('action=bank_transfer_list', @hidden_vars);
my %column_defs = (
'selected' => { 'text' => $cgi->checkbox(-name => 'select_all', -id => 'select_all', -label => ''), },
'id' => { 'text' => $locale->text('Number'), },
'export_date' => { 'text' => $locale->text('Export date'), },
'employee' => { 'text' => $locale->text('Employee'), },
'executed' => { 'text' => $locale->text('Executed'), },
'closed' => { 'text' => $locale->text('Closed'), },
);
my @columns = qw(selected id export_date employee executed closed);
foreach my $name (qw(id export_date employee executed closed)) {
my $sortdir = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
$column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
}
$column_defs{selected}->{visible} = $open_available ? 1 : 0;
$column_defs{executed}->{visible} = $form->{l_executed} && $form->{l_not_executed} ? 1 : 0;
$column_defs{closed}->{visible} = $form->{l_closed} && $form->{l_open} ? 1 : 0;
my @options = ();
push @options, $locale->text('Vendor') . ' : ' . $form->{f_vendor} if ($form->{f_vendor});
push @options, $locale->text('Invoice number') . ' : ' . $form->{f_invnumber} if ($form->{f_invnumber});
push @options, $locale->text('Export date from') . ' : ' . $form->{f_export_date_from} if ($form->{f_export_date_from});
push @options, $locale->text('Export date to') . ' : ' . $form->{f_export_date_to} if ($form->{f_export_date_to});
push @options, $locale->text('Requested execution date from') . ' : ' . $form->{f_requested_execution_date_from} if ($form->{f_requested_execution_date_from});
push @options, $locale->text('Requested execution date to') . ' : ' . $form->{f_requested_execution_date_to} if ($form->{f_requested_execution_date_to});
push @options, $locale->text('Execution date from') . ' : ' . $form->{f_execution_date_from} if ($form->{f_execution_date_from});
push @options, $locale->text('Execution date to') . ' : ' . $form->{f_execution_date_to} if ($form->{f_execution_date_to});
push @options, $form->{l_executed} ? $locale->text('executed') : $locale->text('not yet executed') if ($form->{l_executed} != $form->{l_not_executed});
push @options, $form->{l_closed} ? $locale->text('closed') : $locale->text('open') if ($form->{l_open} != $form->{l_closed});
$report->set_options('top_info_text' => join("\n", @options),
'raw_top_info_text' => $form->parse_html_template('sepa/bank_transfer_list_top'),
'raw_bottom_info_text' => $form->parse_html_template('sepa/bank_transfer_list_bottom', { 'show_buttons' => $open_available }),
'std_column_visibility' => 1,
'output_format' => 'HTML',
'title' => $form->{title},
'attachment_basename' => $locale->text('banktransfers') . strftime('_%Y%m%d', localtime time),
);
$report->set_options_from_form();
$report->set_columns(%column_defs);
$report->set_column_order(@columns);
$report->set_export_options('bank_transfer_list', @hidden_vars);
$report->set_sort_indicator($form->{sort}, $form->{sortdir});
my $edit_url = build_std_url('action=bank_transfer_edit', 'callback');
foreach my $export (@{ $exports }) {
my $row = { map { $_ => { 'data' => $export->{$_} } } keys %{ $export } };
map { $row->{$_}->{data} = $export->{$_} ? $locale->text('yes') : $locale->text('no') } qw(executed closed);
$row->{id}->{link} = $edit_url . '&id=' . E($export->{id});
if (!$export->{closed}) {
$row->{selected}->{raw_data} =
$cgi->hidden(-name => "exports[+].id", -value => $export->{id})
. $cgi->checkbox(-name => "exports[].selected", -value => 1, -label => '');
}
$report->add_data($row);
}
$report->generate_with_headers();
$main::lxdebug->leave_sub();
}
sub bank_transfer_edit {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
my @ids = ();
if (!$form->{mode} || ($form->{mode} eq 'single')) {
push @ids, $form->{id};
} else {
@ids = map $_->{id}, grep { $_->{selected} } @{ $form->{exports} || [] };
if (!@ids) {
$form->show_generic_error($locale->text('You have not selected any export.'), 'back_button' => 1);
}
}
my $export;
foreach my $id (@ids) {
my $curr_export = SL::SEPA->retrieve_export('id' => $id, 'details' => 1);
foreach my $item (@{ $curr_export->{items} }) {
map { $item->{"export_${_}"} = $curr_export->{$_} } grep { !ref $curr_export->{$_} } keys %{ $curr_export };
}
if (!$export) {
$export = $curr_export;
} else {
push @{ $export->{items} }, @{ $curr_export->{items} };
}
}
if ($form->{mode} && ($form->{mode} eq 'multi')) {
$export->{items} = [ grep { !$_->{export_closed} && !$_->{executed} } @{ $export->{items} } ];
if (!@{ $export->{items} }) {
$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);
}
} elsif (!$export) {
$form->error($locale->text('That export does not exist.'));
}
$form->{jsscript} = 1;
$form->{title} = $locale->text('View SEPA export');
$form->header();
print $form->parse_html_template('sepa/bank_transfer_edit',
{ 'ids' => \@ids,
'export' => $export,
'current_date' => $form->current_date(\%main::myconfig),
'show_post_payments_button' => any { !$_->{export_closed} && !$_->{executed} } @{ $export->{items} },
});
$main::lxdebug->leave_sub();
}
sub bank_transfer_post_payments {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
my @items = grep { $_->{selected} } @{ $form->{items} || [] };
if (!@items) {
$form->show_generic_error($locale->text('You have not selected any item.'), 'back_button' => 1);
}
my @export_ids = uniq map { $_->{sepa_export_id} } @items;
my %exports = map { $_ => SL::SEPA->retrieve_export('id' => $_, 'details' => 1) } @export_ids;
my @items_to_post = ();
foreach my $item (@items) {
my $export = $exports{ $item->{sepa_export_id} };
next if (!$export || $export->{closed} || $export->{executed});
push @items_to_post, $item if (none { ($_->{id} == $item->{id}) && $_->{executed} } @{ $export->{items} });
}
if (!@items_to_post) {
$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);
}
if (any { !$_->{execution_date} } @items_to_post) {
$form->show_generic_error($locale->text('You have to specify an execution date for each antry.'), 'back_button' => 1);
}
SL::SEPA->post_payment('items' => \@items_to_post);
$form->show_generic_information($locale->text('The payments have been posted.'));
$main::lxdebug->leave_sub();
}
sub bank_transfer_payment_list_as_pdf {
$main::lxdebug->enter_sub();
my $form = $main::form;
my %myconfig = %main::myconfig;
my $locale = $main::locale;
my @ids = @{ $form->{items} || [] };
my @export_ids = uniq map { $_->{export_id} } @ids;
$form->show_generic_error($locale->text('Multi mode not supported.'), 'back_button' => 1) if 1 != scalar @export_ids;
my $export = SL::SEPA->retrieve_export('id' => $export_ids[0], 'details' => 1);
my @items = ();
foreach my $id (@ids) {
my $item = first { $_->{id} == $id->{id} } @{ $export->{items} };
push @items, $item if $item;
}
$form->show_generic_error($locale->text('No transfers were executed in this export.'), 'back_button' => 1) if 1 > scalar @items;
my $report = SL::ReportGenerator->new(\%main::myconfig, $form);
my %column_defs = (
'invnumber' => { 'text' => $locale->text('Invoice'), },
'vendor_name' => { 'text' => $locale->text('Vendor'), },
'our_iban' => { 'text' => $locale->text('Source IBAN'), },
'our_bic' => { 'text' => $locale->text('Source BIC'), },
'vendor_iban' => { 'text' => $locale->text('Destination IBAN'), },
'vendor_bic' => { 'text' => $locale->text('Destination BIC'), },
'amount' => { 'text' => $locale->text('Amount'), },
'reference' => { 'text' => $locale->text('Reference'), },
'execution_date' => { 'text' => $locale->text('Execution date'), },
);
map { $column_defs{$_}->{align} = 'right' } qw(amount execution_date);
my @columns = qw(invnumber vendor_name our_iban our_bic vendor_iban vendor_bic amount reference execution_date);
$report->set_options('std_column_visibility' => 1,
'output_format' => 'PDF',
'title' => $locale->text('Bank transfer payment list for export #1', $export->{id}),
'attachment_basename' => $locale->text('bank_transfer_payment_list_#1', $export->{id}) . strftime('_%Y%m%d', localtime time),
);
$report->set_columns(%column_defs);
$report->set_column_order(@columns);
foreach my $item (@items) {
my $row = { map { $_ => { 'data' => $item->{$_} } } @columns };
$row->{amount}->{data} = $form->format_amount(\%myconfig, $item->{amount}, 2);
$report->add_data($row);
}
$report->generate_with_headers();
$main::lxdebug->leave_sub();
}
sub bank_transfer_download_sepa_xml {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $myconfig = \%main::myconfig;
my $locale = $main::locale;
my $cgi = $main::cgi;
if (!$myconfig->{company}) {
$form->show_generic_error($locale->text('You have to enter a company name in your user preferences (see the "Program" menu, "Preferences").'), 'back_button' => 1);
}
my @ids;
if ($form->{mode} && ($form->{mode} eq 'multi')) {
@ids = map $_->{id}, grep { $_->{selected} } @{ $form->{exports} || [] };
} else {
@ids = ($form->{id});
}
if (!@ids) {
$form->show_generic_error($locale->text('You have not selected any export.'), 'back_button' => 1);
}
my @items = ();
foreach my $id (@ids) {
my $export = SL::SEPA->retrieve_export('id' => $id, 'details' => 1);
push @items, grep { !$_->{executed} } @{ $export->{items} } if ($export && !$export->{closed});
}
if (!@items) {
$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);
}
my $message_id = strftime('MSG%Y%m%d%H%M%S', localtime) . sprintf('%06d', $$);
my $sepa_xml = SL::SEPA::XML->new('company' => $myconfig->{company},
'src_charset' => $main::dbcharset || 'ISO-8859-15',
'message_id' => $message_id,
'grouped' => 1,
);
foreach my $item (@items) {
my $requested_execution_date;
if ($item->{requested_execution_date}) {
my ($yy, $mm, $dd) = $locale->parse_date($myconfig, $item->{requested_execution_date});
$requested_execution_date = sprintf '%04d-%02d-%02d', $yy, $mm, $dd;
}
$sepa_xml->add_transaction({ 'src_iban' => $item->{our_iban},
'src_bic' => $item->{our_bic},
'dst_iban' => $item->{vendor_iban},
'dst_bic' => $item->{vendor_bic},
'recipient' => $item->{vendor_name},
'amount' => $item->{amount},
'reference' => $item->{reference},
'execution_date' => $requested_execution_date,
'end_to_end_id' => $item->{end_to_end_id} });
}
my $xml = $sepa_xml->to_xml();
print $cgi->header('-type' => 'application/octet-stream',
'-content-disposition' => 'attachment; filename="SEPA_' . $message_id . '.cct"',
'-content-length' => length $xml);
print $xml;
$main::lxdebug->leave_sub();
}
sub bank_transfer_mark_as_closed_step1 {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
my @export_ids = map { $_->{id} } grep { $_->{selected} } @{ $form->{exports} || [] };
if (!@export_ids) {
$form->show_generic_error($locale->text('You have not selected any export.'), 'back_button' => 1);
}
my @open_export_ids = ();
foreach my $id (@export_ids) {
my $export = SL::SEPA->retrieve_export('id' => $id);
push @open_export_ids, $id if (!$export->{closed});
}
if (!@open_export_ids) {
$form->show_generic_error($locale->text('All of the exports you have selected were already closed.'), 'back_button' => 1);
}
$form->{title} = $locale->text('Close SEPA exports');
$form->header();
print $form->parse_html_template('sepa/bank_transfer_mark_as_closed_step1', { 'OPEN_EXPORT_IDS' => \@open_export_ids });
$main::lxdebug->leave_sub();
}
sub bank_transfer_mark_as_closed_step2 {
$main::lxdebug->enter_sub();
my $form = $main::form;
my $locale = $main::locale;
map { SL::SEPA->close_export('id' => $_); } @{ $form->{open_export_ids} || [] };
$form->{title} = $locale->text('Close SEPA exports');
$form->header();
$form->show_generic_information($locale->text('The selected exports have been closed.'));
$main::lxdebug->leave_sub();
}
sub dispatcher {
my $form = $main::form;
foreach my $action (qw(bank_transfer_create bank_transfer_edit bank_transfer_list
bank_transfer_post_payments bank_transfer_download_sepa_xml
bank_transfer_mark_as_closed_step1 bank_transfer_mark_as_closed_step2
bank_transfer_payment_list_as_pdf)) {
if ($form->{"action_${action}"}) {
call_sub($action);
return;
}
}
$form->error($main::locale->text('No action defined.'));
}
1;
locale/de/all
'Account deleted!' => 'Konto gel?scht!',
'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