Revision 789de0c0
Von Moritz Bunkus vor mehr als 4 Jahren hinzugefügt
SL/Controller/BankImport.pm | ||
---|---|---|
package SL::Controller::BankImport;
|
||
|
||
use strict;
|
||
use Data::Dumper;
|
||
|
||
use parent qw(SL::Controller::Base);
|
||
|
||
use SL::Locale::String qw(t8);
|
||
use SL::DB::CsvImportProfile;
|
||
use SL::Helper::MT940;
|
||
use List::MoreUtils qw(apply);
|
||
use List::Util qw(max min);
|
||
|
||
use SL::DB::BankAccount;
|
||
use SL::DB::BankTransaction;
|
||
use SL::DB::Default;
|
||
use SL::Helper::Flash;
|
||
use SL::MT940;
|
||
use SL::SessionFile::Random;
|
||
|
||
use Rose::Object::MakeMethods::Generic
|
||
(
|
||
'scalar --get_set_init' => [ qw(profile) ],
|
||
scalar => [ qw(file_name transactions statistics) ],
|
||
'scalar --get_set_init' => [ qw(bank_accounts) ],
|
||
);
|
||
|
||
__PACKAGE__->run_before('check_auth');
|
||
... | ... | |
my ($self, %params) = @_;
|
||
|
||
$self->setup_upload_mt940_action_bar;
|
||
$self->render('bankimport/form', title => $::locale->text('MT940 import'), profile => $self->profile ? 1 : 0);
|
||
$self->render('bank_import/upload_mt940', title => $::locale->text('MT940 import'));
|
||
}
|
||
|
||
sub action_import_mt940 {
|
||
sub action_import_mt940_preview {
|
||
my ($self, %params) = @_;
|
||
|
||
die "missing file for action import" unless $::form->{file};
|
||
if (!$::form->{file}) {
|
||
flash_later('error', $::locale->text('You have to upload an MT940 file to import.'));
|
||
return $self->redirect_to(action => 'upload_mt940');
|
||
}
|
||
|
||
my $converted_data = SL::Helper::MT940::convert_mt940_data($::form->{file});
|
||
die "missing file for action import_mt940_preview" unless $::form->{file};
|
||
|
||
# store the converted data in a session file and create a temporary profile with it's name
|
||
my $file = SL::SessionFile::Random->new(mode => '>');
|
||
$file->fh->print($converted_data);
|
||
$file->fh->print($::form->{file});
|
||
$file->fh->close;
|
||
$self->profile->set('file_name', $file->file_name);
|
||
$self->profile($self->profile->clone_and_reset_deep)->save;
|
||
|
||
die t8("The MT940 import needs an import profile called MT940") unless $self->profile;
|
||
$self->file_name($file->file_name);
|
||
$self->parse_and_analyze_transactions;
|
||
|
||
$self->setup_upload_mt940_preview_action_bar;
|
||
$self->render('bank_import/import_mt940', title => $::locale->text('MT940 import preview'), preview => 1);
|
||
}
|
||
|
||
sub action_import_mt940 {
|
||
my ($self, %params) = @_;
|
||
|
||
die "missing file for action import_mt940" unless $::form->{file_name};
|
||
|
||
$self->file_name($::form->{file_name});
|
||
$self->parse_and_analyze_transactions;
|
||
$self->import_transactions;
|
||
|
||
$self->render('bank_import/import_mt940', title => $::locale->text('MT940 import result'));
|
||
}
|
||
|
||
sub parse_and_analyze_transactions {
|
||
my ($self, %params) = @_;
|
||
|
||
my $errors = 0;
|
||
my $duplicates = 0;
|
||
my ($min_date, $max_date);
|
||
|
||
$self->redirect_to(controller => 'controller.pl', action => 'CsvImport/test', 'profile.type' => 'bank_transactions', 'profile.id' => $self->profile->id, force_profile => 1);
|
||
my $currency_id = SL::DB::Default->get->currency_id;
|
||
|
||
$self->transactions([ sort { $a->{transdate} cmp $b->{transdate} } SL::MT940->parse($self->file_name) ]);
|
||
|
||
foreach my $transaction (@{ $self->transactions }) {
|
||
$transaction->{bank_account} = $self->bank_accounts->{ make_bank_account_idx($transaction->{local_bank_code}, $transaction->{local_account_number}) };
|
||
$transaction->{bank_account} //= $self->bank_accounts->{ make_bank_account_idx('IBAN', $transaction->{local_account_number}) };
|
||
|
||
if (!$transaction->{bank_account}) {
|
||
$transaction->{error} = $::locale->text('No bank account configured for bank code/BIC #1, account number/IBAN #2.', $transaction->{local_bank_code}, $transaction->{local_account_number});
|
||
$errors++;
|
||
next;
|
||
}
|
||
|
||
$transaction->{local_bank_account_id} = $transaction->{bank_account}->id;
|
||
$transaction->{currency_id} = $currency_id;
|
||
|
||
$min_date = min($min_date // $transaction->{transdate}, $transaction->{transdate});
|
||
$max_date = max($max_date // $transaction->{transdate}, $transaction->{transdate});
|
||
}
|
||
|
||
my %existing_bank_transactions;
|
||
|
||
if ((scalar(@{ $self->transactions }) - $errors) > 0) {
|
||
my @entries =
|
||
@{ SL::DB::Manager::BankTransaction->get_all(
|
||
where => [
|
||
transdate => { ge => $min_date },
|
||
transdate => { lt => $max_date->clone->add(days => 1) },
|
||
],
|
||
inject_results => 1) };
|
||
|
||
%existing_bank_transactions = map { (make_transaction_idx($_) => 1) } @entries;
|
||
}
|
||
|
||
foreach my $transaction (@{ $self->transactions }) {
|
||
next if $transaction->{error};
|
||
|
||
if ($existing_bank_transactions{make_transaction_idx($transaction)}) {
|
||
$transaction->{duplicate} = 1;
|
||
$duplicates++;
|
||
next;
|
||
}
|
||
}
|
||
|
||
$self->statistics({
|
||
total => scalar(@{ $self->transactions }),
|
||
errors => $errors,
|
||
duplicates => $duplicates,
|
||
to_import => scalar(@{ $self->transactions }) - $errors - $duplicates,
|
||
});
|
||
}
|
||
|
||
sub import_transactions {
|
||
my ($self, %params) = @_;
|
||
|
||
my $imported = 0;
|
||
|
||
SL::DB::client->with_transaction(sub {
|
||
# make Emacs happy
|
||
1;
|
||
|
||
foreach my $transaction (@{ $self->transactions }) {
|
||
next if $transaction->{error} || $transaction->{duplicate};
|
||
|
||
SL::DB::BankTransaction->new(
|
||
map { ($_ => $transaction->{$_}) } qw(amount currency_id local_bank_account_id purpose remote_account_number remote_bank_code remote_name transaction_code transdate valutadate)
|
||
)->save;
|
||
|
||
$imported++;
|
||
}
|
||
|
||
1;
|
||
});
|
||
|
||
$self->statistics->{imported} = $imported;
|
||
}
|
||
|
||
sub check_auth {
|
||
$::auth->assert('bank_transaction');
|
||
}
|
||
|
||
sub init_profile {
|
||
my $profile = SL::DB::Manager::CsvImportProfile->find_by(name => 'MT940', login => $::myconfig{login});
|
||
if ( ! $profile ) {
|
||
$profile = SL::DB::Manager::CsvImportProfile->find_by(name => 'MT940', login => 'default');
|
||
sub make_bank_account_idx {
|
||
return join '/', map { my $q = $_; $q =~ s{ +}{}g; $q } @_;
|
||
}
|
||
|
||
sub normalize_text {
|
||
my ($text) = @_;
|
||
|
||
$text = lc($text // '');
|
||
$text =~ s{ }{}g;
|
||
|
||
return $text;
|
||
}
|
||
|
||
sub make_transaction_idx {
|
||
my ($transaction) = @_;
|
||
|
||
if (ref($transaction) eq 'SL::DB::BankTransaction') {
|
||
$transaction = { map { ($_ => $transaction->$_) } qw(local_bank_account_id transdate valutadate amount purpose) };
|
||
}
|
||
return $profile;
|
||
|
||
return normalize_text(join '/',
|
||
map { $_ // '' }
|
||
($transaction->{local_bank_account_id},
|
||
$transaction->{transdate}->ymd,
|
||
$transaction->{valutadate}->ymd,
|
||
(apply { s{0+$}{} } $transaction->{amount} * 1),
|
||
$transaction->{purpose}));
|
||
}
|
||
|
||
sub init_bank_accounts {
|
||
my ($self) = @_;
|
||
|
||
my %bank_accounts;
|
||
|
||
foreach my $bank_account (@{ SL::DB::Manager::BankAccount->get_all }) {
|
||
if ($bank_account->bank_code && $bank_account->account_number) {
|
||
$bank_accounts{make_bank_account_idx($bank_account->bank_code, $bank_account->account_number)} = $bank_account;
|
||
}
|
||
if ($bank_account->iban) {
|
||
$bank_accounts{make_bank_account_idx('IBAN', $bank_account->iban)} = $bank_account;
|
||
}
|
||
}
|
||
|
||
return \%bank_accounts;
|
||
}
|
||
|
||
sub setup_upload_mt940_action_bar {
|
||
... | ... | |
$bar->add(
|
||
action => [
|
||
$::locale->text('Preview'),
|
||
submit => [ '#form', { action => 'BankImport/import_mt940_preview' } ],
|
||
accesskey => 'enter',
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
sub setup_upload_mt940_preview_action_bar {
|
||
my ($self) = @_;
|
||
|
||
for my $bar ($::request->layout->get('actionbar')) {
|
||
$bar->add(
|
||
action => [
|
||
$::locale->text('Import'),
|
||
submit => [ '#form', { action => 'BankImport/import_mt940' } ],
|
||
accesskey => 'enter',
|
||
disabled => $self->statistics->{to_import} ? undef : $::locale->text('No entries can be imported.'),
|
||
],
|
||
);
|
||
}
|
SL/MT940.pm | ||
---|---|---|
package SL::MT940;
|
||
|
||
use strict;
|
||
use warnings;
|
||
|
||
use Data::Dumper;
|
||
use DateTime;
|
||
use Encode;
|
||
use File::Slurp qw(read_file);
|
||
|
||
sub _join_entries {
|
||
my ($parts, $from, $to, $separator) = @_;
|
||
|
||
$separator //= ' ';
|
||
|
||
return
|
||
join $separator,
|
||
grep { $_ }
|
||
map { s{^\s+|\s+$}{}g; $_ }
|
||
grep { $_ }
|
||
map { $parts->{$_} }
|
||
($from..$to);
|
||
}
|
||
|
||
sub parse {
|
||
my ($class, $file_name) = @_;
|
||
|
||
my ($local_bank_code, $local_account_number, %transaction, @transactions, @lines);
|
||
my $line_number = 0;
|
||
|
||
my $store_transaction = sub {
|
||
if (%transaction) {
|
||
push @transactions, { %transaction };
|
||
%transaction = ();
|
||
}
|
||
};
|
||
|
||
foreach my $line (read_file($file_name)) {
|
||
chomp $line;
|
||
$line = Encode::decode('UTF-8', $line);
|
||
$line =~ s{\r+}{};
|
||
$line_number++;
|
||
|
||
if (@lines && ($line =~ m{^\%})) {
|
||
$lines[-1]->[0] .= substr($line, 1);
|
||
|
||
} else {
|
||
push @lines, [ $line, $line_number ];
|
||
}
|
||
}
|
||
|
||
foreach my $line (@lines) {
|
||
if ($line->[0] =~ m{^:25:(\d+)/(\d+)}) {
|
||
$local_bank_code = $1;
|
||
$local_account_number = $2;
|
||
|
||
} elsif ($line->[0] =~ m{^:61: (\d{2}) (\d{2}) (\d{2}) (\d{2}) (\d{2}) (C|D|RC|RD) (.) (\d+) (?:, (\d*))? N (.{3}) (.*)}x) {
|
||
# 1 2 3 4 5 6 7 8 9 10 11
|
||
# :61:2008060806CR952,N051NONREF
|
||
|
||
$store_transaction->();
|
||
|
||
my $valuta_year = $1 * 1 + 2000;
|
||
my $valuta_month = $2;
|
||
my $valuta_day = $3;
|
||
my $trans_month = $4;
|
||
my $trans_day = $5;
|
||
my $debit_credit = $6;
|
||
my $currency = $7;
|
||
my $amount1 = $8;
|
||
my $amount2 = $9 || 0;
|
||
my $transaction_code = $10;
|
||
my $reference = $11;
|
||
|
||
my $valuta_date = DateTime->new_local(year => $valuta_year, month => $valuta_month, day => $valuta_day);
|
||
my $trans_date = DateTime->new_local(year => $valuta_year, month => $trans_month, day => $trans_day);
|
||
my $diff = $valuta_date->subtract_datetime($trans_date);
|
||
my $trans_year_diff = $diff->months < 6 ? 0
|
||
: $valuta_date > $trans_date ? 1
|
||
: -1;
|
||
$trans_date = DateTime->new_local(year => $valuta_year + $trans_year_diff, month => $trans_month, day => $trans_day);
|
||
my $sign = ($debit_credit eq 'D') || ($debit_credit eq 'RC') ? -1 : 1;
|
||
$reference =~ s{//.*}{};
|
||
$reference = '' if $reference eq 'NONREF';
|
||
|
||
%transaction = (
|
||
line_number => $line->[1],
|
||
currency => $currency,
|
||
valutadate => $valuta_date,
|
||
transdate => $trans_date,
|
||
amount => ($amount1 * 1 + ($amount2 / (10 ** length($amount2))))* $sign,
|
||
reference => $reference,
|
||
transaction_code => $transaction_code,
|
||
local_bank_code => $local_bank_code,
|
||
local_account_number => $local_account_number,
|
||
);
|
||
|
||
} elsif (%transaction && ($line->[0] =~ m{^:86:})) {
|
||
if ($line->[0] =~ m{^:86:\d+\?(.+)}) {
|
||
# structured
|
||
my %parts = map { ((substr($_, 0, 2) // '0') * 1 => substr($_, 2)) } split m{\?}, $1;
|
||
|
||
$transaction{purpose} = _join_entries(\%parts, 20, 29);
|
||
$transaction{remote_name} = _join_entries(\%parts, 32, 33, '');
|
||
$transaction{remote_bank_code} = $parts{30};
|
||
$transaction{remote_account_number} = $parts{31};
|
||
|
||
} else {
|
||
# unstructured
|
||
$transaction{purpose} = substr($line->[0], 5);
|
||
}
|
||
|
||
$store_transaction->();
|
||
}
|
||
}
|
||
|
||
$store_transaction->();
|
||
|
||
return @transactions;
|
||
}
|
||
|
||
1;
|
locale/de/all | ||
---|---|---|
'Allow the following users access to my follow-ups:' => 'Erlaube den folgenden Benutzern Zugriff auf meine Wiedervorlagen:',
|
||
'Allow to delete generated printfiles' => 'Löschen von erzeugten Dokumenten erlaubt',
|
||
'Already counted' => 'Bereits erfasst',
|
||
'Already imported entries (duplicates)' => 'Bereits importierte Einträge (Duplikate)',
|
||
'Always edit assembly items (user can change/delete items even if assemblies are already produced)' => 'Erzeugnisbestandteile verändern (Löschen/Umsortieren) auch nachdem dieses Erzeugnis schon produziert wurde.',
|
||
'Always save orders with a projectnumber (create new projects)' => 'Aufträge immer mit Projektnummer speichern (neue Projekte erstellen)',
|
||
'Amended Advance Turnover Tax Return' => 'Berichtigte Anmeldung',
|
||
... | ... | |
'Dunnings' => 'Mahnungen',
|
||
'Dunnings (Id -- Dunning Date --Dunning Level -- Dunning Fee)' => 'Mahnungen (Nummer -- Mahndatum -- Mahnstufe -- Mahngebühr/Zinsen)',
|
||
'Dunningstatistic' => 'Mahnstatistik',
|
||
'Duplicate' => 'Duplikat',
|
||
'Duplicate in CSV file' => 'Duplikat in CSV-Datei',
|
||
'Duplicate in database' => 'Duplikat in Datenbank',
|
||
'During the next update a taxkey 0 with tax rate of 0 will automatically created.' => 'Beim nächsten Ausführen des Updates wird ein Steuerschlüssel 0 mit einem Steuersatz von 0% automatisch erzeugt.',
|
||
... | ... | |
'Enter the requested execution date or leave empty for the quickest possible execution:' => 'Geben Sie das jeweils gewünschte Ausführungsdatum an, oder lassen Sie das Feld leer für die schnellstmögliche Ausführung:',
|
||
'Entries for which automatic conversion failed:' => 'Einträge, für die die automatische Umstellung fehlschlug:',
|
||
'Entries for which automatic conversion succeeded:' => 'Einträge, für die die automatische Umstellung erfolgreich war:',
|
||
'Entries ready to import' => 'Zu importierende Einträge',
|
||
'Entries with errors' => 'Einträge mit Fehlern',
|
||
'Equity' => 'Passiva',
|
||
'Erfolgsrechnung' => 'Erfolgsrechnung',
|
||
'Error' => 'Fehler',
|
||
... | ... | |
'Error: Invalid language' => 'Fehler: Sprache ungültig',
|
||
'Error: Invalid part' => 'Fehler: Artikel ungültig',
|
||
'Error: Invalid part type' => 'Fehler: ungültiger Artikeltyp',
|
||
'Error: Invalid parts group' => 'Fehler: Warengruppe ungültig',
|
||
'Error: Invalid parts group id #1' => 'Fehler: Ungültige Warengruppen-ID #1',
|
||
'Error: Invalid parts group name #1' => 'Fehler: Ungültiger Warengruppenname: #1',
|
||
'Error: Invalid payment terms' => 'Fehler: Zahlungsbedingungen ungültig',
|
||
... | ... | |
'Import result' => 'Import-Ergebnis',
|
||
'Import scanned documents' => 'Importiere gescannte Dateien',
|
||
'Importdate' => 'Importdatum',
|
||
'Imported' => 'Importiert',
|
||
'Imported entries' => 'Importierte Einträge',
|
||
'In addition to the above date functions, subtract the following amount of days from the calculated date as a buffer.' => 'Der folgende Puffer in Tagen wird von den beiden obigen vorausberechneten Daten abgezogen.',
|
||
'In order to do that hit the button "Delete transaction".' => 'Drücken Sie dafür auf den Button "Buchung löschen".',
|
||
'In order to migrate the old folder structure into the new structure you have to chose which client the old structure will be assigned to.' => 'Um die alte Ordnerstruktur in die neue Struktur zu migrieren, müssen Sie festlegen, welchem Mandanten die bisherige Struktur zugewiesen wird.',
|
||
... | ... | |
'Loading...' => 'Wird geladen...',
|
||
'Local Bank Code' => 'Lokale Bankleitzahl',
|
||
'Local Tax Office Preferences' => 'Angaben zum Finanzamt',
|
||
'Local account' => 'Eigenes Konto',
|
||
'Local account number' => 'Lokale Kontonummer',
|
||
'Local bank account' => 'Lokales Bankkonto',
|
||
'Local bank code' => 'Lokale Bankleitzahl',
|
||
... | ... | |
'MD' => 'PT',
|
||
'MIME type' => 'MIME-Typ',
|
||
'MT940 import' => 'MT940 Import',
|
||
'MT940 import preview' => 'MT940-Import-Vorschau',
|
||
'MT940 import result' => 'MT940-Import-Ergebnis',
|
||
'Mails' => 'E-Mails',
|
||
'Main Contact Person' => 'Hauptansprechpartner',
|
||
'Main Preferences' => 'Grundeinstellungen',
|
||
... | ... | |
'No assembly has been selected yet.' => 'Es wurde noch kein Erzeugnis ausgewahlt.',
|
||
'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.',
|
||
'No bank account chosen!' => 'Kein Bankkonto ausgewählt!',
|
||
'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => 'Kein Bankkonto für BLZ/BIC #1, Kontonummer/IBAN #2 konfiguriert.',
|
||
'No bank account flagged for ZUGFeRD usage was found.' => 'Es wurde kein Bankkonto gefunden, das für Nutzung mit ZUGFeRD markiert ist.',
|
||
'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => 'Für diesen Kunden wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
|
||
'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => 'Für diesen Lieferanten wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
|
||
... | ... | |
'No email for user with login #1 defined.' => 'Keine E-Mail-Adresse für den Benutzer mit dem Login #1 definiert.',
|
||
'No email recipient for customer #1 defined.' => 'Keine E-Mail-Adresse (Rechnungs- oder global) für den Kunden #1 definiert.',
|
||
'No end date given, setting to today' => 'Kein Enddatum gegeben, setze Enddatum auf heute',
|
||
'No entries can be imported.' => 'Es können keine Einträge importiert werden.',
|
||
'No entries have been imported yet.' => 'Es wurden noch keine Einträge importiert.',
|
||
'No entries have been selected.' => 'Es wurden keine Einträge ausgewählt.',
|
||
'No errors have occurred.' => 'Es sind keine Fehler aufgetreten.',
|
||
... | ... | |
'Orphaned' => 'Nie benutzt',
|
||
'Orphaned currencies' => 'Verwaiste Währungen',
|
||
'Other Matches' => 'Andere Treffer',
|
||
'Other party' => 'Andere Partei',
|
||
'Other recipients' => 'Weitere EmpfängerInnen',
|
||
'Other users\' follow-ups' => 'Wiedervorlagen anderer Benutzer',
|
||
'Other values are ignored.' => 'Andere Eingaben werden ignoriert.',
|
||
... | ... | |
'Please contact your administrator or a service provider.' => 'Bitte kontaktieren Sie Ihren Administrator oder einen Dienstleister.',
|
||
'Please contact your administrator.' => 'Bitte wenden Sie sich an Ihren Administrator.',
|
||
'Please correct the settings and try again or deactivate that client.' => 'Bitte korrigieren Sie die Einstellungen und versuchen Sie es erneut, oder deaktivieren Sie diesen Mandanten.',
|
||
'Please create a CSV import profile called "MT940" for the import type bank transactions:' => 'Bitte erstellen Sie ein CSV Import Profil mit dem Namen "MT940" für den Importtyp Bankbewegungen',
|
||
'Please define a taxkey for the following taxes and run the update again:' => 'Bitte definieren Sie einen Steuerschlüssel für die folgenden Steuern und starten Sie dann das Update erneut:',
|
||
'Please do so in the administration area.' => 'Bitte erledigen Sie dies im Administrationsbereich.',
|
||
'Please enter a profile name.' => 'Bitte geben Sie einen Profilnamen an.',
|
||
... | ... | |
'Remittance information prefix' => 'Verwendungszweckvorbelegung (Präfix)',
|
||
'Remote Bank Code' => 'Fremde Bankleitzahl',
|
||
'Remote Name/Customer/Description' => 'Kunden/Lieferantenname und Beschreibung',
|
||
'Remote account' => 'Gegenkonto',
|
||
'Remote account number' => 'Fremde Kontonummer',
|
||
'Remote bank code' => 'Fremde Bankleitzahl',
|
||
'Remote name' => 'Fremder Kontoinhaber',
|
||
... | ... | |
'The IBAN is missing.' => 'Die IBAN fehlt.',
|
||
'The ID #1 is not a valid database ID.' => 'Die ID #1 ist keine gültige Datenbank-ID.',
|
||
'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => 'Der LDAP-Server "#1:#2" ist nicht erreichbar. Bitte überprüfen Sie die Angaben in config/kivitendo.conf.',
|
||
'The MT940 import needs an import profile called MT940' => 'Der MT940 Import benötigt ein Importprofil mit dem Namen "MT940"',
|
||
'The Mail strings have been saved.' => 'Die vorbelegten E-Mail-Texte wurden gespeichert.',
|
||
'The PDF has been created' => 'Die PDF-Datei wurde erstellt.',
|
||
'The PDF has been printed' => 'Das PDF-Dokument wurde gedruckt.',
|
||
... | ... | |
'To (time)' => 'Bis',
|
||
'To Date' => 'Bis',
|
||
'To continue please change the taxkey 0 to another value.' => 'Um fortzufahren, ändern Sie bitte den Steuerschlüssel 0 auf einen anderen Wert.',
|
||
'To import' => 'Zu importieren',
|
||
'To upload images: Please create shoppart first' => 'Um Bilder hochzuladen bitte Shopartikel zuerst anlegen',
|
||
'To user login' => 'Zum Benutzerlogin',
|
||
'Toggle marker' => 'Markierung umschalten',
|
||
... | ... | |
'Total' => 'Summe',
|
||
'Total Fees' => 'Kumulierte Gebühren',
|
||
'Total Sales Orders Value' => 'Auftragseingang',
|
||
'Total number of entries' => 'Gesamtzahl Einträge',
|
||
'Total stock value' => 'Gesamter Bestandswert',
|
||
'Total sum' => 'Gesamtsumme',
|
||
'Total weight' => 'Gesamtgewicht',
|
||
... | ... | |
'Transaction ID missing.' => 'Die Buchungs-ID fehlt.',
|
||
'Transaction Value' => 'Umsatz',
|
||
'Transaction Value Currency Code' => 'WKZ Umsatz',
|
||
'Transaction date' => 'Buchungsdatum',
|
||
'Transaction deleted!' => 'Buchung gelöscht!',
|
||
'Transaction description' => 'Vorgangsbezeichnung',
|
||
'Transaction has already been cancelled!' => 'Diese Buchung wurde bereits storniert.',
|
||
... | ... | |
'You have to grant users access to one or more clients.' => 'Benutzern muss dann Zugriff auf einzelne Mandanten gewährt werden.',
|
||
'You have to specify a department.' => 'Sie müssen eine Abteilung wählen.',
|
||
'You have to specify an execution date for each antry.' => 'Sie müssen für jeden zu buchenden Eintrag ein Ausführungsdatum angeben.',
|
||
'You have to upload an MT940 file to import.' => 'Sie müssen die zu importierende MT940-Datei hochladen.',
|
||
'You must chose a user.' => 'Sie müssen einen Benutzer auswählen.',
|
||
'You must enter a name for your new print templates.' => 'Sie müssen einen Namen für die neuen Druckvorlagen angeben.',
|
||
'You must not change this AP transaction.' => 'Sie dürfen diese Kreditorenbuchung nicht verändern.',
|
templates/webpages/bank_import/import_mt940.html | ||
---|---|---|
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE T8 %]
|
||
|
||
<h1>[% FORM.title %]</h1>
|
||
|
||
[% IF preview %]
|
||
<form method="post" action="controller.pl" enctype="multipart/form-data" id="form">
|
||
[% L.hidden_tag('file_name', SELF.file_name) %]
|
||
</form>
|
||
[% END %]
|
||
|
||
<h2>[% LxERP.t8("Overview") %]</h2>
|
||
|
||
<div>
|
||
<table>
|
||
<tr>
|
||
<td>[% LxERP.t8("Total number of entries") %]:</td>
|
||
<td align="right">[% SELF.statistics.total %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td>[% LxERP.t8("Entries with errors") %]:</td>
|
||
<td align="right">[% SELF.statistics.errors %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<td>[% LxERP.t8("Already imported entries (duplicates)") %]:</td>
|
||
<td align="right">[% SELF.statistics.duplicates %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
[% IF preview %]
|
||
<td>[% LxERP.t8("Entries ready to import") %]:</td>
|
||
<td align="right">[% SELF.statistics.to_import %]</td>
|
||
[% ELSE %]
|
||
<td>[% LxERP.t8("Imported entries") %]:</td>
|
||
<td align="right">[% SELF.statistics.imported %]</td>
|
||
[% END %]
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
|
||
[% IF SELF.statistics.total %]
|
||
|
||
<h2>[% LxERP.t8("Transactions") %]</h2>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr class="listheading">
|
||
<th>[% LxERP.t8("Transaction date") %]</th>
|
||
<th>[% LxERP.t8("Valutadate") %]</th>
|
||
<th>[% LxERP.t8("Other party") %]</th>
|
||
<th>[% LxERP.t8("Purpose") %]</th>
|
||
<th>[% LxERP.t8("Amount") %]</th>
|
||
<th>[% LxERP.t8("Remote account") %]</th>
|
||
<th>[% LxERP.t8("Local account") %]</th>
|
||
<th>[% LxERP.t8("Information") %]</th>
|
||
</tr>
|
||
</thead>
|
||
|
||
<tbody>
|
||
[% FOREACH transaction = SELF.transactions %]
|
||
<tr class="listrow[% IF transaction.error %]_error[% END %]">
|
||
<td align="right">[% transaction.transdate.to_kivitendo %]</td>
|
||
<td align="right">[% transaction.valutadate.to_kivitendo %]</td>
|
||
<td>[% HTML.escape(transaction.remote_name) %]</td>
|
||
<td>[% HTML.escape(transaction.purpose) %]</td>
|
||
<td align="right">[% LxERP.format_amount(transaction.amount, 2) %]</td>
|
||
<td>
|
||
[% IF transaction.remote_bank_code && transaction.remote_account_number %]
|
||
[% HTML.escape(transaction.remote_bank_code) %] / [% HTML.escape(transaction.remote_account_number) %]
|
||
[% END %]
|
||
</td>
|
||
<td>[% HTML.escape(transaction.bank_account.name) %]</td>
|
||
<td>
|
||
[% IF transaction.error %]
|
||
[% HTML.escape(transaction.error) %]
|
||
[% ELSIF transaction.duplicate %]
|
||
[% LxERP.t8("Duplicate") %]
|
||
[% ELSIF preview %]
|
||
[% LxERP.t8("To import") %]
|
||
[% ELSE %]
|
||
[% LxERP.t8("Imported") %]
|
||
[% END %]
|
||
</td>
|
||
</tr>
|
||
[% END %]
|
||
</tbody>
|
||
</table>
|
||
|
||
[% END %]
|
templates/webpages/bank_import/upload_mt940.html | ||
---|---|---|
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE T8 %]
|
||
|
||
[%- INCLUDE 'common/flash.html' %]
|
||
|
||
<h1>[% FORM.title %]</h1>
|
||
|
||
<p>
|
||
[% "Import a MT940 file:" | $T8 %]
|
||
</p>
|
||
|
||
<form method="post" action="controller.pl" enctype="multipart/form-data" id="form">
|
||
[% L.input_tag('file', '', type => 'file', accept => '*') %]
|
||
</form>
|
templates/webpages/bankimport/form.html | ||
---|---|---|
[%- USE HTML %]
|
||
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
[%- USE T8 %]
|
||
|
||
<div class="listtop">[% FORM.title %]</div>
|
||
|
||
[% IF profile %]
|
||
<p>
|
||
[% "Import a MT940 file:" | $T8 %]
|
||
</p>
|
||
|
||
<form method="post" action="controller.pl" enctype="multipart/form-data" id="form">
|
||
[% L.input_tag('file', '', type => 'file', accept => '*') %]
|
||
</form>
|
||
[% ELSE %]
|
||
<p>
|
||
[% "Please create a CSV import profile called \"MT940\" for the import type bank transactions:" | $T8 %] <a href="[% SELF.url_for(controller => 'CsvImport', action => 'new', 'profile.type' => 'bank_transactions' ) %]">CsvImport</a>
|
||
</p>
|
||
[% END %]
|
Auch abrufbar als: Unified diff
MT940-Import: Implementation eines eigenen Parsers anstelle von AQBanking