|
use strict;
|
|
|
|
use List::MoreUtils qw(any none uniq);
|
|
use List::Util qw(sum first);
|
|
use POSIX qw(strftime);
|
|
|
|
use SL::DB::BankAccount;
|
|
use SL::DB::SepaExport;
|
|
use SL::Chart;
|
|
use SL::CT;
|
|
use SL::Form;
|
|
use SL::GenericTranslations;
|
|
use SL::Locale::String qw(t8);
|
|
use SL::ReportGenerator;
|
|
use SL::SEPA;
|
|
use SL::SEPA::XML;
|
|
use SL::SEPA::SwissXML;
|
|
use SL::Helper::QrBillParser;
|
|
use SL::Helper::ISO3166;
|
|
|
|
use SL::Helper::QrBillFunctions qw(
|
|
get_street_name_from_address_line
|
|
get_building_number_from_address_line
|
|
);
|
|
|
|
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;
|
|
my $vc = $form->{vc} eq 'customer' ? 'customer' : 'vendor';
|
|
my $vc_no = $form->{vc} eq 'customer' ? $::locale->text('VN') : $::locale->text('CN');
|
|
|
|
my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export;
|
|
|
|
$form->{title} = $vc eq 'customer' ?
|
|
$::locale->text('Prepare bank collection via SEPA XML') :
|
|
$swiss_export ?
|
|
$locale->text('Prepare bank transfer via swiss XML') :
|
|
$locale->text('Prepare bank transfer via SEPA XML');
|
|
|
|
my $bank_accounts = SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] );
|
|
|
|
if (!scalar @{ $bank_accounts }) {
|
|
$form->error($locale->text('You have not added bank accounts yet.'));
|
|
}
|
|
|
|
my $invoices = SL::SEPA->retrieve_open_invoices(vc => $vc);
|
|
|
|
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;
|
|
}
|
|
|
|
# Only include those per default that require manual action from our
|
|
# side. For sales invoices these are the ones for which direct debit
|
|
# has been selected. For purchase invoices it's the other way
|
|
# around: if direct debit is active then the vendor will collect
|
|
# from us automatically and we don't have to send money manually.
|
|
$_->{checked} = ($vc eq 'customer' ? $_->{direct_debit} : !$_->{direct_debit}) for @{ $invoices };
|
|
|
|
my $translation_list = GenericTranslations->list(translation_type => 'sepa_remittance_info_pfx');
|
|
my %translations = map { ( ($_->{language_id} || 'default') => $_->{translation} ) } @{ $translation_list };
|
|
|
|
foreach my $invoice (@{ $invoices }) {
|
|
my $prefix = $translations{ $invoice->{language_id} } || $translations{default} || $::locale->text('Invoice');
|
|
$prefix .= ' ' unless $prefix =~ m/ $/;
|
|
$invoice->{reference_prefix} = $prefix;
|
|
|
|
# add c_vendor_id or v_vendor_id as a prefix if a entry exists
|
|
next unless $invoice->{vc_vc_id};
|
|
|
|
my $prefix_vc_number = $translations{ $invoice->{language_id} } || $translations{default} || $vc_no;
|
|
$prefix_vc_number .= ' ' unless $prefix_vc_number =~ m/ $/;
|
|
$invoice->{reference_prefix_vc} = ' ' . $prefix_vc_number unless $prefix_vc_number =~ m/^ /;
|
|
}
|
|
|
|
# for swiss export override database check because of different cases
|
|
if ($swiss_export) {
|
|
foreach my $invoice (@{ $invoices }) {
|
|
# determine invoice type
|
|
if ($invoice->{qrbill_data}) {
|
|
$invoice->{type} = 'QRBILL';
|
|
|
|
# vendor iban comes from qrbill data
|
|
# no further checks needed
|
|
$invoice->{vc_bank_info_ok} = 1;
|
|
|
|
} elsif ($invoice->{vc_iban} =~ m/^(CH|LI)/) {
|
|
$invoice->{type} = 'DOMESTIC';
|
|
|
|
# vendor iban is needed
|
|
$invoice->{vc_bank_info_ok} = $invoice->{vc_iban} ? 1 : 0;
|
|
|
|
} else {
|
|
$invoice->{type} = 'SEPA';
|
|
|
|
# vendor iban and bic are needed
|
|
$invoice->{vc_bank_info_ok} = $invoice->{vc_iban} && $invoice->{vc_bic} ? 1 : 0
|
|
}
|
|
}
|
|
}
|
|
|
|
setup_sepa_add_transfer_action_bar();
|
|
|
|
$form->header();
|
|
print $form->parse_html_template('sepa/bank_transfer_add',
|
|
{ 'INVOICES' => $invoices,
|
|
'BANK_ACCOUNTS' => $bank_accounts,
|
|
'vc' => $vc,
|
|
});
|
|
|
|
$main::lxdebug->leave_sub();
|
|
}
|
|
|
|
sub bank_transfer_create {
|
|
$main::lxdebug->enter_sub();
|
|
|
|
my $form = $main::form;
|
|
my $locale = $main::locale;
|
|
my $myconfig = \%main::myconfig;
|
|
my $vc = $form->{vc} eq 'customer' ? 'customer' : 'vendor';
|
|
|
|
my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export;
|
|
|
|
$form->{title} = $vc eq 'customer' ?
|
|
$::locale->text('Create bank collection via SEPA XML') :
|
|
$swiss_export ?
|
|
$locale->text('Create bank transfer via swiss XML') :
|
|
$locale->text('Create bank transfer via SEPA XML');
|
|
|
|
my $bank_accounts = SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] );
|
|
if (!scalar @{ $bank_accounts }) {
|
|
$form->error($locale->text('You have not added bank accounts yet.'));
|
|
}
|
|
|
|
my $bank_account = SL::DB::Manager::BankAccount->find_by( id => $form->{bank_account} );
|
|
|
|
unless ( $bank_account ) {
|
|
$form->error($locale->text('The selected bank account does not exist anymore.'));
|
|
}
|
|
|
|
my $arap_id = $vc eq 'customer' ? 'ar_id' : 'ap_id';
|
|
my $invoices = SL::SEPA->retrieve_open_invoices(vc => $vc);
|
|
|
|
# Load all open invoices (again), but grep out the ones that were selected with checkboxes beforehand ($_->selected).
|
|
# At this stage we again have all the invoice information, including dropdown with payment_type options.
|
|
# All the information from retrieve_open_invoices is then ADDED to what was passed via @{ $form->{bank_transfers} }.
|
|
# Parse amount from the entry in the form, but take skonto_amount from PT again.
|
|
# The map inserts the values of invoice_map directly into the array of hashes.
|
|
my %selected_ids = map { ($_ => 1) } @{ $form->{ids} || [] };
|
|
my %invoices_map = map { $_->{id} => $_ } @{ $invoices };
|
|
my @bank_transfers =
|
|
map +{ %{ $invoices_map{ $_->{$arap_id} } }, %{ $_ } },
|
|
grep { ($_->{selected} || $selected_ids{$_->{$arap_id}}) && (0 < $_->{amount}) && $invoices_map{ $_->{$arap_id} } && !($invoices_map{ $_->{$arap_id} }->{is_sepa_blocked}) }
|
|
map { $_->{amount} = $form->parse_amount($myconfig, $_->{amount}); $_ }
|
|
@{ $form->{bank_transfers} || [] };
|
|
|
|
# override default payment_type selection and set it to the one chosen by the user
|
|
# in the previous step, so that we don't need the logic in the template
|
|
my $subtract_days = $::instance_conf->get_sepa_set_skonto_date_buffer_in_days;
|
|
my $set_skonto_date = $::instance_conf->get_sepa_set_skonto_date_as_default_exec_date;
|
|
my $set_duedate = $::instance_conf->get_sepa_set_duedate_as_default_exec_date;
|
|
foreach my $bt (@bank_transfers) {
|
|
# add a good recommended exec date
|
|
# set to skonto date if exists or to duedate
|
|
# in both cases subtract the same buffer (if configured, default 0)
|
|
$bt->{recommended_execution_date} =
|
|
$set_skonto_date && $bt->{payment_type} eq 'with_skonto_pt' ?
|
|
DateTime->from_kivitendo($bt->{skonto_date})->subtract(days => $subtract_days)->to_kivitendo
|
|
: $set_duedate && $bt->{duedate} ?
|
|
DateTime->from_kivitendo($bt->{duedate} )->subtract(days => $subtract_days)->to_kivitendo
|
|
: undef;
|
|
|
|
|
|
foreach my $type ( @{$bt->{payment_select_options}} ) {
|
|
if ( $type->{payment_type} eq $bt->{payment_type} ) {
|
|
$type->{selected} = 1;
|
|
} else {
|
|
$type->{selected} = 0;
|
|
};
|
|
};
|
|
};
|
|
|
|
if (!scalar @bank_transfers) {
|
|
$form->error($locale->text('You have selected none of the invoices.'));
|
|
}
|
|
|
|
my $total_trans = sum map { $_->{open_amount} } @bank_transfers;
|
|
|
|
my ($vc_bank_info);
|
|
my $error_message;
|
|
my @bank_columns = qw(iban bic);
|
|
|
|
# separate validation for swiss export
|
|
if (!$swiss_export) {
|
|
push @bank_columns, qw(mandator_id mandate_date_of_signature) if $vc eq 'customer';
|
|
|
|
if ($form->{confirmation}) {
|
|
$vc_bank_info = { map { $_->{id} => $_ } @{ $form->{vc_bank_info} || [] } };
|
|
|
|
foreach my $info (values %{ $vc_bank_info }) {
|
|
if (any { !$info->{$_} } @bank_columns) {
|
|
$error_message = $locale->text('The bank information must not be empty.');
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
($error_message, $vc_bank_info) = validate_vendors_swiss_export(\@bank_transfers);
|
|
}
|
|
|
|
if ($error_message || !$form->{confirmation}) {
|
|
if (!$swiss_export) {
|
|
my @vc_ids = uniq map { $_->{vc_id} } @bank_transfers;
|
|
|
|
$vc_bank_info ||= CT->get_bank_info('vc' => $vc, 'id' => \@vc_ids);
|
|
}
|
|
|
|
my @vc_bank_info = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vc_bank_info };
|
|
|
|
setup_sepa_create_transfer_action_bar(is_vendor => $vc eq 'vendor');
|
|
|
|
$form->header();
|
|
print $form->parse_html_template('sepa/bank_transfer_create',
|
|
{ 'BANK_TRANSFERS' => \@bank_transfers,
|
|
'BANK_ACCOUNTS' => $bank_accounts,
|
|
'VC_BANK_INFO' => \@vc_bank_info,
|
|
'bank_account' => $bank_account,
|
|
'error_message' => $error_message,
|
|
'vc' => $vc,
|
|
'total_trans' => $total_trans,
|
|
});
|
|
|
|
} else {
|
|
foreach my $bank_transfer (@bank_transfers) {
|
|
foreach (@bank_columns) {
|
|
$bank_transfer->{"vc_${_}"} = $vc_bank_info->{ $bank_transfer->{vc_id} }->{$_};
|
|
$bank_transfer->{"our_${_}"} = $bank_account->{$_};
|
|
}
|
|
|
|
$bank_transfer->{chart_id} = $bank_account->{chart_id};
|
|
}
|
|
|
|
my $id = SL::SEPA->create_export('employee' => $::myconfig{login},
|
|
'bank_transfers' => \@bank_transfers,
|
|
'vc' => $vc);
|
|
|
|
$form->header();
|
|
print $form->parse_html_template('sepa/bank_transfer_created',
|
|
{
|
|
'id' => $id,
|
|
'vc' => $vc,
|
|
});
|
|
}
|
|
|
|
$main::lxdebug->leave_sub();
|
|
}
|
|
|
|
sub validate_vendors_swiss_export {
|
|
my ($bank_transfers) = @_;
|
|
|
|
my $form = $main::form;
|
|
my $locale = $main::locale;
|
|
my $myconfig = \%main::myconfig;
|
|
|
|
# determine unique vendor types
|
|
my %unique_vendor_types;
|
|
for my $bt (@$bank_transfers) {
|
|
my $uid = "$bt->{vc_id}_$bt->{type}";
|
|
|
|
# if qr-bill get iban from qr-bill data to display in vendor account information,
|
|
# important for manual verification
|
|
my $qr_iban;
|
|
if ($bt->{type} eq 'QRBILL') {
|
|
my $qr_bill_data = SL::Helper::QrBillParser->new($bt->{qrbill_data});
|
|
$qr_iban = $qr_bill_data->{creditor_information}->{iban};
|
|
}
|
|
|
|
$unique_vendor_types{$uid} = {
|
|
vc_id => $bt->{vc_id},
|
|
type => $bt->{type},
|
|
qr_iban => $qr_iban,
|
|
} unless defined $unique_vendor_types{$uid};
|
|
}
|
|
|
|
# get bank info for unique vendor types
|
|
my $vendors = $form->{vc_bank_info} ?
|
|
{ map { $_->{id} => $_ } @{ $form->{vc_bank_info} } } :
|
|
|
|
push @options, ($vc eq 'customer' ? $::locale->text('Customer') : $locale->text('Vendor')) . ' : ' . $form->{f_vc} if ($form->{f_vc});
|
|
push @options, $locale->text('Invoice number') . ' : ' . $form->{f_invnumber} if ($form->{f_invnumber});
|
|
push @options, $locale->text('SEPA message ID') . ' : ' . $form->{f_message_id} if (length $form->{f_message_id});
|
|
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-> |