Bankerweiterung - Zwischenstand, erster Entwurf
authorNiclas Zimmermann <niclas@kivitendo-premium.de>
Mon, 2 Sep 2013 15:25:16 +0000 (17:25 +0200)
committerG. Richardson <information@kivitendo-premium.de>
Tue, 5 May 2015 07:45:02 +0000 (09:45 +0200)
Erstellung von Tabelle bank_transactions

Import von Bankbewegungen (in Tabelle bank_transactions)

Menu-Eintrag war noch nicht commitet

Controller für die Bank-Transaktionen

Dialog hin- und her

Achtung: noch mit Debug-Statements!

Dies und das für Bank_transactions...

BankTransaction in RecordLinks

Kann verknüpfte Belege Speichern

Man kann Rechnungen mehr oder weniger schon als bezahlt markieren

Erweiterung für EK-Rechnungen (funktioniert noch nicht ganz)

EK-Rechnungen und erste Vorschläge für Rechnung bezahlen

Information von Rechnungen + Javascript statt Ajax

Style als Link lassen

Deckt verschiedene Spezialfälle ab

Datums- und Zahlenformatierungen (noch nicht fertig)

Datumsformat und Übersetzungen

Sub date wieder aus LxERP entfernt

Logik für automatisches Zuweisen

Bericht für BTs (noch nicht ganz fertig)

Formatierungen für Zeilen mit ReportGenerator

Löschen-Knopf und paar Sachen

Entwurf laden und mit Parametern aus form überschreiben

Aufruf mit Parametern als get für Kreditorenbuchung:

ap.pl?action=load_draft&id=ap-invoice-1385202327-644205-8150&invnumber=dsfdf&remove_draft=0&paid_1=123,45

Rechnung aus Bankbeleg erstellen

Bankkonten-Filter und id auf bank_account statt local...

Verknüpfte Belege mit id-Verknüpfung zu local_bank_account

CsvImport mit ID auf BankAccounts (noch nicht getestet)

Behebt Bugs bei Csv-Import von Bankbewegungen

Währungs-ID statt Währungsstring benutzen

Passt den Bankbewegungs-Import an die neuen Änderungen der Helfer an

Filter für create_invoice

Vergessene Templates

Filter für create invoice funktionier halb

Rest für den Filter von create Invoice

Auswahllistenbegrenzer bei Lieferantenfilter beachten

Mehr Daten bei 'create invoice' übernehmen

Wenn man eine Kreditorenbuchung aus einer Bankbewegung erstellt,
werden nun mehr Daten aus der Bankbewegung in die Vorlage über-
nommen.

Änderungen in drafts.pl (gehören zu letztem Commit)

Betrag richtig formatieren und Form submitten

Bei assign invoice werden jetzt Rechnungsbeträge richtig formatiert
und es werden Rechnungen gesucht, wenn man auf 'Enter' drückt.

Weiterleitung auf List und Rechnungsbezahlen repariert

Vorzeichen wechseln bei create invoice

Das Vorzeichen von der Bankbewegung muss geändert werden, wenn man
aus einer Bankbewegung eine Kreditorenbuchung erstellt.

Sortierung von Spalten bei Bankbewegungen

Geoffery's Änderungen bzgl. Vorschlagsuche

BankTransaction - create_invoice filtert für Kreditorenbuchungen

Im Bank-Dropdown Bankname zuerst anzeigen

Und die Breite vom Dropdown angepasst.

Bankauszug verbuchen Menüpunkt nicht unter Berichte

Banktransactions - Spalten in Tabellen umsortiert

Banktransactions Tooltip - bt-Info in Tooltip Fenster aufnehmen

Damit man innerhalb des Tooltip Fensters die wichtigsten Infos auf einen
Blick hat.

Sortierreihenfolge

Es wird jetzt zuerst nach Alphabet, dann gegen das Alphabet sortiert.

Banktransactions

Verbuchte Beträge werden jetzt in der BankTransaction gespeichert.

Sortierung auf DB-Ebene, falls möglich

Anzeige von verbuchten Beträgen der BankTransactions

Kleinerer Bug

Ein paar Debug-Statements entfernt

Verknüpfte Rechnungen im BankTransaction-Bericht anzeigen.

Kontenabgleich

Erste Schritte zum Kontenabgleich.

Kontenabgleich funktioniert schon ganz gut.

TODO: nochmal Funktionen checken
TODO: Filter hinzufügen

Kontenabgleich

Filter für BT und PR im Kontenabgleich.

TODO: Datumsfilter für beide gemeinsam umschreiben und Bilanzierung anzeigen.

Kontenabgleich übergreifender Datumsfilter

Das transdate wird jetzt auch übergreifend für Bankbewegungen und
verbuchte Zahlungen gesetzt.

TODO: Nach Änderung dieser Daten sollten die Tabellen neugeladen
und alle Teilfilter zurück auf 0 gesetzt werden.

Kontenabgleich - ein paar Sachen ausprobiert

Ist nur zum weitermachen (alle Änderungen nur Tests).

Kontenabgleich Kleiner Bug in Search

Kontenabgleich - Erste Übersicht

Kontenabgleich Anzeige von abgeglichenen Buchungen mit Bankbuchungen
funktioniert schonmal

Nächstes: Filter.

Kontenabgleich Filtern ist jetzt in der Übersicht möglich.

Next: Spalte reconciliation_link durch group oder so ersetzen.

Kontenabgleich In rec_group in reconciliation_links

In der Tabelle reconciliation_links werden jetzt keine ids mehr auf
reconciliation_links gespeichert. Stattdessen erkennt man an der
rec_group, welche Zeilen zusammengehören.

Kontenabgleich Berechnung von Summen

Jetzt werden Summen von Bankbewegungen und cleared payments angezeigt.

Next: Anhaken und abgleichen implementieren.

Kontenabgleich Checkboxen fügen Elemente zur Tabelle

Es gibt jetzt Checkboxen neben BT's und BB's. Wenn man sie anhakt,
wird die BT bzw. die BB in eine Tabelle zum abgleichen gesteckt.

TODO: Button zum abgleichen programmieren.

Kontenabgleich Man kann jetzt auch im Report abgleichen

Es fehlt noch: Abbruch-Action, falls beim Abgleichen etwas fehlerhaft ist.

Kontenabgleich Manager für ReconciliationLink

Wurde vergessen zu committen

Kontenabgleich Vorzeichenfehler bei AccTransactions behoben.

In der Anzeige muss das Vorzeichen von AccTransactions bei Buchungen
auf Bank geändert werden.

Kontenabgleich Code aufräumen und Sortierung nach Datum

Es wird jetzt immer nach Datum sortiert. Weiterhin wurde im Code
eine sub von actions nach helpers geschoben.

Kontenabgleich Abbruch nach Fehlern beim Abgleichen

Wenn beim Abgleichen Fehler auftreten, wird man jetzt auf die Über-
sichtsseite geleitet.

Kontenabgleich/BankTransactions Upgrade-Script für Tabellen anpassen

Einige Änderungen der Tabellen sind in den Scripten jetzt enthalten.

Kontenabgleich Farbliche Hinterlegung und Entfernung von Debug-Code

Nicht zugewiesene BankTransactions und Buchungen auf Bankkonten
werden im Kontenabgleich jetzt rot hinterlegt.

Weiterhin wurden noch Debug-Statements entfernt.

TODO: Erweiterung auf andere css Klassen (nicht nur kivitendo, sondern
auch mobile/alte lx-office-styles etc...)

Kontenabgleich Behebt Bug

Unter gewissen Umständen wurden nach der Filterung noch alte Daten
beibehalten. Jetzt wird alles erneuert.

Kontenabgleich Filter für cleared

Man kann jetzt in der Übersicht auch nach cleared und uncleared filtern.

Kontenabgleich Erste Schritte für Automatischen Abgleich

Bisher werden Vorschläge nur dargestellt.

TODO: Button 'Abgleichen' für Vorschläge programmieren.

Kontenabgleich Vorschläge können jetzt abgeglichen werden

Vorschläge können jetzt auch automatisch abgeglichen werden, wenn
man sie anhakt und danach auf abgleichen klickt.

Kontenabgleich Anzeige von Reference

Es gibt jetzt einen Link im Kontenabgleich auf ar/ap/gl.

Kontenabgleich Kunden-/Lieferantennamen

Kunden- und Lieferantennamen werden jetzt für AccTransactions an-
gezeigt. Im Falle einer Dialogbuchung erscheint ihre Beschreibung.

Kontenabgleich Untersortierung nach Betrag

Bisher wurde nur nach Datum sortiert. Innerhalb eines Datums wird
jetzt zusätzlich nach Betrag sortiert.

Kontenabgleich/BankTransactions Entfernung Debug-Statements

Kontenabgleich BankTransactions als verbucht markieren

BankTransactions werden jetzt als verbucht markiert, wenn sie abge-
glichen sind. Ansonsten wurde noch ein kleiner Syntax-Fehler behoben.

Kontenabgleich Bilanz über alle Buchungen berechnen

Die Bilanz wird jetzt unabhängig vom Filter sowohl für cleared als
auch uncleared berechnet. Weiterhin wurde noch ein Vorzeichenfehler
in der Bilanz für Bankbewegungen behoben und der Code etwas verschoben.

Kontenabgleich Verlinkungen aufheben

Man kann jetzt auch Verlinkungen nach dem Abgleichen aufheben.

Kontenabgleich Code-Formatierung und kleine Verbesserung in der Anzeige

Formatierung von Code wurde geändert, sowie einige kleine Besserungen
in der Anzeige (zum Beispiel sind Beträge jetzt rechts orientiert
in der Tabellenzeile).

Kontenabgleich Tabellenhöhe mit relativer Größe

Die Tabellenhöhe hängt jetzt relativ von der Größe des Bildschirms
ab anstatt von einer festen Pixel-Anzahl.

Kontenabgleich Ok message für Flash eingebaut

Kontenabgleich gehört noch zum Flash-message-Commit

Kontenabgleich Zusammenführen verschiedener Abgleichmöglichkeiten

Bisher wurden verschiedene Methoden implementiert, den Kontenabgleich
zu machen. In diesem Commit werden zwei verschiedene Möglichkeiten
unter einen Hut gebracht. Es ist auf der erstellten Seite auch ohne
weiteres Möglich weitere Möglichkeiten hinzuzufügen.

Kontenabgleich Anzeige von Overview umbauen

Die Anzeige von Overview wurde verändert, so dass man jetzt noch
schneller abgleichen kann.

TODO: Anzeige von Proposals der Anzeige von Overview angleichen.

Kontenabgleich Behebt Syntaxfehler

Kontenabgleich Umgang mit Stornos ändern

Der Umgang mit Stornos wurde geändert. Statt Stornos nicht anzuzeigen,
gibt es jetzt einen Filter dafür. Der Saldo von BBs und BTs errechnet
sich jetzt nur noch aus den gefilterten Objekten.

Weiterhin gibt es jetzt eine onchange-Action auf den Datumsfeldern
im Filter und der Filter-Button wird nicht mehr angezeigt.

Kontenabgleich Anpassung der proposals an neue Ansicht

Automatische Vorschläge werden jetzt auch in der neuen Darstellung
angezeigt.

Kontenabgleich Mastercheckbox wieder da und Spaltenreihenfolge vertauscht

Die Checkbox, bei der man alle Vorschläge an-/abhaken kann, wird
jetzt wieder angezeigt. Weiterhin wird jetzt auch der Betrag und
das Belegdatum weiter vorne angezeigt.

Kontenabgleich Saldo in der richtigen Spalte

Der Saldo wird jetzt in der richtigen Spalte angezeigt.

Kontenabgleich Buchungen außerhalb des Filters ausgrauen

Bisher kam es vor, dass Buchungen, die in einen Zeitraum außerhalb
des Filters fallen (weil ihre Gegenbuchung in den gefilterten Zeitraum
fällt), trotzdem angezeigt wurden, aber nicht in der
Summe mitgerechnet wurden. Solche Buchungen werden jetzt nur noch
in grauer Schrift angezeigt.

Kontenabgleich Abgeglichene Buchungen besser Filtern

Es kam bisher vor, dass abgeglichene Buchungen, bei denen eine Buchung
nach dem gefilterten Zeitraum un eine Buchung vor dem gefilterten
Zeitraum lag, angezeigt wurden. Dabei war es bisher nicht erforderlich,
dass eine Buchung im gefilterten Zeitraum lag. Jetzt muss mindestens
eine Buchung im gefilterten Zeitraum liegen, damit die abgeglichenen
Buchungen auch angezeigt werden.

Kontenabgleich Absolute Bilanz anzeigen

Jetzt wird auch immer die Summe von BB's und BT's von Beginn der
Buchungen bis zum "Todate" angezeigt (inklusive Stornos).

Banktransactions nach Datum filtern

Man kann jetzt Bankbewegungen nach Datum filtern. Weiterhin funktioniert
jetzt auch der Callback, nachdem man eine Debitorenbuchung erstellt.

Banktransactions: Datumsfilter nach Sortierung beibehalten

Bisher ging der Datumsfilter nach der Sortierung verloren, jetzt
wird er beibehalten.

Kontenabgleich Mehr Vorschläge machen

Bisher wurden nur Vorschläge gemacht, wenn Rechnungen via Bankbewegungen
bezahlt wurden. Jetzt werden auch Vorschläge gemacht, wenn Beträge
deckungsgleich sind und Kontoverbindung von Kunde/Lieferant und
Bankbeleg übereinstimmen.

Kontenabgleich Reconciliate-Button taucht nicht auf

Behebt einen Bug, bei dem der Abgleichen-Knopf nicht auftaucht,
obwohl er das sollte. Das Problem lag daran, dass die beiden Zahlen
6.286,18 und -6.286,18 aufaddiert in Perl nicht Null ergaben.

Kontenabgleich Fügt mehr Proposals hinzu

Auf diese Weise werden noch mehr Übereinstimmungen gefunden.

Banktransactions Teilzahlungen funktionieren jetzt besser

Bisher haben Teilzahlungen zwar funktioniert, jedoch tauchten die
Bankbewegungen, mit denen sie erstellt wurden immer wieder in der
Liste auf. Es lag daran, dass invoice_amount in der Tabelle
bank_transactions falsch gesetzt wurde.

Banktransactions 40 statt 5 Bankbewegungen pro Seite anzeigen

Kontenabgleich Entfernen von Tests

Entfernt alten Code, der die ersten Tests enthielt.

Kontenabgleich Umbenennung von Controller

Der Controller SL/Controller/Reconciliation_3.pm wird umbenannt in
SL/Controller/Reconciliation.pm

Kontenabgleich Umbenennung von Ordner whole_reconciliation

Der Order whole_reconciliation wurde in reconciliation umbenannt.

Banktransactions Minuspunkte für transdate vergeben

Wenn eine Rechnung nicht 30 Tage vor der Bankbewegung liegt, so
wird jetzt ein Minuspunkt für die Vorschläge vergeben.

Kontenabgleich/Banktransaction Rechte hinzufügen

Bank CSV Import - Purpose zusammengefügt

Icons bei Reconciliation

Kontenabgleich Icons in Vorschlägen übernehmen

Icons werden jetzt auch bei Vorschlägen benutzt. Weiterhin wurde
unsinniges Attribut bei Icons im Overview entfernt.

Kontenabgleich bei Import Standard-Währung setzen

Beim Import wird jetzt die Standard-Währung eingetragen, wenn keine
andere Währung vorhanden ist. Weiterhin ist die Währung jetzt auch ein
Pflichtfeld durch ein NOT NULL-Constraint.

Kontenabgleich Spalte in Summenzeile ergänzen

Aufgrund der neu hinzugekommenen Icons musste noch eine Spalte in
der Zeile eingefügt werden, wo die Summen angezeigt werden.

Banktransaction Betrag im Filter parsen

Beim Suchen von Rechnungen im "Rechnung hinzufügen"-Modus wurde der
Betrag im Filter nicht geparst. Man musste bisher also immer einen
Punkt statt einem Komma eingeben.

Kontenabgleich CSS verbessern

Bisher konnte man nicht bis an den unteren Bildschirmrand scrollen.
Dieser Commit versucht dies zu beheben. Allerdings ist es unklar,
ob das Problem durch den Commit behoben ist.

Banktransactions Automatisches bezahlen von Vorschlägen

Banktransactions Speichern von Vorschlägen

Automatische Vorschläge, die kivitendo schon macht, können jetzt
auch benutzt werden, um vorgeschlagene Rechnungen direkt zu speichern.

Banktransactions Bugfix

Bisher wurden der "Rechnung speichern"-Button und der "Save proposals"-
Button nur getogglet. Das hat dazu geführt, dass wenn man zweimal
auf den Reiter "Proposals" drückt, der Rechnung speichern-Butten
angezeigt wurde, obwohl der Save proposals-Button angezeigt werden
musste. Jetzt werden die Buttons versteckt und angezeigt, was den
Fehler behebt.

Banktransactions - kein von und bis wenn leer

Die Wörter "von" und "bis" nur anzeigen, wenn auch ein von- oder
bis-Datum gesetzt ist.

Bankbewegungen - in Tooltip Tagesdelta anzeigen

Anzahl der Tage zwischen (vorgeschlagenem) Rechnungsdatum und
Bankeingangsdatum wird in Klammern hinter dem Bankdatum angezeigt.

Banktransactions - bei Proposals trotz sub-cent Matchen

Übereinstimmung muß nicht genau sein, sondern kleiner 1 Cent.
Wegen Rundungsproblematik.

String-Vergleich bei Banktransactions - mind. 3 Zeichen

Mindestlänge für Wortvergleich.

testing - agreement modifying tests

Banktransactions Belegdatum bei Rechnung zuweisen

Wenn man auf Rechnung zuweisen klickt, wird jetzt sowohl von dem
Bankbeleg als auch von den Rechnungen das Belegdatum angezeigt.
Weiterhin wurde ein Filter für das Datum hinzugefügt.

Kontenabgleich/Banktransactions

Reiter 'Set cleared entfernen' im Kontenabgleich.

Bei Vorgeschlagenen Rechnungen anzeigen, ob EK oder VK.

Deutsche Übersetzungen

VERSION angepasst

MT940 Importer der aqbanking-cli über CSV-Import aufruft

Noch alles hartkodiert
Kein Dublettencheck

MT940 Importer der aqbanking-cli über CSV-Import aufruft

Noch alles hartkodiert
Kein Dublettencheck

Bank - in Bankkontodropdowns Kontoname berücksichtigen

Bankerweiterung - Offener Betrag bei Rechnungsfilter

wenn man aus Bankbuchungen eine Rechnung zuweisen möchte.

RB - corrected all after rebase

RB - GLTransaction.pm nach Rebase gefixed

60 files changed:
SL/Auth.pm
SL/Controller/BankTransaction.pm [new file with mode: 0644]
SL/Controller/CsvImport.pm
SL/Controller/CsvImport/BankTransaction.pm [new file with mode: 0644]
SL/Controller/CsvImport/Base.pm
SL/Controller/Helper/GetModels/Sorted.pm
SL/Controller/Project.pm
SL/Controller/Reconciliation.pm [new file with mode: 0644]
SL/DB/AccTransaction.pm
SL/DB/BankTransaction.pm [new file with mode: 0644]
SL/DB/GLTransaction.pm
SL/DB/Helper/ALL.pm
SL/DB/Helper/Mappings.pm
SL/DB/Invoice.pm
SL/DB/Manager/BankTransaction.pm [new file with mode: 0644]
SL/DB/Manager/Invoice.pm
SL/DB/Manager/OrderItem.pm
SL/DB/Manager/ReconciliationLink.pm [new file with mode: 0644]
SL/DB/MetaSetup/BankTransaction.pm [new file with mode: 0644]
SL/DB/MetaSetup/ReconciliationLink.pm [new file with mode: 0644]
SL/DB/PurchaseInvoice.pm
SL/DB/ReconciliationLink.pm [new file with mode: 0644]
SL/Helper/Flash.pm
SL/Presenter.pm
SL/Presenter/BankAccount.pm [new file with mode: 0644]
SL/Presenter/Invoice.pm
SL/Presenter/Record.pm
SL/RC.pm
VERSION
bin/mozilla/drafts.pl
css/kivitendo/main.css
image/bank-building.jpg [new file with mode: 0644]
js/locale/de.js
locale/de/all
menus/erp.ini
sql/Pg-upgrade2/automatic_reconciliation.sql [new file with mode: 0644]
sql/Pg-upgrade2/bank_transactions.sql [new file with mode: 0644]
templates/webpages/bank_transactions/_filter.html [new file with mode: 0644]
templates/webpages/bank_transactions/add_list.html [new file with mode: 0644]
templates/webpages/bank_transactions/assign_invoice.html [new file with mode: 0644]
templates/webpages/bank_transactions/create_invoice.html [new file with mode: 0644]
templates/webpages/bank_transactions/filter_drafts.html [new file with mode: 0644]
templates/webpages/bank_transactions/invoices.html [new file with mode: 0644]
templates/webpages/bank_transactions/list.html [new file with mode: 0644]
templates/webpages/bank_transactions/report_bottom.html [new file with mode: 0644]
templates/webpages/bank_transactions/report_top.html [new file with mode: 0644]
templates/webpages/bank_transactions/search.html [new file with mode: 0644]
templates/webpages/bank_transactions/tabs/all.html [new file with mode: 0644]
templates/webpages/bank_transactions/tabs/automatic.html [new file with mode: 0644]
templates/webpages/common/flash.html
templates/webpages/csv_import/_form_mt940.html [new file with mode: 0644]
templates/webpages/csv_import/form.html
templates/webpages/reconciliation/_linked_transactions.html [new file with mode: 0644]
templates/webpages/reconciliation/assigning_table.html [new file with mode: 0644]
templates/webpages/reconciliation/form.html [new file with mode: 0644]
templates/webpages/reconciliation/proposals.html [new file with mode: 0644]
templates/webpages/reconciliation/search.html [new file with mode: 0644]
templates/webpages/reconciliation/tabs/automatic.html [new file with mode: 0644]
templates/webpages/reconciliation/tabs/overview.html [new file with mode: 0644]
templates/webpages/reconciliation/tabs/set_cleared.html [new file with mode: 0644]

index 8439a2c..e202b98 100644 (file)
@@ -977,6 +977,7 @@ sub all_rights_full {
     ["general_ledger",                 $locale->text("Transactions, AR transactions, AP transactions")],
     ["datev_export",                   $locale->text("DATEV Export")],
     ["cash",                           $locale->text("Receipt, payment, reconciliation")],
+    ["bank_transaction",               $locale->text("Bank transactions")],
     ["--reports",                      $locale->text('Reports')],
     ["report",                         $locale->text('All reports')],
     ["advance_turnover_tax_return",    $locale->text('Advance turnover tax return')],
diff --git a/SL/Controller/BankTransaction.pm b/SL/Controller/BankTransaction.pm
new file mode 100644 (file)
index 0000000..afa06e9
--- /dev/null
@@ -0,0 +1,621 @@
+package SL::Controller::BankTransaction;
+
+# idee- möglichkeit bankdaten zu übernehmen in stammdaten
+# erst Kontenabgleich, um alle gl-Einträge wegzuhaben
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Controller::Helper::GetModels;
+use SL::Controller::Helper::ReportGenerator;
+use SL::ReportGenerator;
+
+use SL::DB::BankTransaction;
+use SL::Helper::Flash;
+use SL::Locale::String;
+use SL::SEPA;
+use SL::DB::Invoice;
+use SL::DB::PurchaseInvoice;
+use SL::DB::RecordLink;
+use SL::JSON;
+use SL::DB::Chart;
+use SL::DB::AccTransaction;
+use SL::DB::Tax;
+use SL::DB::Draft;
+use SL::DB::BankAccount;
+
+use Rose::Object::MakeMethods::Generic
+(
+ 'scalar --get_set_init' => [ qw(models) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+
+
+#
+# actions
+#
+
+sub action_search {
+  my ($self) = @_;
+
+  my $bank_accounts = SL::DB::Manager::BankAccount->get_all();
+
+  $self->render('bank_transactions/search',
+                 label_sub => sub { t8('#1 - Account number #2, bank code #3, #4', $_[0]->name, $_[0]->account_number, $_[0]->bank_code, $_[0]->bank, )},
+                 BANK_ACCOUNTS => $bank_accounts);
+}
+
+sub action_list_all {
+  my ($self) = @_;
+
+  my $transactions = $self->models->get;
+
+  $self->make_filter_summary;
+  $self->prepare_report;
+
+  $self->report_generator_list_objects(report => $self->{report}, objects => $transactions);
+}
+
+sub action_list {
+  my ($self) = @_;
+
+  if (!$::form->{filter}{bank_account}) {
+    flash('error', t8('No bank account chosen!'));
+    $self->action_search;
+    return;
+  }
+
+  my $sort_by = $::form->{sort_by} || 'transdate';
+  $sort_by = 'transdate' if $sort_by eq 'proposal';
+  $sort_by .= $::form->{sort_dir} ? ' DESC' : ' ASC';
+
+  my $fromdate = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{fromdate});
+  my $todate   = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{todate});
+  $todate->add( days => 1 ) if $todate;
+
+  my @where = ();
+  push @where, (transdate => { ge => $fromdate }) if ($fromdate);
+  push @where, (transdate => { lt => $todate })   if ($todate);
+
+  my $bank_transactions = SL::DB::Manager::BankTransaction->get_all(where => [ amount => {ne => \'invoice_amount'},
+                                                                               local_bank_account_id => $::form->{filter}{bank_account},
+                                                                               @where ],
+                                                                    with_objects => [ 'local_bank_account', 'currency' ],
+                                                                    sort_by => $sort_by, limit => 10000);
+
+  my $all_open_ar_invoices = SL::DB::Manager::Invoice->get_all(where => [amount => { gt => \'paid' }], with_objects => 'customer');
+  my $all_open_ap_invoices = SL::DB::Manager::PurchaseInvoice->get_all(where => [amount => { gt => \'paid' }], with_objects => 'vendor');
+
+  my @all_open_invoices;
+  push @all_open_invoices, @{ $all_open_ar_invoices };
+  push @all_open_invoices, @{ $all_open_ap_invoices };
+
+  foreach my $bt (@{ $bank_transactions }) {
+    next unless $bt->{remote_name};  # bank has no name, usually fees, use create invoice to assign
+    foreach my $open_invoice (@all_open_invoices){
+      $open_invoice->{agreement} = 0;
+
+      #compare banking arrangements
+      my ($bank_code, $account_number);
+      $bank_code      = $open_invoice->customer->bank_code      if $open_invoice->is_sales;
+      $account_number = $open_invoice->customer->account_number if $open_invoice->is_sales;
+      $bank_code      = $open_invoice->vendor->bank_code        if ! $open_invoice->is_sales;
+      $account_number = $open_invoice->vendor->account_number   if ! $open_invoice->is_sales;
+      ($bank_code eq $bt->remote_bank_code
+        && $account_number eq $bt->remote_account_number) ? ($open_invoice->{agreement} += 2) : ();
+
+      my $datediff = $bt->transdate->{utc_rd_days} - $open_invoice->transdate->{utc_rd_days};
+      $open_invoice->{datediff} = $datediff;
+
+      #compare amount
+#      (abs($open_invoice->amount) == abs($bt->amount)) ? ($open_invoice->{agreement} += 2) : ();
+# do we need double abs here? 
+      (abs(abs($open_invoice->amount) - abs($bt->amount)) < 0.01) ? ($open_invoice->{agreement} += 4) : ();
+
+      #search invoice number in purpose
+      my $invnumber = $open_invoice->invnumber;
+# possible improvement: match has to have more than 1 character?
+      $bt->purpose =~ /\b$invnumber\b/i ? ($open_invoice->{agreement} += 2) : ();
+
+      #check sign
+      if ( $open_invoice->is_sales && $bt->amount < 0 ) {
+        $open_invoice->{agreement} -= 1;
+      };
+      if ( ! $open_invoice->is_sales && $bt->amount > 0 ) {
+        $open_invoice->{agreement} -= 1;
+      };
+
+      #search customer/vendor number in purpose
+      my $cvnumber;
+      $cvnumber = $open_invoice->customer->customernumber if $open_invoice->is_sales;
+      $cvnumber = $open_invoice->vendor->vendornumber     if ! $open_invoice->is_sales;
+      $bt->purpose =~ /\b$cvnumber\b/i ? ($open_invoice->{agreement}++) : ();
+
+      #compare customer/vendor name and account holder
+      my $cvname;
+      $cvname = $open_invoice->customer->name if $open_invoice->is_sales;
+      $cvname = $open_invoice->vendor->name   if ! $open_invoice->is_sales;
+      $bt->remote_name =~ /\b$cvname\b/i ? ($open_invoice->{agreement}++) : ();
+
+      #Compare transdate of bank_transaction with transdate of invoice
+      #Check if words in remote_name appear in cvname
+      $open_invoice->{agreement} += &check_string($bt->remote_name,$cvname);
+
+      $open_invoice->{agreement} -= 1 if $datediff < -5; # dies hebelt eventuell Vorkasse aus
+      $open_invoice->{agreement} += 1 if $datediff < 30; # dies hebelt eventuell Vorkasse aus
+
+      # only if we already have a good agreement, let date further change value of agreement.
+      # this is so that if there are several open invoices which are all equal (rent jan, rent feb...) the one with the best date match is chose over the others
+      # another way around this is to just pre-filter by periods instead of matching everything
+      if ( $open_invoice->{agreement} > 5 ) {
+        if ( $datediff == 0 ) { 
+          $open_invoice->{agreement} += 3;
+        } elsif  ( $datediff > 0 and $datediff <= 14 ) {
+          $open_invoice->{agreement} += 2;
+        } elsif  ( $datediff >14 and $datediff < 35) {
+          $open_invoice->{agreement} += 1;
+        } elsif  ( $datediff >34 and $datediff < 120) {
+          $open_invoice->{agreement} += 1;
+        } elsif  ( $datediff < 0 ) {
+          $open_invoice->{agreement} -= 1;
+        } else {
+          # e.g. datediff > 120
+        };
+      };
+
+      #if ($open_invoice->transdate->{utc_rd_days} == $bt->transdate->{utc_rd_days}) {  
+        #$open_invoice->{agreement} += 4;
+        #print FH "found matching date for invoice " . $open_invoice->invnumber . " ( " . $bt->transdate->{utc_rd_days} . " . \n";
+      #} elsif (($open_invoice->transdate->{utc_rd_days} + 30) < $bt->transdate->{utc_rd_days}) {  
+        #$open_invoice->{agreement} -= 1;
+      #} else {
+        #$open_invoice->{agreement} -= 2;
+        #print FH "found nomatch date -2 for invoice " . $open_invoice->invnumber . " ( " . $bt->transdate->{utc_rd_days} . " . \n";
+      #};
+      #print FH "agreement after date_agreement: " . $open_invoice->{agreement} . "\n";
+
+
+
+    }
+# finished going through all open_invoices
+
+    # go through each bt
+    # for each open_invoice try to match it to each open_invoice and store agreement in $open_invoice->{agreement} (which gets overwritten each time for each bt)
+    #    calculate 
+#  
+
+    $bt->{proposals} = [];
+    my $agreement = 11;
+    # wird nie ausgeführt, bzw. nur ganz am Ende
+# oder einmal am Anfang?
+# es werden maximal 7 vorschläge gemacht?
+    # 7 mal wird geprüft, ob etwas passt
+    while (scalar @{ $bt->{proposals} } < 1 && $agreement-- > 0) {
+      $bt->{proposals} = [ grep { $_->{agreement} > $agreement } @all_open_invoices ];
+      #Kann wahrscheinlich weg:
+#      map { $_->{style} = "green" } @{ $bt->{proposals} } if $agreement >= 5;
+#      map { $_->{style} = "orange" } @{ $bt->{proposals} } if $agreement < 5 and $agreement >= 3;
+#      map { $_->{style} = "red" } @{ $bt->{proposals} } if $agreement < 3;
+      $bt->{agreement} = $agreement;  # agreement value at cutoff, will correspond to several results if threshold is 7 and several are already above 7
+    }
+  }  # finished one bt
+  # finished all bt
+
+  # separate filter for proposals (second tab, agreement >= 5 and exactly one match)
+  # to qualify as a proposal there has to be
+  # * agreement >= 5
+  # * there must be only one exact match 
+  # * depending on whether sales or purchase the amount has to have the correct sign (so Gutschriften don't work?)
+
+  my @proposals = grep { $_->{agreement} >= 5
+                         and 1 == scalar @{ $_->{proposals} }
+                         and (@{ $_->{proposals} }[0]->is_sales ? abs(@{ $_->{proposals} }[0]->amount - $_->amount) < 0.01  : abs(@{ $_->{proposals} }[0]->amount + $_->amount) < 0.01) } @{ $bank_transactions };
+
+  #Sort bank transactions by quality of proposal
+  $bank_transactions = [ sort { $a->{agreement} <=> $b->{agreement} } @{ $bank_transactions } ] if $::form->{sort_by} eq 'proposal' and $::form->{sort_dir} == 1;
+  $bank_transactions = [ sort { $b->{agreement} <=> $a->{agreement} } @{ $bank_transactions } ] if $::form->{sort_by} eq 'proposal' and $::form->{sort_dir} == 0;
+
+
+  $self->render('bank_transactions/list',
+                title             => t8('List of bank transactions'),
+                BANK_TRANSACTIONS => $bank_transactions,
+                PROPOSALS         => \@proposals,
+                bank_account      => SL::DB::Manager::BankAccount->find_by(id => $::form->{filter}{bank_account}) );
+}
+
+sub check_string {
+    my $bankstring = shift;
+    my $namestring = shift;
+    return 0 unless $bankstring and $namestring;
+
+    my @bankwords = grep(/^\w+$/, split(/\b/,$bankstring));
+
+    my $match = 0;
+    foreach my $bankword ( @bankwords ) {
+        # only try to match strings with more than 2 characters
+        next unless length($bankword)>2; 
+        if ( $namestring =~ /\b$bankword\b/i ) {
+            $match++;
+        };
+    };
+    return $match;
+};
+
+sub action_assign_invoice {
+  my ($self) = @_;
+
+  $self->{transaction} = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
+
+  $self->render('bank_transactions/assign_invoice', { layout  => 0 },
+                title      => t8('Assign invoice'),);
+}
+
+sub action_create_invoice {
+  my ($self) = @_;
+  my %myconfig = %main::myconfig;
+
+  $self->{transaction} = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
+  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(account_number => $self->{transaction}->{remote_account_number});
+
+  my $drafts = SL::DB::Manager::Draft->get_all(where => [ module => 'ap'] , with_objects => 'employee');
+
+  my @filtered_drafts;
+
+  foreach my $draft ( @{ $drafts } ) {
+    my $draft_as_object = YAML::Load($draft->form);
+    my $vendor = SL::DB::Manager::Vendor->find_by(id => $draft_as_object->{vendor_id});
+    $draft->{vendor} = $vendor->name;
+    $draft->{vendor_id} = $vendor->id;
+    push @filtered_drafts, $draft;
+  }
+
+  #Filter drafts
+  @filtered_drafts = grep { $_->{vendor_id} == $vendor_of_transaction->id } @filtered_drafts if $vendor_of_transaction;
+
+  my $all_vendors = SL::DB::Manager::Vendor->get_all();
+
+  $self->render('bank_transactions/create_invoice', { layout  => 0 },
+      title      => t8('Create invoice'),
+      DRAFTS     => \@filtered_drafts,
+      vendor_id  => $vendor_of_transaction ? $vendor_of_transaction->id : undef,
+      vendor_name => $vendor_of_transaction ? $vendor_of_transaction->name : undef,
+      ALL_VENDORS => $all_vendors,
+      limit      => $myconfig{vclimit},
+      callback   => $self->url_for(action                => 'list',
+                                   'filter.bank_account' => $::form->{filter}->{bank_account},
+                                   'filter.todate'       => $::form->{filter}->{todate},
+                                   'filter.fromdate'     => $::form->{filter}->{fromdate}),
+      );
+}
+
+sub action_filter_drafts {
+  my ($self) = @_;
+
+  $self->{transaction} = SL::DB::Manager::BankTransaction->find_by(id => $::form->{bt_id});
+  my $vendor_of_transaction = SL::DB::Manager::Vendor->find_by(account_number => $self->{transaction}->{remote_account_number});
+
+  my $drafts = SL::DB::Manager::Draft->get_all(with_objects => 'employee');
+
+  my @filtered_drafts;
+
+  foreach my $draft ( @{ $drafts } ) {
+    my $draft_as_object = YAML::Load($draft->form);
+    my $vendor = SL::DB::Manager::Vendor->find_by(id => $draft_as_object->{vendor_id});
+    $draft->{vendor} = $vendor->name;
+    $draft->{vendor_id} = $vendor->id;
+    push @filtered_drafts, $draft;
+  }
+
+  my $vendor_name = $::form->{vendor};
+  my $vendor_id = $::form->{vendor_id};
+
+  #Filter drafts
+  @filtered_drafts = grep { $_->{vendor_id} == $vendor_id } @filtered_drafts if $vendor_id;
+  @filtered_drafts = grep { $_->{vendor} =~ /$vendor_name/i } @filtered_drafts if $vendor_name;
+
+  my $output  = $self->render(
+      'bank_transactions/filter_drafts',
+      { output      => 0 },
+      DRAFTS => \@filtered_drafts,
+      );
+
+  my %result = ( count => 0, html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_ajax_add_list {
+  my ($self) = @_;
+
+  my @where_sale     = (amount => { ne => \'paid' });
+  my @where_purchase = (amount => { ne => \'paid' });
+
+  if ($::form->{invnumber}) {
+    push @where_sale,     (invnumber => { ilike => '%' . $::form->{invnumber} . '%'});
+    push @where_purchase, (invnumber => { ilike => '%' . $::form->{invnumber} . '%'});
+  }
+
+  if ($::form->{amount}) {
+    push @where_sale,     (amount => $::form->parse_amount(\%::myconfig, $::form->{amount}));
+    push @where_purchase, (amount => $::form->parse_amount(\%::myconfig, $::form->{amount}));
+  }
+
+  if ($::form->{vcnumber}) {
+    push @where_sale,     ('customer.customernumber' => { ilike => '%' . $::form->{vcnumber} . '%'});
+    push @where_purchase, ('vendor.vendornumber'     => { ilike => '%' . $::form->{vcnumber} . '%'});
+  }
+
+  if ($::form->{vcname}) {
+    push @where_sale,     ('customer.name' => { ilike => '%' . $::form->{vcname} . '%'});
+    push @where_purchase, ('vendor.name'   => { ilike => '%' . $::form->{vcname} . '%'});
+  }
+
+  if ($::form->{transdatefrom}) {
+    my $fromdate = $::locale->parse_date_to_object(\%::myconfig, $::form->{transdatefrom});
+    push @where_sale,     ('transdate' => { ge => $fromdate});
+    push @where_purchase, ('transdate' => { ge => $fromdate});
+  }
+
+  if ($::form->{transdateto}) {
+    my $todate = $::locale->parse_date_to_object(\%::myconfig, $::form->{transdateto});
+    $todate->add(days => 1);
+    push @where_sale,     ('transdate' => { lt => $todate});
+    push @where_purchase, ('transdate' => { lt => $todate});
+  }
+
+  my $all_open_ar_invoices = SL::DB::Manager::Invoice->get_all(where => \@where_sale, with_objects => 'customer');
+  my $all_open_ap_invoices = SL::DB::Manager::PurchaseInvoice->get_all(where => \@where_purchase, with_objects => 'vendor');
+
+  my @all_open_invoices;
+  push @all_open_invoices, @{ $all_open_ar_invoices };
+  push @all_open_invoices, @{ $all_open_ap_invoices };
+
+  @all_open_invoices = sort { $a->id <=> $b->id } @all_open_invoices;
+  #my $all_open_invoices = SL::DB::Manager::Invoice->get_all(where => \@where);
+
+  my $output  = $self->render(
+      'bank_transactions/add_list',
+      { output      => 0 },
+      INVOICES => \@all_open_invoices,
+      );
+
+  my %result = ( count => 0, html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_ajax_accept_invoices {
+  my ($self) = @_;
+
+  my @selected_invoices;
+  foreach my $invoice_id (@{ $::form->{invoice_id} || [] }) {
+    my $invoice_object = SL::DB::Manager::Invoice->find_by(id => $invoice_id);
+    $invoice_object ||= SL::DB::Manager::PurchaseInvoice->find_by(id => $invoice_id);
+
+    push @selected_invoices, $invoice_object;
+  }
+
+  $self->render('bank_transactions/invoices', { layout => 0 },
+                INVOICES => \@selected_invoices,
+                bt_id    => $::form->{bt_id} );
+}
+
+sub action_save_invoices {
+  my ($self) = @_;
+
+  my $invoice_hash = delete $::form->{invoice_ids};
+
+  while ( my ($bt_id, $invoice_ids) = each(%$invoice_hash) ) {
+    my $bank_transaction = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+    my $sign = $bank_transaction->amount < 0 ? -1 : 1;
+    my $amount_of_transaction = $sign * $bank_transaction->amount;
+
+    my @invoices;
+    foreach my $invoice_id (@{ $invoice_ids }) {
+      push @invoices, (SL::DB::Manager::Invoice->find_by(id => $invoice_id) || SL::DB::Manager::PurchaseInvoice->find_by(id => $invoice_id));
+    }
+    @invoices = sort { return 1 if ($a->is_sales and $a->amount > 0);
+                          return 1 if (!$a->is_sales and $a->amount < 0);
+                          return -1; } @invoices                if $bank_transaction->amount > 0;
+    @invoices = sort { return -1 if ($a->is_sales and $a->amount > 0);
+                       return -1 if (!$a->is_sales and $a->amount < 0);
+                       return 1; } @invoices                    if $bank_transaction->amount < 0;
+
+    foreach my $invoice (@invoices) {
+      if ($amount_of_transaction == 0) {
+        flash('warning',  $::locale->text('There are invoices which could not be payed by bank transaction #1 (Account number: #2, bank code: #3)!',
+                                            $bank_transaction->purpose,
+                                            $bank_transaction->remote_account_number,
+                                            $bank_transaction->remote_bank_code));
+        last;
+      }
+      #pay invoice or go to the next bank transaction if the amount is not sufficiently high
+      if ($invoice->amount <= $amount_of_transaction) {
+        $invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id, trans_id => $invoice->id, amount => $invoice->amount, transdate => $bank_transaction->transdate);
+        if ($invoice->is_sales) {
+          $amount_of_transaction -= $sign * $invoice->amount;
+          $bank_transaction->invoice_amount($bank_transaction->invoice_amount + $invoice->amount);
+        } else {
+          $amount_of_transaction += $sign * $invoice->amount if (!$invoice->is_sales);
+          $bank_transaction->invoice_amount($bank_transaction->invoice_amount - $invoice->amount);
+        }
+      } else {
+        $invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id, trans_id => $invoice->id, amount => $amount_of_transaction, transdate => $bank_transaction->transdate);
+        $bank_transaction->invoice_amount($bank_transaction->amount) if $invoice->is_sales;
+        $bank_transaction->invoice_amount($bank_transaction->amount) if !$invoice->is_sales;
+        $amount_of_transaction = 0;
+      }
+
+      #Record a link from the bank transaction to the invoice
+      my @props = (
+          from_table => 'bank_transactions',
+          from_id    => $bt_id,
+          to_table   => $invoice->is_sales ? 'ar' : 'ap',
+          to_id      => $invoice->id,
+          );
+
+      my $existing = SL::DB::Manager::RecordLink->get_all(where => \@props, limit => 1)->[0];
+
+      SL::DB::RecordLink->new(@props)->save if !$existing;
+    }
+    $bank_transaction->save;
+  }
+
+  $self->action_list();
+}
+
+sub action_save_proposals {
+  my ($self) = @_;
+
+  foreach my $bt_id (@{ $::form->{proposal_ids} }) {
+    #mark bt as booked
+    my $bt = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+    $bt->invoice_amount($bt->amount);
+    $bt->save;
+
+    #pay invoice
+    my $arap = SL::DB::Manager::Invoice->find_by(id => $::form->{"proposed_invoice_$bt_id"});
+    $arap    = SL::DB::Manager::PurchaseInvoice->find_by(id => $::form->{"proposed_invoice_$bt_id"}) if not defined $arap;
+    $arap->pay_invoice(chart_id  => $bt->local_bank_account->chart_id,
+                       trans_id  => $arap->id,
+                       amount    => $arap->amount,
+                       transdate => $bt->transdate);
+    $arap->save;
+
+    #create record link
+    my @props = (
+        from_table => 'bank_transactions',
+        from_id    => $bt_id,
+        to_table   => $arap->is_sales ? 'ar' : 'ap',
+        to_id      => $arap->id,
+        );
+
+    my $existing = SL::DB::Manager::RecordLink->get_all(where => \@props, limit => 1)->[0];
+
+    SL::DB::RecordLink->new(@props)->save if !$existing;
+  }
+
+  flash('ok', t8('#1 proposal(s) saved.', scalar @{ $::form->{proposal_ids} }));
+
+  $self->action_list();
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('bank_transaction');
+}
+
+#
+# helpers
+#
+
+sub make_filter_summary {
+  my ($self) = @_;
+
+  my $filter = $::form->{filter} || {};
+  my @filter_strings;
+
+  my @filters = (
+    [ $filter->{"transdate:date::ge"},  $::locale->text('Transdate') . " " . $::locale->text('From Date') ],
+    [ $filter->{"transdate:date::le"},  $::locale->text('Transdate') . " " . $::locale->text('To Date')   ],
+    [ $filter->{"valutadate:date::ge"}, $::locale->text('Valutadate') . " " . $::locale->text('From Date') ],
+    [ $filter->{"valutadate:date::le"}, $::locale->text('Valutadate') . " " . $::locale->text('To Date')   ],
+    [ $filter->{"amount:number"},       $::locale->text('Amount')                                           ],
+    [ $filter->{"bank_account_id:integer"}, $::locale->text('Local bank account')                                           ],
+  );
+
+  for (@filters) {
+    push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
+  }
+
+  $self->{filter_summary} = join ', ', @filter_strings;
+}
+
+sub prepare_report {
+  my ($self)      = @_;
+
+  my $callback    = $self->models->get_callback;
+
+  my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
+  $self->{report} = $report;
+
+  my @columns     = qw(transdate valudate remote_name remote_account_number remote_bank_code amount invoice_amount invoices currency purpose local_account_number local_bank_code id);
+  my @sortable    = qw(transdate valudate remote_name remote_account_number remote_bank_code amount                                  purpose local_account_number local_bank_code);
+
+  my %column_defs = (
+    transdate             => { sub => sub { $_[0]->transdate_as_date } },
+    valutadate            => { sub => sub { $_[0]->valutadate_as_date } },
+    remote_name           => { },
+    remote_account_number => { },
+    remote_bank_code      => { },
+    amount                => { sub => sub { $_[0]->amount_as_number },
+                               align => 'right' },
+    invoice_amount        => { sub => sub { $_[0]->invoice_amount_as_number },
+                               align => 'right' },
+    invoices              => { sub => sub { $_[0]->linked_invoices } },
+    currency              => { sub => sub { $_[0]->currency->name } },
+    purpose               => { },
+    local_account_number  => { sub => sub { $_[0]->local_bank_account->account_number } },
+    local_bank_code       => { sub => sub { $_[0]->local_bank_account->bank_code } },
+    id                    => {},
+  );
+
+  map { $column_defs{$_}->{text} ||= $::locale->text( $self->models->get_sort_spec->{$_}->{title} ) } keys %column_defs;
+
+  $report->set_options(
+    std_column_visibility => 1,
+    controller_class      => 'BankTransaction',
+    output_format         => 'HTML',
+    top_info_text         => $::locale->text('Bank transactions'),
+    title                 => $::locale->text('Bank transactions'),
+    allow_pdf_export      => 1,
+    allow_csv_export      => 1,
+  );
+  $report->set_columns(%column_defs);
+  $report->set_column_order(@columns);
+  $report->set_export_options(qw(list filter));
+  $report->set_options_from_form;
+  $self->models->disable_pagination if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
+  $self->models->set_report_generator_sort_options(report => $report, sortable_columns => \@sortable);
+
+  my $bank_accounts = SL::DB::Manager::BankAccount->get_all();
+  my $label_sub = sub { t8('#1 - Account number #2, bank code #3, #4', $_[0]->name, $_[0]->account_number, $_[0]->bank_code, $_[0]->bank )};
+
+  $report->set_options(
+    raw_top_info_text     => $self->render('bank_transactions/report_top',    { output => 0 }, BANK_ACCOUNTS => $bank_accounts, label_sub => $label_sub),
+    raw_bottom_info_text  => $self->render('bank_transactions/report_bottom', { output => 0 }),
+  );
+}
+
+sub init_models {
+  my ($self) = @_;
+
+  SL::Controller::Helper::GetModels->new(
+    controller => $self,
+    sorted => {
+      _default => {
+        by    => 'transdate',
+        dir   => 1,
+      },
+      transdate             => t8('Transdate'),
+      remote_name           => t8('Remote name'),
+      amount                => t8('Amount'),
+      invoice_amount        => t8('Assigned'),
+      invoices              => t8('Linked invoices'),
+      valutadate            => t8('Valutadate'),
+      remote_account_number => t8('Remote account number'),
+      remote_bank_code      => t8('Remote bank code'),
+      currency              => t8('Currency'),
+      purpose               => t8('Purpose'),
+      local_account_number  => t8('Local account number'),
+      local_bank_code       => t8('Local bank code'),
+    },
+    with_objects => [ 'local_bank_account', 'currency' ],
+  );
+}
+
+1;
index 1992210..3f65d0e 100644 (file)
@@ -18,6 +18,7 @@ use SL::Controller::CsvImport::Shipto;
 use SL::Controller::CsvImport::Project;
 use SL::Controller::CsvImport::Order;
 use SL::JSON;
+use SL::Controller::CsvImport::BankTransaction;
 use SL::BackgroundJob::CsvImport;
 use SL::System::TaskServer;
 
@@ -268,6 +269,8 @@ sub render_inputs {
             : $self->type eq 'inventories'       ? $::locale->text('CSV import: inventories')
             : $self->type eq 'projects'          ? $::locale->text('CSV import: projects')
             : $self->type eq 'orders'            ? $::locale->text('CSV import: orders')
+            : $self->type eq 'bank_transactions' ? $::locale->text('CSV import: bank transactions')
+            : $self->type eq 'mt940'             ? $::locale->text('CSV import: MT940')
             : die;
 
   if ($self->{type} eq 'customers_vendors' or $self->{type} eq 'orders'  ) {
@@ -289,10 +292,28 @@ sub test_and_import_deferred {
 
   $self->profile_from_form;
 
-  if ($::form->{file}) {
+
+  if ( $::form->{file} && $::form->{FILENAME} =~ /\.940$/ ) {
+    my $mt940_file = SL::SessionFile->new($::form->{FILENAME}, mode => '>');
+    $mt940_file->fh->print($::form->{file});
+    $mt940_file->fh->close;
+
+    my $aqbin = '/usr/bin/aqbanking-cli';
+    my $cmd = "$aqbin --cfgdir=\"users\" import --importer=\"swift\" --profile=\"SWIFT-MT940\" -f " . $mt940_file->file_name . " | $aqbin --cfgdir=\"users\" listtrans --exporter=\"csv\" --profile=\"AqMoney2\" ";
+    my $converted_mt940;
+    open(MT, "$cmd |");
+    $converted_mt940 .=  '"transaction_id";"local_bank_code";"local_account_number";"remote_bank_code";"remote_account_number";"transdate";"valutadate";"amount";"currency";"remote_name";"remote_name_1";"purpose";"purpose1";"purpose2";"purpose3";"purpose4";"purpose5";"purpose6";"purpose7";"purpose8";"purpose9";"purpose10";"purpose11"' . "\n";
+    my $headerline = <MT>;  # discard original header line
+    while (<MT>) {
+      $converted_mt940 .= $_;
+    };
     my $file = SL::SessionFile->new($self->csv_file_name, mode => '>');
-    $file->fh->print($::form->{file});
+    $file->fh->print($converted_mt940);
     $file->fh->close;
+  } elsif ($::form->{file}) {
+      my $file = SL::SessionFile->new($self->csv_file_name, mode => '>');
+      $file->fh->print($::form->{file});
+      $file->fh->close;
   }
 
   my $file = SL::SessionFile->new($self->csv_file_name, mode => '<', encoding => $self->profile->get('charset'));
@@ -618,6 +639,8 @@ sub init_worker {
        : $self->{type} eq 'inventories'       ? SL::Controller::CsvImport::Inventory->new(@args)
        : $self->{type} eq 'projects'          ? SL::Controller::CsvImport::Project->new(@args)
        : $self->{type} eq 'orders'            ? SL::Controller::CsvImport::Order->new(@args)
+       : $self->{type} eq 'bank_transactions' ? SL::Controller::CsvImport::BankTransaction->new(@args)
+       : $self->{type} eq 'mt940'             ? SL::Controller::CsvImport::BankTransaction->new(@args)
        :                                        die "Program logic error";
 }
 
diff --git a/SL/Controller/CsvImport/BankTransaction.pm b/SL/Controller/CsvImport/BankTransaction.pm
new file mode 100644 (file)
index 0000000..ae49616
--- /dev/null
@@ -0,0 +1,136 @@
+package SL::Controller::CsvImport::BankTransaction;
+
+use strict;
+
+use SL::Helper::Csv;
+use SL::Controller::CsvImport::Helper::Consistency;
+use SL::DB::BankTransaction;
+
+use Data::Dumper;
+
+use parent qw(SL::Controller::CsvImport::Base);
+
+use Rose::Object::MakeMethods::Generic
+(
+ 'scalar --get_set_init' => [ qw(table bank_accounts_by) ],
+);
+
+sub init_class {
+  my ($self) = @_;
+  $self->class('SL::DB::BankTransaction');
+}
+
+sub init_bank_accounts_by {
+  my ($self) = @_;
+
+  return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_bank_accounts } } ) } qw(id account_number) };
+}
+
+sub check_objects {
+  my ($self) = @_;
+
+  $self->controller->track_progress(phase => 'building data', progress => 0);
+
+  my $i;
+  my $num_data = scalar @{ $self->controller->data };
+  foreach my $entry (@{ $self->controller->data }) {
+    $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
+
+    $self->check_bank_account($entry);
+    $self->check_currency($entry, take_default => 1);
+
+    $self->join_purposes($entry);
+    #TODO: adde checks für die Variablen
+  } continue {
+    $i++;
+  }
+
+  $self->add_cvar_raw_data_columns;
+}
+
+sub setup_displayable_columns {
+  my ($self) = @_;
+
+  $self->SUPER::setup_displayable_columns;
+
+  $self->add_displayable_columns({ name => 'transaction_id',   description => $::locale->text('Transaction ID') },
+                                 { name => 'local_bank_code',   description => $::locale->text('Own bank code') },
+                                 { name => 'local_account_number',   description => $::locale->text('Own bank account number') },
+                                 { name => 'local_bank_account_id',   description => $::locale->text('ID of own bank account') },
+                                 { name => 'remote_bank_code',   description => $::locale->text('Bank code of the goal/source') },
+                                 { name => 'remote_account_number',   description => $::locale->text('Account number of the goal/source') },
+                                 { name => 'transdate',   description => $::locale->text('Date of transaction') },
+                                 { name => 'valutadate',   description => $::locale->text('Valuta') },
+                                 { name => 'amount',   description => $::locale->text('Amount') },
+                                 { name => 'currency',   description => $::locale->text('Currency') },
+                                 { name => 'currency_id',       description => $::locale->text('Currency (database ID)')          },
+                                 { name => 'remote_name',   description => $::locale->text('Name of the goal/source') },
+                                 { name => 'remote_name_1',   description => $::locale->text('Name of the goal/source') },
+                                 { name => 'purpose',   description => $::locale->text('Purpose') },
+                                );
+}
+
+sub check_bank_account {
+  my ($self, $entry) = @_;
+
+  my $object = $entry->{object};
+
+  # Check whether or not local_bank_account ID is valid.
+  if ($object->local_bank_account_id && !$self->bank_accounts_by->{id}->{ $object->local_bank_account_id }) {
+    push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
+    return 0;
+  }
+
+  # Check whether or not local_bank_account ID, local_account_number and local_bank_code are consistent.
+  if ($object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
+    my $bank_account = $self->bank_accounts_by->{id}->{ $object->local_bank_account_id };
+    if ($bank_account->account_number ne $entry->{raw_data}->{local_account_number}) {
+      push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
+      return 0;
+    }
+    if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
+      push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
+      return 0;
+    }
+
+  }
+
+  # Map account information to ID if given.
+  if (!$object->local_bank_account_id && $entry->{raw_data}->{local_account_number}) {
+    my $bank_account = $self->bank_accounts_by->{account_number}->{ $entry->{raw_data}->{local_account_number} };
+    if (!$bank_account) {
+      push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
+      return 0;
+    }
+    if ($entry->{raw_data}->{local_bank_code} && $entry->{raw_data}->{local_bank_code} ne $bank_account->bank_code) {
+      push @{ $entry->{errors} }, $::locale->text('Error: Invalid local bank account');
+      return 0;
+    }
+
+    $object->local_bank_account_id($bank_account->id);
+  }
+
+  return $object->local_bank_account_id ? 1 : 0;
+}
+
+sub join_purposes {
+  my ($self, $entry) = @_;
+
+  my $object = $entry->{object};
+
+  my $purpose = join('', $entry->{raw_data}->{purpose},
+                        $entry->{raw_data}->{purpose1},
+                        $entry->{raw_data}->{purpose2},
+                        $entry->{raw_data}->{purpose3},
+                        $entry->{raw_data}->{purpose4},
+                        $entry->{raw_data}->{purpose5},
+                        $entry->{raw_data}->{purpose6},
+                        $entry->{raw_data}->{purpose7},
+                        $entry->{raw_data}->{purpose8},
+                        $entry->{raw_data}->{purpose9},
+                        $entry->{raw_data}->{purpose10},
+                        $entry->{raw_data}->{purpose11} );
+  $object->purpose($purpose);
+}
+
+1;
index 9712697..b896d99 100644 (file)
@@ -6,6 +6,8 @@ use English qw(-no_match_vars);
 use List::MoreUtils qw(pairwise any);
 
 use SL::Helper::Csv;
+
+use SL::DB::BankAccount;
 use SL::DB::Customer;
 use SL::DB::Language;
 use SL::DB::PaymentTerm;
@@ -19,7 +21,7 @@ use parent qw(Rose::Object);
 use Rose::Object::MakeMethods::Generic
 (
  scalar                  => [ qw(controller file csv test_run save_with_cascade) ],
- 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by delivery_terms_by all_vc vc_by clone_methods) ],
+ 'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by delivery_terms_by all_bank_accounts all_vc vc_by clone_methods) ],
 );
 
 sub run {
@@ -141,6 +143,12 @@ sub init_all_languages {
   return SL::DB::Manager::Language->get_all;
 }
 
+sub init_all_bank_accounts {
+  my ($self) = @_;
+
+  return SL::DB::Manager::BankAccount->get_all;
+}
+
 sub init_payment_terms_by {
   my ($self) = @_;
 
index 3b03e04..73144db 100644 (file)
@@ -6,6 +6,8 @@ use parent 'SL::Controller::Helper::GetModels::Base';
 use Carp;
 use List::MoreUtils qw(uniq);
 
+use Data::Dumper;
+
 use Rose::Object::MakeMethods::Generic (
   scalar => [ qw(by dir specs form_data) ],
   'scalar --get_set_init' => [ qw(form_params) ],
index ba7bf1d..2c2498b 100644 (file)
@@ -22,6 +22,8 @@ use SL::DB::ProjectType;
 use SL::Helper::Flash;
 use SL::Locale::String;
 
+use Data::Dumper;
+
 use Rose::Object::MakeMethods::Generic
 (
  scalar => [ qw(project linked_records) ],
@@ -41,6 +43,7 @@ sub action_search {
   my %params;
 
   $params{CUSTOM_VARIABLES}  = CVar->get_configs(module => 'Projects');
+
   ($params{CUSTOM_VARIABLES_FILTER_CODE}, $params{CUSTOM_VARIABLES_INCLUSION_CODE})
     = CVar->render_search_options(variables      => $params{CUSTOM_VARIABLES},
                                   include_prefix => 'l_',
diff --git a/SL/Controller/Reconciliation.pm b/SL/Controller/Reconciliation.pm
new file mode 100644 (file)
index 0000000..cd5707b
--- /dev/null
@@ -0,0 +1,585 @@
+package SL::Controller::Reconciliation;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Locale::String;
+use SL::JSON;
+use SL::Controller::Helper::ParseFilter;
+use SL::Helper::Flash;
+
+use SL::DB::BankTransaction;
+use SL::DB::BankAccount;
+use SL::DB::AccTransaction;
+use SL::DB::ReconciliationLink;
+
+use Rose::Object::MakeMethods::Generic (
+  'scalar --get_set_init' => [ qw(cleared BANK_ACCOUNTS) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+__PACKAGE__->run_before('_bank_account');
+
+#
+# actions
+#
+
+sub action_search {
+  my ($self) = @_;
+
+  $self->render('reconciliation/search',
+                 label_sub => sub { t8('#1 - Account number #2, bank code #3, #4',
+                                        $_[0]->name,
+                                        $_[0]->bank,
+                                        $_[0]->account_number,
+                                        $_[0]->bank_code) });
+}
+
+sub action_reconciliation {
+  my ($self) = @_;
+
+  $self->_get_linked_transactions;
+
+  $self->_get_balances;
+
+  $self->render('reconciliation/form',
+                title => t8('Reconciliation'),
+                label_sub => sub { t8('#1 - Account number #2, bank code #3, #4',
+                                        $_[0]->name,
+                                        $_[0]->bank,
+                                        $_[0]->account_number,
+                                        $_[0]->bank_code) });
+}
+
+sub action_load_overview {
+  my ($self) = @_;
+
+  $self->_get_proposals;
+
+  $self->_get_linked_transactions;
+
+  $self->_get_balances;
+
+  my $output = $self->render('reconciliation/tabs/overview', { output => 0 });
+  my %result = ( html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_filter_overview {
+  my ($self) = @_;
+
+  $self->_get_linked_transactions;
+  $self->_get_balances;
+
+  my $output = $self->render('reconciliation/_linked_transactions', { output => 0 });
+  my %result = ( html => $output,
+                 absolut_bt_balance => $::form->format_amount(\%::myconfig, $self->{absolut_bt_balance}, 2),
+                 absolut_bb_balance => $::form->format_amount(\%::myconfig, -1 * $self->{absolut_bb_balance}, 2),
+                 bt_balance => $::form->format_amount(\%::myconfig, $self->{bt_balance}, 2),
+                 bb_balance => $::form->format_amount(\%::myconfig, -1 * $self->{bb_balance}, 2)
+                 );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_update_reconciliation_table {
+  my ($self) = @_;
+
+  my @errors = $self->_get_elements_and_validate();
+
+  my $output = $self->render('reconciliation/assigning_table', { output => 0 },
+                 bt_sum => $::form->format_amount(\%::myconfig, $self->{bt_sum}, 2),
+                 bb_sum => $::form->format_amount(\%::myconfig, -1 * $self->{bb_sum}, 2),
+                 show_button => !@errors
+                 );
+
+  my %result = ( html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_reconciliate {
+  my ($self) = @_;
+
+  #Check elements
+  my @errors = $self->_get_elements_and_validate;
+
+  if (@errors) {
+    unshift(@errors, (t8('Could not reconciliate chosen elements!')));
+    flash('error', @errors);
+    $self->action_reconciliation;
+    return;
+  }
+
+  $self->_reconciliate;
+
+  $self->action_reconciliation;
+}
+
+sub action_delete_reconciliation {
+  my ($self) = @_;
+
+  my $rec_links = SL::DB::Manager::ReconciliationLink->get_all(where => [ rec_group => $::form->{rec_group} ]);
+
+  foreach my $rec_link (@{ $rec_links }) {
+    my $bank_transaction = SL::DB::Manager::BankTransaction->find_by( id           => $rec_link->bank_transaction_id );
+    my $acc_transaction  = SL::DB::Manager::AccTransaction ->find_by( acc_trans_id => $rec_link->acc_trans_id        );
+
+    $bank_transaction->cleared('0');
+    $acc_transaction->cleared('0');
+
+    $bank_transaction->save;
+    $acc_transaction->save;
+
+    $rec_link->delete;
+  }
+
+  $self->_get_linked_transactions;
+  $self->_get_balances;
+
+  my $output = $self->render('reconciliation/_linked_transactions', { output => 0 });
+  my %result = ( html => $output,
+                 absolut_bt_balance => $::form->format_amount(\%::myconfig, $self->{absolut_bt_balance}, 2),
+                 absolut_bb_balance => $::form->format_amount(\%::myconfig, -1 * $self->{absolut_bb_balance}, 2),
+                 bt_balance => $::form->format_amount(\%::myconfig, $self->{bt_balance}, 2),
+                 bb_balance => $::form->format_amount(\%::myconfig, -1 * $self->{bb_balance}, 2)
+                 );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_load_proposals {
+  my ($self) = @_;
+
+  $self->_get_proposals;
+
+  my $output = $self->render('reconciliation/tabs/automatic', { output => 0 });
+  my %result = ( html => $output );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_filter_proposals {
+  my ($self) = @_;
+
+  $self->_get_balances;
+  $self->_get_proposals;
+
+  my $output = $self->render('reconciliation/proposals', { output => 0 });
+  my %result = ( html => $output,
+                 absolut_bt_balance => $::form->format_amount(\%::myconfig, $self->{absolut_bt_balance}, 2),
+                 absolut_bb_balance => $::form->format_amount(\%::myconfig, -1 * $self->{absolut_bb_balance}, 2),
+                 bt_balance => $::form->format_amount(\%::myconfig, $self->{bt_balance}, 2),
+                 bb_balance => $::form->format_amount(\%::myconfig, -1 * $self->{bb_balance}, 2)
+                 );
+
+  $self->render(\to_json(\%result), { type => 'json', process => 0 });
+}
+
+sub action_reconciliate_proposals {
+  my ($self) = @_;
+
+  my $counter = 0;
+
+  foreach my $bt_id ( @{ $::form->{bt_ids} }) {
+    my $rec_group = SL::DB::Manager::ReconciliationLink->get_new_rec_group();
+    my $bank_transaction = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+    $bank_transaction->cleared('1');
+    if ( $bank_transaction->isa('SL::DB::BankTransaction') ) {
+      $bank_transaction->invoice_amount($bank_transaction->amount);
+    }
+    $bank_transaction->save;
+    foreach my $acc_trans_id (@{ $::form->{proposal_list}->{$bt_id}->{BB} }) {
+      SL::DB::ReconciliationLink->new(
+        rec_group => $rec_group,
+        bank_transaction_id => $bt_id,
+        acc_trans_id => $acc_trans_id
+      )->save;
+      my $acc_trans = SL::DB::Manager::AccTransaction->find_by(acc_trans_id => $acc_trans_id);
+      $acc_trans->cleared('1');
+      $acc_trans->save;
+    }
+    $counter++;
+  }
+
+  flash('ok', t8('#1 proposal(s) saved.', $counter));
+
+  $self->action_reconciliation;
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('bank_transaction');
+}
+
+sub _bank_account {
+  my ($self) = @_;
+  $self->{bank_account} = SL::DB::Manager::BankAccount->find_by(id => $::form->{filter}->{"local_bank_account_id:number"});
+}
+
+#
+# helpers
+#
+
+sub _get_proposals {
+  my ($self) = @_;
+
+  $self->_filter_to_where;
+
+  my $bank_transactions = SL::DB::Manager::BankTransaction->get_all(where => [ @{ $self->{bt_where} }, cleared => '0' ]);
+
+  my $check_sum;
+
+  my @proposals;
+
+  foreach my $bt (@{ $bank_transactions }) {
+    $check_sum = $bt->amount;
+    my $proposal;
+    $proposal->{BT} = $bt;
+    $proposal->{BB} = [];
+
+    my $linked_records = SL::DB::Manager::RecordLink->get_all(where => [ from_table => 'bank_transactions', from_id => $bt->id ]);
+    foreach my $linked_record (@{ $linked_records }) {
+      my $invoice;
+      if ($linked_record->to_table eq 'ar') {
+        $invoice = SL::DB::Manager::Invoice->find_by(id => $linked_record->to_id);
+        #find payments
+        my $payments = SL::DB::Manager::AccTransaction->get_all(where => [ trans_id => $invoice->id, chart_link => { like => '%AR_paid%' }, transdate => $bt->transdate ]);
+        foreach my $payment (@{ $payments }) {
+          $check_sum += $payment->amount;
+          push @{ $proposal->{BB} }, $payment;
+        }
+      }
+      if ($linked_record->to_table eq 'ap') {
+        $invoice = SL::DB::Manager::PurchaseInvoice->find_by(id => $linked_record->to_id);
+        #find payments
+        my $payments = SL::DB::Manager::AccTransaction->get_all(where => [ trans_id => $invoice->id, chart_link => { like => '%AP_paid%' }, transdate => $bt->transdate ]);
+        foreach my $payment (@{ $payments }) {
+          $check_sum += $payment->amount;
+          push @{ $proposal->{BB} }, $payment;
+        }
+      }
+    }
+
+    #add proposal if something in acc_trans was found
+    #otherwise try to find another entry in acc_trans and add it
+    if (scalar @{ $proposal->{BB} } and !$check_sum) {
+      push @proposals, $proposal;
+    } elsif (!scalar @{ $proposal->{BB} }) {
+      my $acc_transactions = SL::DB::Manager::AccTransaction->get_all(where => [ @{ $self->{bb_where} },
+                                                                                 amount => -1 * $bt->amount,
+                                                                                 cleared => '0',
+                                                                                 or => [
+                                                                                   and => [ 'ar.customer.account_number' => $bt->remote_account_number,
+                                                                                            'ar.customer.bank_code'      => $bt->remote_bank_code, ],
+                                                                                   and => [ 'ap.vendor.account_number' => $bt->remote_account_number,
+                                                                                            'ap.vendor.bank_code'      => $bt->remote_bank_code, ],
+                                                                                   'gl.storno' => '0' ]],
+                                                                       with_objects => [ 'ar', 'ap', 'ar.customer', 'ap.vendor', 'gl' ]);
+      if (scalar @{ $acc_transactions } == 1) {
+        push @{ $proposal->{BB} }, @{ $acc_transactions }[0];
+        push @proposals, $proposal;
+      }
+    }
+  }
+
+  $self->{PROPOSALS} = \@proposals;
+}
+
+sub _get_elements_and_validate {
+  my ($self) = @_;
+
+  my @errors;
+
+  if ( not defined $::form->{bt_ids} ) {
+    push @errors, t8('No bank account chosen!');
+  }
+
+  if ( not defined $::form->{bb_ids} ) {
+    push @errors, t8('No transaction on chart bank chosen!');
+  }
+
+  if (!@errors) {
+    if (scalar @{ $::form->{bt_ids} } > 1 and scalar @{ $::form->{bb_ids} } > 1) {
+      push @errors, t8('No 1:n or n:1 relation');
+    }
+  }
+
+  my @elements;
+  my ($bt_sum, $bb_sum) = (0,0);
+
+  foreach my $bt_id (@{ $::form->{bt_ids} }) {
+    my $bt = SL::DB::Manager::BankTransaction->find_by(id => $bt_id);
+    $bt->{type} = 'BT';
+    $bt_sum += $bt->amount;
+    push @elements, $bt;
+  }
+
+  foreach my $bb_id (@{ $::form->{bb_ids} }) {
+    my $bb = SL::DB::Manager::AccTransaction->find_by(acc_trans_id => $bb_id);
+    $bb->{type} = 'BB';
+    $bb->{id} = $bb->acc_trans_id;
+    $bb_sum += $bb->amount;
+    push @elements, $bb;
+  }
+
+  if ($::form->round_amount($bt_sum + $bb_sum) != 0) {
+    push @errors, t8('Out of balance!');
+  }
+
+  $self->{ELEMENTS} = \@elements;
+  $self->{bt_sum} = $bt_sum;
+  $self->{bb_sum} = $bb_sum;
+
+  return @errors;
+}
+
+sub _reconciliate {
+  my ($self) = @_;
+
+  #1. Step: Set AccTrans and BankTransactions to 'cleared'
+  foreach my $element (@{ $self->{ELEMENTS} }) {
+    $element->cleared('1');
+    $element->invoice_amount($element->amount) if $element->isa('SL::DB::BankTransaction');
+    $element->save;
+  }
+
+  #2. Step: Insert entry in reconciliation_links
+  my $rec_group = SL::DB::Manager::ReconciliationLink->get_new_rec_group();
+  #There is either a 1:n relation or a n:1 relation
+  if (scalar @{ $::form->{bt_ids} } == 1) {
+    my $bt_id = @{ $::form->{bt_ids} }[0];
+    foreach my $bb_id (@{ $::form->{bb_ids} }) {
+      my $rec_link = SL::DB::ReconciliationLink->new(bank_transaction_id => $bt_id,
+                                                     acc_trans_id        => $bb_id,
+                                                     rec_group           => $rec_group);
+      $rec_link->save;
+    }
+  } else {
+    my $bb_id = @{ $::form->{bb_ids} }[0];
+    foreach my $bt_id (@{ $::form->{bt_ids} }) {
+      my $rec_link = SL::DB::ReconciliationLink->new(bank_transaction_id => $bt_id,
+                                                     acc_trans_id        => $bb_id,
+                                                     rec_group           => $rec_group);
+      $rec_link->save;
+    }
+  }
+}
+
+sub _filter_to_where {
+  my ($self) = @_;
+
+  my %parse_filter = parse_filter($::form->{filter});
+  my %filter = @{ $parse_filter{query} };
+
+  my (@rl_where, @bt_where, @bb_where);
+  @rl_where    = ('bank_transaction.local_bank_account_id' => $filter{local_bank_account_id});
+  @bt_where = (local_bank_account_id => $filter{local_bank_account_id});
+  @bb_where = (chart_id              => $self->{bank_account}->chart_id);
+
+  if ($filter{fromdate} and $filter{todate}) {
+
+    push @rl_where, (or => [ and => [ 'acc_tran.transdate'         => $filter{fromdate},
+                                      'acc_tran.transdate'         => $filter{todate} ],
+                             and => [ 'bank_transaction.transdate' => $filter{fromdate},
+                                      'bank_transaction.transdate' => $filter{todate} ] ] );
+
+    push @bt_where, (transdate                    => $filter{todate} );
+    push @bt_where, (transdate                    => $filter{fromdate} );
+    push @bb_where, (transdate                    => $filter{todate} );
+    push @bb_where, (transdate                    => $filter{fromdate} );
+  }
+
+  if ($filter{fromdate} and not $filter{todate}) {
+    push @rl_where, (or => [ 'acc_tran.transdate'         => $filter{fromdate},
+                             'bank_transaction.transdate' => $filter{fromdate} ] );
+    push @bt_where, (transdate                    => $filter{fromdate} );
+    push @bb_where, (transdate                    => $filter{fromdate} );
+  }
+
+  if ($filter{todate} and not $filter{fromdate}) {
+    push @rl_where, ( or => [ 'acc_tran.transdate'         => $filter{todate} ,
+                              'bank_transaction.transdate' => $filter{todate} ] );
+    push @bt_where, (transdate                    => $filter{todate} );
+    push @bb_where, (transdate                    => $filter{todate} );
+  }
+
+  if ($filter{cleared}) {
+    $filter{cleared} = $filter{cleared} eq 'FALSE' ? '0' : '1';
+    push @rl_where, ('acc_tran.cleared'         => $filter{cleared} );
+
+    push @bt_where, (cleared                    => $filter{cleared} );
+    push @bb_where, (cleared                    => $filter{cleared} );
+  }
+
+  $self->{rl_where} = \@rl_where;
+  $self->{bt_where} = \@bt_where;
+  $self->{bb_where} = \@bb_where;
+}
+
+sub _get_linked_transactions {
+  my ($self) = @_;
+
+  $self->_filter_to_where;
+
+  my (@where, @bt_where, @bb_where);
+  @where    = (@{ $self->{rl_where} });
+  @bt_where = (@{ $self->{bt_where} }, cleared => '0');
+  @bb_where = (@{ $self->{bb_where} }, cleared => '0');
+
+  my @rows;
+
+  my $reconciliation_groups = SL::DB::Manager::ReconciliationLink->get_all(distinct => 1,
+                                                                           select => ['rec_group'],
+                                                                           where => \@where,
+                                                                           with_objects => ['bank_transaction', 'acc_tran']);
+
+  my $fromdate = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{fromdate_date__ge});
+  my $todate   = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{todate_date__le});
+
+  foreach my $rec_group (@{ $reconciliation_groups }) {
+    my $linked_transactions = SL::DB::Manager::ReconciliationLink->get_all(where => [rec_group => $rec_group->rec_group], with_objects => ['bank_transaction', 'acc_tran']);
+    my $line;
+    my $first_transaction = shift @{ $linked_transactions };
+    my $first_bt = $first_transaction->bank_transaction;
+    my $first_bb = $first_transaction->acc_tran;
+
+    if (defined $fromdate) {
+      $first_bt->{class} = 'out_of_balance' if ( $first_bt->transdate lt $fromdate );
+      $first_bb->{class} = 'out_of_balance' if ( $first_bb->transdate lt $fromdate );
+    }
+    if (defined $todate) {
+      $first_bt->{class} = 'out_of_balance' if ( $first_bt->transdate gt $todate );
+      $first_bb->{class} = 'out_of_balance' if ( $first_bb->transdate gt $todate );
+    }
+    $line->{BT} = [ $first_bt ];
+    $line->{BB} = [ $first_bb ];
+    $line->{rec_group} = $first_transaction->rec_group;
+    $line->{type} = 'Link';
+
+    #add the rest of transaction of this group
+    my ($previous_bt_id, $previous_acc_trans_id) = ($first_transaction->bank_transaction_id, $first_transaction->acc_trans_id);
+    foreach my $linked_transaction (@{ $linked_transactions }) {
+      my $bank_transaction = $linked_transaction->bank_transaction;
+      my $acc_transaction  = $linked_transaction->acc_tran;
+      if (defined $fromdate) {
+        $bank_transaction->{class} = 'out_of_balance' if ( $bank_transaction->transdate lt $fromdate );
+        $acc_transaction->{class}  = 'out_of_balance' if ( $acc_transaction->transdate  lt $fromdate );
+      }
+      if (defined $todate) {
+        $bank_transaction->{class} = 'out_of_balance' if ( $bank_transaction->transdate gt $todate );
+        $acc_transaction->{class}  = 'out_of_balance' if ( $acc_transaction->transdate  gt $todate );
+      }
+      if ($bank_transaction->id != $previous_bt_id) {
+        push @{ $line->{BT} }, $bank_transaction;
+      }
+      if ($acc_transaction->acc_trans_id != $previous_acc_trans_id) {
+        push @{ $line->{BB} }, $acc_transaction;
+      }
+    }
+    push @rows, $line;
+  }
+
+  #add non-cleared bank transactions
+  my $bank_transactions = SL::DB::Manager::BankTransaction->get_all(where => \@bt_where);
+  foreach my $bt (@{ $bank_transactions }) {
+    my $line;
+    $line->{BT} = [ $bt ];
+    $line->{type} = 'BT';
+    $line->{id} = $bt->id;
+    push @rows, $line;
+  }
+
+  #add non-cleared bookings on bank
+  my $bookings_on_bank = SL::DB::Manager::AccTransaction->get_all(where => \@bb_where);
+  foreach my $bb (@{ $bookings_on_bank }) {
+    if ($::form->{filter}->{show_stornos} or !$bb->get_transaction->storno) {
+      my $line;
+      $line->{BB} = [ $bb ];
+      $line->{type} = 'BB';
+      $line->{id} = $bb->acc_trans_id;
+      push @rows, $line;
+    }
+  }
+
+  #sort lines
+  @rows = sort sort_by_transdate @rows;
+
+  $self->{LINKED_TRANSACTIONS} = \@rows;
+}
+
+sub sort_by_transdate {
+  if ($a->{BT} and $b->{BT}) {
+    return $a->{BT}[0]->amount <=> $b->{BT}[0]->amount if $a->{BT}[0]->transdate eq $b->{BT}[0]->transdate;
+    return $a->{BT}[0]->transdate cmp $b->{BT}[0]->transdate;
+  }
+  if ($a->{BT}) {
+    return $a->{BT}[0]->amount <=> (-1 * $b->{BB}[0]->amount) if $a->{BT}[0]->transdate eq $b->{BB}[0]->transdate;
+    return $a->{BT}[0]->transdate cmp $b->{BB}[0]->transdate;
+  }
+  if ($b->{BT}) {
+    return (-1 * $a->{BB}[0]->amount) <=> $b->{BT}[0]->amount if $a->{BB}[0]->transdate eq $b->{BT}[0]->transdate;
+    return $a->{BB}[0]->transdate cmp $b->{BT}[0]->transdate;
+  }
+  return (-1 * $a->{BB}[0]->amount) <=> (-1 * $b->{BB}[0]->amount) if $a->{BB}[0]->transdate eq $b->{BB}[0]->transdate;
+  return $a->{BB}[0]->transdate cmp $b->{BB}[0]->transdate;
+}
+
+sub _get_balances {
+  my ($self) = @_;
+
+  $self->_filter_to_where;
+
+  my (@bt_where, @bb_where);
+  @bt_where = @{ $self->{bt_where} };
+  @bb_where = @{ $self->{bb_where} };
+
+  my $bank_transactions = SL::DB::Manager::BankTransaction->get_all(where => \@bt_where );
+  my $payments  = SL::DB::Manager::AccTransaction ->get_all(where => \@bb_where );
+
+  #for absolute balance get all bookings till todate
+  my $todate   = $::locale->parse_date_to_object(\%::myconfig, $::form->{filter}->{todate_date__le});
+
+  my @all_bt_where = (local_bank_account_id => $self->{bank_account}->id);
+  my @all_bb_where = (chart_id              => $self->{bank_account}->chart_id);
+
+  if ($todate) {
+    push @all_bt_where, (transdate => { le => $todate });
+    push @all_bb_where, (transdate => { le => $todate });
+  }
+
+  my $all_bank_transactions = SL::DB::Manager::BankTransaction->get_all(where => \@all_bt_where);
+  my $all_payments  = SL::DB::Manager::AccTransaction ->get_all(where => \@all_bb_where);
+
+  my ($bt_balance, $bb_balance) = (0,0);
+  my ($absolut_bt_balance, $absolut_bb_balance) = (0,0);
+
+  map { $bt_balance += $_->amount } @{ $bank_transactions };
+  map { $bb_balance += $_->amount if ($::form->{filter}->{show_stornos} or !$_->get_transaction->storno) } @{ $payments };
+  map { $absolut_bt_balance += $_->amount } @{ $all_bank_transactions };
+  map { $absolut_bb_balance += $_->amount } @{ $all_payments };
+
+  $self->{bt_balance} = $bt_balance || 0;
+  $self->{bb_balance} = $bb_balance || 0;
+  $self->{absolut_bt_balance} = $absolut_bt_balance || 0;
+  $self->{absolut_bb_balance} = $absolut_bb_balance || 0;
+
+  $self->{difference} = $bt_balance + $bb_balance;
+}
+
+sub init_cleared {
+  [ { title => t8("all"),       value => ''           },
+    { title => t8("cleared"),   value => 'TRUE'       },
+    { title => t8("uncleared"), value => 'FALSE'      }, ]
+}
+
+sub init_BANK_ACCOUNTS {
+  SL::DB::Manager::BankAccount->get_all();
+}
+
+1;
index 053a30a..f25db8e 100644 (file)
@@ -7,6 +7,28 @@ use strict;
 
 use SL::DB::MetaSetup::AccTransaction;
 
+use SL::DB::GLTransaction;
+require SL::DB::Invoice;
+require SL::DB::PurchaseInvoice;
+
+__PACKAGE__->meta->add_relationship(
+  ar => {
+    type         => 'many to one',
+    class        => 'SL::DB::Invoice',
+    column_map   => { trans_id => 'id' },
+  },
+  ap => {
+    type         => 'many to one',
+    class        => 'SL::DB::PurchaseInvoice',
+    column_map   => { trans_id => 'id' },
+  },
+  gl => {
+    type         => 'many to one',
+    class        => 'SL::DB::GLTransaction',
+    column_map   => { trans_id => 'id' },
+  },
+);
+
 __PACKAGE__->meta->initialize;
 
 # Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
@@ -24,6 +46,17 @@ sub record {
   };
 
 };
+
+sub get_transaction {
+  my ($self) = @_;
+
+  my $transaction = SL::DB::Manager::GLTransaction->find_by(id => $self->trans_id);
+  $transaction = SL::DB::Manager::Invoice->find_by(id => $self->trans_id)         if not defined $transaction;
+  $transaction = SL::DB::Manager::PurchaseInvoice->find_by(id => $self->trans_id) if not defined $transaction;
+
+  return $transaction;
+}
+
 1;
 __END__
 
diff --git a/SL/DB/BankTransaction.pm b/SL/DB/BankTransaction.pm
new file mode 100644 (file)
index 0000000..cc6a1ee
--- /dev/null
@@ -0,0 +1,53 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::BankTransaction;
+
+use strict;
+
+use SL::DB::MetaSetup::BankTransaction;
+use SL::DB::Manager::BankTransaction;
+use SL::DB::Helper::LinkedRecords;
+
+__PACKAGE__->meta->initialize;
+
+use SL::DB::Invoice;
+use SL::DB::PurchaseInvoice;
+
+use Data::Dumper;
+
+# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
+#__PACKAGE__->meta->make_manager_class;
+
+sub compare_to {
+  my ($self, $other) = @_;
+
+  return  1 if  $self->transdate && !$other->transdate;
+  return -1 if !$self->transdate &&  $other->transdate;
+
+  my $result = 0;
+  $result    = $self->transdate <=> $other->transdate if $self->transdate;
+  return $result || ($self->id <=> $other->id);
+}
+
+sub linked_invoices {
+  my ($self) = @_;
+
+  #my $record_links = $self->linked_records(direction => 'both');
+
+  my @linked_invoices;
+
+  my $record_links = SL::DB::Manager::RecordLink->get_all(where => [ from_table => 'bank_transactions', from_id => $self->id ]);
+
+  foreach my $record_link (@{ $record_links }) {
+    push @linked_invoices, SL::DB::Manager::Invoice->find_by(id => $record_link->to_id)->invnumber         if $record_link->to_table eq 'ar';
+    push @linked_invoices, SL::DB::Manager::PurchaseInvoice->find_by(id => $record_link->to_id)->invnumber if $record_link->to_table eq 'ap';
+  }
+
+#  $main::lxdebug->message(0, "linked invoices sind: " . Dumper(@linked_invoices));
+#  $main::lxdebug->message(0, "record_links sind: " . Dumper($record_links));
+
+  return [ @linked_invoices ];
+}
+
+1;
index 52c8b6e..67a4d3a 100644 (file)
@@ -28,7 +28,19 @@ sub abbreviation {
   my $abbreviation = $::locale->text('GL Transaction (abbreviation)');
   $abbreviation   .= "(" . $::locale->text('Storno (one letter abbreviation)') . ")" if $self->storno;
   return $abbreviation;
+}
+
+sub link {
+  my ($self) = @_;
+
+  my $html;
+  $html   = SL::Presenter->get->gl_transaction($self, display => 'inline');
+
+  return $html;
+}
 
+sub invnumber {
+  return $_[0]->reference;
 }
 
 1;
index 4e59b92..8783006 100644 (file)
@@ -15,6 +15,7 @@ use SL::DB::AuthUserGroup;
 use SL::DB::BackgroundJob;
 use SL::DB::BackgroundJobHistory;
 use SL::DB::BankAccount;
+use SL::DB::BankTransaction;
 use SL::DB::Bin;
 use SL::DB::Buchungsgruppe;
 use SL::DB::Business;
@@ -80,6 +81,7 @@ use SL::DB::ProjectStatus;
 use SL::DB::ProjectType;
 use SL::DB::PurchaseInvoice;
 use SL::DB::RecordLink;
+use SL::DB::ReconciliationLink;
 use SL::DB::RequirementSpecAcceptanceStatus;
 use SL::DB::RequirementSpecComplexity;
 use SL::DB::RequirementSpecDependency;
index 70e73a4..c29cc0a 100644 (file)
@@ -99,6 +99,7 @@ my %kivitendo_package_names = (
   background_job_histories       => 'background_job_history',
   ap                             => 'purchase_invoice',
   bank_accounts                  => 'bank_account',
+  bank_transactions              => 'bank_transaction',
   buchungsgruppen                => 'buchungsgruppe',
   bin                            => 'bin',
   business                       => 'business',
@@ -160,6 +161,7 @@ my %kivitendo_package_names = (
   project_statuses               => 'project_status',
   project_types                  => 'project_type',
   record_links                   => 'record_link',
+  reconciliation_links           => 'reconciliation_link',
   requirement_spec_acceptance_statuses => 'RequirementSpecAcceptanceStatus',
   requirement_spec_complexities        => 'RequirementSpecComplexity',
   requirement_spec_item_dependencies   => 'RequirementSpecDependency',
index 3e6c65a..3ecc957 100644 (file)
@@ -19,6 +19,8 @@ use SL::DB::Helper::PriceTaxCalculator;
 use SL::DB::Helper::PriceUpdater;
 use SL::DB::Helper::TransNumberGenerator;
 use SL::Locale::String qw(t8);
+use SL::DB::CustomVariable;
+use SL::DB::AccTransaction;
 
 __PACKAGE__->meta->add_relationship(
   invoiceitems => {
@@ -366,6 +368,59 @@ sub customervendor {
   goto &customer;
 }
 
+sub pay_invoice {
+  my ($self, %params) = @_;
+
+  #Mark invoice as paid
+  $self->paid($self->paid+$params{amount});
+  $self->save;
+
+  Common::check_params(\%params, qw(chart_id trans_id amount transdate));
+
+  #account of bank account or cash
+  my $account_bank = SL::DB::Manager::Chart->find_by(id => $params{chart_id});
+
+  #Search the contra account
+  my $acc_trans = SL::DB::Manager::AccTransaction->find_by(trans_id   => $params{trans_id},
+                                                           or => [ chart_link => { like => "%:AR" },
+                                                                   chart_link => { like => "AR:%" },
+                                                                   chart_link => "AR" ]);
+  my $contra_account = SL::DB::Manager::Chart->find_by(id => $acc_trans->chart_id);
+
+  #Two new transfers in acc_trans (for bank account and for contra account)
+  my $new_acc_trans = SL::DB::AccTransaction->new(trans_id   => $params{trans_id},
+                                                  chart_id   => $account_bank->id,
+                                                  chart_link => $account_bank->link,
+                                                  amount     => (-1 * $params{amount}),
+                                                  transdate  => $params{transdate},
+                                                  source     => $params{source},
+                                                  memo       => '',
+                                                  taxkey     => 0,
+                                                  tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+  $new_acc_trans->save;
+  $new_acc_trans = SL::DB::AccTransaction->new(trans_id   => $params{trans_id},
+                                               chart_id   => $contra_account->id,
+                                               chart_link => $contra_account->link,
+                                               amount     => $params{amount},
+                                               transdate  => $params{transdate},
+                                               source     => $params{source},
+                                               memo       => '',
+                                               taxkey     => 0,
+                                               tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+  $new_acc_trans->save;
+}
+
+sub link {
+  my ($self) = @_;
+
+  my $html;
+  $html   = SL::Presenter->get->sales_invoice($self, display => 'inline') if $self->invoice;
+  $html   = SL::Presenter->get->ar_transaction($self, display => 'inline') if !$self->invoice;
+
+  return $html;
+}
+
+>>>>>>> Test: Bank-Commit zusammengefasst
 1;
 
 __END__
diff --git a/SL/DB/Manager/BankTransaction.pm b/SL/DB/Manager/BankTransaction.pm
new file mode 100644 (file)
index 0000000..50a2319
--- /dev/null
@@ -0,0 +1,29 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::BankTransaction;
+
+use strict;
+
+use parent qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Paginated;
+use SL::DB::Helper::Sorted;
+use SL::DB::Helper::Filtered;
+
+sub object_class { 'SL::DB::BankTransaction' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+  return ( default => [ 'transdate', 1 ],
+           columns => { SIMPLE => 'ALL',
+                        local_account_number => 'local_bank_account.account_number',
+                        local_bank_code      => 'local_bank_account.bank_code' }, );
+}
+
+sub default_objects_per_page {
+  40;
+}
+
+1;
index 03c2296..5dc943f 100644 (file)
@@ -20,6 +20,7 @@ sub type_filter {
   return (and => [ invoice => 1, amount  => { lt => 0 }, or => [ storno => 0, storno => undef ] ]) if $type eq 'credit_note';
   return (and => [ invoice => 1, amount  => { lt => 0 },         storno => 1                    ]) if $type =~ m/(?:invoice_)?storno/;
   return (and => [ invoice => 1, amount  => { ge => 0 },         storno => 1                    ]) if $type eq 'credit_note_storno';
+  return (amount => {gt => 'paid'}) if $type eq 'open';
 
   die "Unknown type $type";
 }
index e964f4f..ce673db 100644 (file)
@@ -46,6 +46,6 @@ sub _sort_spec {
          );
 }
 
-sub default_objects_per_page { 40 }
+sub default_objects_per_page { 15 }
 
 1;
diff --git a/SL/DB/Manager/ReconciliationLink.pm b/SL/DB/Manager/ReconciliationLink.pm
new file mode 100644 (file)
index 0000000..8f63a1c
--- /dev/null
@@ -0,0 +1,27 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::Manager::ReconciliationLink;
+
+use strict;
+
+use SL::DBUtils;
+
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
+
+sub object_class { 'SL::DB::ReconciliationLink' }
+
+__PACKAGE__->make_manager_methods;
+
+sub get_new_rec_group {
+  my $class = shift;
+
+  my $query = qq|SELECT max(rec_group) FROM reconciliation_links|;
+
+  my ($max) = selectfirst_array_query($::form, $class->object_class->init_db->dbh, $query);
+
+  return $max + 1;
+}
+
+1;
diff --git a/SL/DB/MetaSetup/BankTransaction.pm b/SL/DB/MetaSetup/BankTransaction.pm
new file mode 100644 (file)
index 0000000..fefe1e5
--- /dev/null
@@ -0,0 +1,43 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::BankTransaction;
+
+use strict;
+
+use base qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('bank_transactions');
+
+__PACKAGE__->meta->columns(
+  amount                => { type => 'numeric', not_null => 1, precision => 5, scale => 15 },
+  cleared               => { type => 'boolean', default => 'false', not_null => 1 },
+  currency_id           => { type => 'integer' },
+  id                    => { type => 'serial', not_null => 1 },
+  invoice_amount        => { type => 'numeric', default => '0', precision => 5, scale => 15 },
+  local_bank_account_id => { type => 'integer', not_null => 1 },
+  purpose               => { type => 'text' },
+  remote_account_number => { type => 'text' },
+  remote_bank_code      => { type => 'text' },
+  remote_name           => { type => 'text' },
+  remote_name_1         => { type => 'text' },
+  transaction_id        => { type => 'integer' },
+  transdate             => { type => 'date', not_null => 1 },
+  valutadate            => { type => 'date', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->foreign_keys(
+  currency => {
+    class       => 'SL::DB::Currency',
+    key_columns => { currency_id => 'id' },
+  },
+
+  local_bank_account => {
+    class       => 'SL::DB::BankAccount',
+    key_columns => { local_bank_account_id => 'id' },
+  },
+);
+
+1;
+;
diff --git a/SL/DB/MetaSetup/ReconciliationLink.pm b/SL/DB/MetaSetup/ReconciliationLink.pm
new file mode 100644 (file)
index 0000000..796d308
--- /dev/null
@@ -0,0 +1,33 @@
+# This file has been auto-generated. Do not modify it; it will be overwritten
+# by rose_auto_create_model.pl automatically.
+package SL::DB::ReconciliationLink;
+
+use strict;
+
+use base qw(SL::DB::Object);
+
+__PACKAGE__->meta->table('reconciliation_links');
+
+__PACKAGE__->meta->columns(
+  acc_trans_id        => { type => 'bigint', not_null => 1 },
+  bank_transaction_id => { type => 'integer', not_null => 1 },
+  id                  => { type => 'integer', not_null => 1, sequence => 'id' },
+  rec_group           => { type => 'integer', not_null => 1 },
+);
+
+__PACKAGE__->meta->primary_key_columns([ 'id' ]);
+
+__PACKAGE__->meta->foreign_keys(
+  acc_tran => {
+    class       => 'SL::DB::AccTransaction',
+    key_columns => { acc_trans_id => 'acc_trans_id' },
+  },
+
+  bank_transaction => {
+    class       => 'SL::DB::BankTransaction',
+    key_columns => { bank_transaction_id => 'id' },
+  },
+);
+
+1;
+;
index 19808eb..78034c7 100644 (file)
@@ -80,6 +80,58 @@ sub abbreviation {
   return t8('Invoice (one letter abbreviation)'). '(' . t8('Storno (one letter abbreviation)') . ')' if $self->storno;
   return t8('Invoice (one letter abbreviation)');
 
+};
+
+sub pay_invoice {
+  my ($self, %params) = @_;
+
+  #Mark invoice as paid
+  $self->paid($self->paid+$params{amount});
+  $self->save;
+
+  Common::check_params(\%params, qw(chart_id trans_id amount transdate));
+
+  #account of bank account or cash
+  my $account_bank = SL::DB::Manager::Chart->find_by(id => $params{chart_id});
+
+  #Search the contra account
+  my $acc_trans = SL::DB::Manager::AccTransaction->find_by(trans_id   => $params{trans_id},
+                                                           or => [ chart_link => { like => "%:AP" },
+                                                                   chart_link => { like => "AP:%" },
+                                                                   chart_link => "AP" ]);
+  my $contra_account = SL::DB::Manager::Chart->find_by(id => $acc_trans->chart_id);
+
+  #Two new transfers in acc_trans (for bank account and for contra account)
+  my $new_acc_trans = SL::DB::AccTransaction->new(trans_id   => $params{trans_id},
+                                                  chart_id   => $account_bank->id,
+                                                  chart_link => $account_bank->link,
+                                                  amount     => $params{amount},
+                                                  transdate  => $params{transdate},
+                                                  source     => $params{source},
+                                                  memo       => '',
+                                                  taxkey     => 0,
+                                                  tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+  $new_acc_trans->save;
+  $new_acc_trans = SL::DB::AccTransaction->new(trans_id   => $params{trans_id},
+                                               chart_id   => $contra_account->id,
+                                               chart_link => $contra_account->link,
+                                               amount     => (-1 * $params{amount}),
+                                               transdate  => $params{transdate},
+                                               source     => $params{source},
+                                               memo       => '',
+                                               taxkey     => 0,
+                                               tax_id     => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+  $new_acc_trans->save;
+}
+
+sub link {
+  my ($self) = @_;
+
+  my $html;
+  $html   = SL::Presenter->get->purchase_invoice($self, display => 'inline') if $self->invoice;
+  $html   = SL::Presenter->get->ap_transaction($self, display => 'inline') if !$self->invoice;
+
+  return $html;
 }
 
 1;
diff --git a/SL/DB/ReconciliationLink.pm b/SL/DB/ReconciliationLink.pm
new file mode 100644 (file)
index 0000000..0632d51
--- /dev/null
@@ -0,0 +1,13 @@
+# This file has been auto-generated only because it didn't exist.
+# Feel free to modify it at will; it will not be overwritten automatically.
+
+package SL::DB::ReconciliationLink;
+
+use strict;
+
+use SL::DB::MetaSetup::ReconciliationLink;
+use SL::DB::Manager::ReconciliationLink;
+
+__PACKAGE__->meta->initialize;
+
+1;
index 07b22c0..77dda78 100644 (file)
@@ -9,7 +9,7 @@ our @EXPORT_OK = qw(render_flash delay_flash);
 
 my %valid_categories = (
   map({$_ => 'info'} qw(information message)),
-  map({$_ => $_}     qw(info error warning)),
+  map({$_ => $_}     qw(info error warning ok)),
 );
 
 #
index 7b83e36..deeed1e 100644 (file)
@@ -23,6 +23,7 @@ use SL::Presenter::RequirementSpecTextBlock;
 use SL::Presenter::SepaExport;
 use SL::Presenter::Text;
 use SL::Presenter::Tag;
+use SL::Presenter::BankAccount;
 
 use Rose::Object::MakeMethods::Generic (
   scalar => [ qw(need_reinit_widgets) ],
diff --git a/SL/Presenter/BankAccount.pm b/SL/Presenter/BankAccount.pm
new file mode 100644 (file)
index 0000000..13a8cb2
--- /dev/null
@@ -0,0 +1,22 @@
+package SL::Presenter::BankAccount;
+
+use strict;
+
+use parent qw(Exporter);
+
+use Exporter qw(import);
+our @EXPORT = qw(account_number bank_code);
+
+use Carp;
+
+sub account_number {
+  my ($self, $bank_account) = @_;
+  return $self->escaped_text($bank_account->account_number);
+}
+
+sub bank_code {
+  my ($self, $bank_account) = @_;
+  return $self->escaped_text($bank_account->bank_code);
+}
+
+1;
index c078450..b18c064 100644 (file)
@@ -5,7 +5,7 @@ use strict;
 use parent qw(Exporter);
 
 use Exporter qw(import);
-our @EXPORT = qw(sales_invoice ar_transaction purchase_invoice ap_transaction);
+our @EXPORT = qw(sales_invoice ar_transaction purchase_invoice ap_transaction gl_transaction);
 
 use Carp;
 
@@ -33,6 +33,12 @@ sub ap_transaction {
   return _is_ir_record($self, $invoice, 'ap', %params);
 }
 
+sub gl_transaction {
+  my ($self, $invoice, %params) = @_;
+
+  return _is_ir_record($self, $invoice, 'gl', %params);
+}
+
 sub _is_ir_record {
   my ($self, $invoice, $controller, %params) = @_;
 
index b46896d..b395e82 100644 (file)
@@ -55,6 +55,8 @@ sub grouped_record_list {
   $output .= _purchase_invoice_list(       $self, $groups{purchase_invoices},        %params) if $groups{purchase_invoices};
   $output .= _ap_transaction_list(         $self, $groups{ap_transactions},          %params) if $groups{ap_transactions};
 
+  $output .= _bank_transactions(           $self, $groups{bank_transactions},        %params) if $groups{bank_transactions};
+
   $output .= _sepa_collection_list(        $self, $groups{sepa_collections},         %params) if $groups{sepa_collections};
   $output .= _sepa_transfer_list(          $self, $groups{sepa_transfers},           %params) if $groups{sepa_transfers};
 
@@ -177,6 +179,7 @@ sub _group_records {
     sepa_collections         => sub { (ref($_[0]) eq 'SL::DB::SepaExportItem')  &&  $_[0]->ar_id                        },
     sepa_transfers           => sub { (ref($_[0]) eq 'SL::DB::SepaExportItem')  &&  $_[0]->ap_id                        },
     gl_transactions          => sub { (ref($_[0]) eq 'SL::DB::GLTransaction')                                           },
+    bank_transactions        => sub { (ref($_[0]) eq 'SL::DB::BankTransaction') &&  $_[0]->id                           },
   );
 
   my %groups;
@@ -429,6 +432,29 @@ sub _ap_transaction_list {
   );
 }
 
+sub _bank_transactions {
+  my ($self, $list, %params) = @_;
+
+  return $self->record_list(
+    $list,
+    title   => $::locale->text('Bank transactions'),
+    type    => 'bank_transactions',
+    columns => [
+      [ $::locale->text('Transdate'),            'transdate'                      ],
+      [ $::locale->text('Local Bank Code'),      sub { $self->bank_code($_[0]->local_bank_account) }  ],
+      [ $::locale->text('Local account number'), sub { $self->account_number($_[0]->local_bank_account) }  ],
+      [ $::locale->text('Remote Bank Code'),     'remote_bank_code' ],
+      [ $::locale->text('Remote account number'),'remote_account_number' ],
+      [ $::locale->text('Valutadate'),           'valutadate' ],
+      [ $::locale->text('Amount'),               'amount' ],
+      [ $::locale->text('Currency'),             sub { $_[0]->currency->name } ],
+      [ $::locale->text('Remote name'),          'remote_name' ],
+      [ $::locale->text('Purpose'),              'purpose' ],
+    ],
+    %params,
+  );
+}
+
 sub _sepa_export_list {
   my ($self, $list, %params) = @_;
 
index 9c73d45..89904d8 100644 (file)
--- a/SL/RC.pm
+++ b/SL/RC.pm
@@ -116,6 +116,21 @@ sub payment_transactions {
     push(@values, conv_date($form->{todate}));
   }
 
+  if($form->{additional_fromdate}) {
+    $query .= qq|  AND ac.transdate >= ? |;
+    push(@values, conv_date($form->{additional_fromdate}));
+  }
+
+  if($form->{additional_todate}){
+    $query .= qq|  AND ac.transdate <= ? |;
+    push(@values, conv_date($form->{additional_todate}));
+  }
+
+  if($form->{filter_amount}){
+    $query .= qq|  AND ac.amount = ? |;
+    push(@values, conv_i($form->{filter_amount}));
+  }
+
   $query .=
     qq|UNION | .
 
@@ -141,6 +156,21 @@ sub payment_transactions {
     push(@values, conv_date($form->{todate}));
   }
 
+  if($form->{additional_fromdate}) {
+    $query .= qq| AND ac.transdate >= ? |;
+    push(@values, conv_date($form->{additional_fromdate}));
+  }
+
+  if($form->{additional_todate}){
+    $query .= qq| AND ac.transdate <= ? |;
+    push(@values, conv_date($form->{additional_todate}));
+  }
+
+  if($form->{filter_amount}){
+    $query .= qq| AND ac.amount = ? |;
+    push(@values, conv_i($form->{filter_amount}));
+  }
+
   $query .=
     qq|UNION | .
 
@@ -166,7 +196,22 @@ sub payment_transactions {
     push(@values, conv_date($form->{todate}));
   }
 
-  $query .= " ORDER BY 3,7,8";
+  if($form->{additional_fromdate}) {
+    $query .= qq| AND ac.transdate >= ? |;
+    push(@values, conv_date($form->{additional_fromdate}));
+  }
+
+  if($form->{additional_todate}){
+    $query .= qq| AND ac.transdate <= ? |;
+    push(@values, conv_date($form->{additional_todate}));
+  }
+
+  if($form->{filter_amount}){
+    $query .= qq| AND ac.amount = ? |;
+    push(@values, conv_i($form->{filter_amount}));
+  }
+
+  $query .= " ORDER BY 3,7,8 LIMIT 6";
 
   $form->{PR} = selectall_hashref_query($form, $dbh, $query, @values);
 
@@ -208,4 +253,33 @@ sub reconcile {
   $main::lxdebug->leave_sub();
 }
 
+sub get_statement_balance {
+  $main::lxdebug->enter_sub();
+
+  my ($self, $myconfig, $form) = @_;
+
+  # connect to database, turn AutoCommit off
+  my $dbh = $form->dbconnect_noauto($myconfig);
+
+  my ($query, @values);
+
+  $query = qq|SELECT sum(amount) FROM acc_trans where chart_id=45 AND cleared='1'|;
+
+  if($form->{fromdate}) {
+    $query .= qq| AND transdate >= ? |;
+    push(@values, conv_date($form->{fromdate}));
+  }
+
+  if($form->{todate}){
+    $query .= qq| AND transdate <= ? |;
+    push(@values, conv_date($form->{todate}));
+  }
+
+  ($form->{statement_balance}) = selectrow_query($form, $dbh, $query, @values);
+
+  $dbh->disconnect;
+
+  $main::lxdebug->leave_sub();
+}
+
 1;
diff --git a/VERSION b/VERSION
index e4604e3..928d224 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.2.1
+3.2.1-rb
index 83cf6be..5b1661b 100644 (file)
@@ -115,6 +115,16 @@ sub load_draft {
   my $form     = $main::form;
   my %myconfig = %main::myconfig;
 
+  # check and store certain form parameters that might have been passed as get, so we can later overwrite the values from the draft
+  # the overwrite happens at the end of this function
+  my @valid_overwrite_vars = qw(remove_draft amount_1 invnumber ordnumber transdate duedate notes datepaid_1 paid_1 callback AP_paid_1 currency);  # reference description
+  my $overwrite_hash;
+  # my @valid_fields;
+  foreach ( @valid_overwrite_vars ) {
+    $overwrite_hash->{$_} = $form->{$_} if exists $form->{$_};  # variant 1
+    # push(@valid_fields, $_) if exists $form->{$_}; # variant 2
+  };
+
   my ($old_form, $id, $description) = Drafts->load(\%myconfig, $form, $form->{id});
 
   if ($old_form) {
@@ -133,6 +143,14 @@ sub load_draft {
   # ungültige Belege. Vielleicht geht es anderen ähnlich jan 19.2.2011
   $form->{invdate} = $form->current_date(\%myconfig); # Aktuelles Rechnungsdatum  ...
   $form->{duedate} = $form->current_date(\%myconfig); # Aktuelles Fälligkeitsdatum  ...
+
+  if ( $overwrite_hash ) {
+    foreach ( keys $overwrite_hash ) {
+      $form->{$_} = $overwrite_hash->{$_};  # variante 1
+    };
+  };
+  # @{$form}{@valid_fields} = @{$overwrite_hash}{@valid_fields};  # variante 2
+
   update();
 
   $main::lxdebug->leave_sub();
index b20f60d..d09f72b 100644 (file)
@@ -39,6 +39,18 @@ a.nomobile {
        background-color:transparent;
        border:none;
 }
+a.green {
+       background-color:#40FF00;
+       border:none;
+}
+a.orange {
+       background-color:#FF8000;
+       border:none;
+}
+a.red {
+       background-color:#FF0000;
+       border:none;
+}
 
 table {
     font-size: 90% !important;
@@ -236,6 +248,16 @@ body.menu {
        color: black;
        vertical-align: top;
 }
+.listrow_error1, .listrow_error:nth-child(even) {
+       background-color: #F6CECE;
+       color: black;
+       vertical-align: top;
+}
+.listrow_error0, .listrow_error:nth-child(odd) {
+       background-color: #F5A9A9;
+       color: black;
+       vertical-align: top;
+}
 .listrowempty {
        background-color: #FFFFFF;
        color: black;
@@ -328,6 +350,13 @@ label {
        margin-bottom: 5px;
        padding: 5px;
 }
+.flash_message_ok {
+       background-color: #ADFFB6;
+       border: 1px solid #007F0F;
+       margin-top: 5px;
+       margin-bottom: 5px;
+       padding: 5px;
+}
 .flash_message_warning {
        background-color: #FFE8C7;
        border: 1px solid #FF6600;
diff --git a/image/bank-building.jpg b/image/bank-building.jpg
new file mode 100644 (file)
index 0000000..1de86ee
Binary files /dev/null and b/image/bank-building.jpg differ
index 95b1357..0d84848 100644 (file)
@@ -9,6 +9,7 @@ namespace("kivi").setupLocale({
 "Add text block":"Textblock erfassen",
 "Additional articles actions":"Aktionen zu zusätzlichen Artikeln",
 "Are you sure?":"Sind Sie sicher?",
+"Assign invoice":"Rechnung zuweisen",
 "Basic settings actions":"Aktionen zu Grundeinstellungen",
 "Cancel":"Abbrechen",
 "Chart picker":"Kontenauswahl",
@@ -19,6 +20,7 @@ namespace("kivi").setupLocale({
 "Create HTML":"HTML erzeugen",
 "Create PDF":"PDF erzeugen",
 "Create a new version":"Eine neue Version anlegen",
+"Create invoice":"Buchung erstellen",
 "Create new quotation/order":"Neues Angebot/neuen Auftrag anlegen",
 "Create new qutoation/order":"Neues Angebot/neuen Auftrag anlegen",
 "Create new version":"Neue Version anlegen",
index 620fe6c..c9bb440 100755 (executable)
@@ -26,6 +26,9 @@ $self->{texts} = {
   '#1 text block(s) front'      => '#1 Textblock/-blöcke hinten',
   '%'                           => '%',
   '(recommended) Insert the used currencies in the system. You can simply change the name of the currencies by editing the textfields above. Do not use a name of a currency that is already in use.' => '(empfohlen) Fügen Sie die verwaisten Währungen in Ihr System ein. Sie können den Namen der Währung einfach ändern, indem Sie die Felder oben bearbeiten. Benutzen Sie keine Namen von Währungen, die Sie bereits benutzen.',
+  '#1 proposal(s) saved.'       => '#1 Vorschläge gespeichert.',
+  '#1, Account number #2, bank code #3' => '#1, Kontonummer #2, BLZ #3',
+  '(Purchase)'                  => '',
   '*/'                          => '*/',
   ', if set'                    => ', falls gesetzt',
   '---please select---'         => '---bitte auswählen---',
@@ -82,8 +85,12 @@ $self->{texts} = {
   'AUTOMATICALLY MATCH BINS'    => 'LAGERPLÄTZE AUTOMATISCH ZUWEISEN',
   'Abort'                       => 'Abbrechen',
   'Abrechnungsnummer'           => 'Abrechnungsnummer',
+  'Absolute BB Balance'         => 'Gesamtsaldo laut Kontoauszug',
+  'Absolute BT Balance'         => 'Gesamtsaldo laut Bankbuchungen',
   'Abteilung'                   => 'Abteilung',
   'Acceptance Statuses'         => 'Abnahmestatus',
+  'Acc Transaction'             => 'Hauptbuch',
+  'Acc transaction'             => 'Hauptbuch Buchung',
   'Access rights'               => 'Zugriffsrechte',
   'Access to clients'           => 'Zugriff auf Mandanten',
   'Account'                     => 'Konto',
@@ -121,6 +128,7 @@ $self->{texts} = {
   'Account for interest'        => 'Konto f&uuml;r Zinsen',
   'Account number'              => 'Kontonummer',
   'Account number not unique!'  => 'Kontonummer bereits vorhanden!',
+  'Account number of the goal/source' => 'Ziel- oder Quellkonto',
   'Account saved!'              => 'Konto gespeichert!',
   'Accounting method'           => 'Versteuerungsart',
   'Accrual'                     => 'Soll-Versteuerung',
@@ -141,6 +149,7 @@ $self->{texts} = {
   'Add Delivery Note'           => 'Lieferschein erfassen',
   'Add Delivery Order'          => 'Lieferschein erfassen',
   'Add Dunning'                 => 'Mahnung erzeugen',
+  'Add Exchangerate'            => '',
   'Add Follow-Up'               => 'Wiedervorlage erstellen',
   'Add Follow-Up for #1'        => 'Wiedervorlage f&uuml;r #1 erstellen',
   'Add General Ledger Transaction' => 'Dialogbuchen',
@@ -178,6 +187,7 @@ $self->{texts} = {
   'Add bank account'            => 'Bankkonto erfassen',
   'Add custom variable'         => 'Benutzerdefinierte Variable erfassen',
   'Add function block'          => 'Funktionsblock hinzufügen',
+  'Add invoices'                => 'Rechnung hinzufügen',
   'Add link: select records to link with' => 'Verknüpfungen hinzufügen: zu verknüpfende Belege auswählen',
   'Add linked record'           => 'Verknüpften Beleg hinzufügen',
   'Add links'                   => 'Verknüpfungen hinzufügen',
@@ -232,6 +242,8 @@ $self->{texts} = {
   'Amended Advance Turnover Tax Return (Nr. 10)' => 'Ist dies eine berichtigte Anmeldung? (Nr. 10/Zeile 15 Steuererklärung)',
   'Amount'                      => 'Betrag',
   'Amount (for verification)'   => 'Betrag (zur Überprüfung)',
+  'Amount BB'                   => 'Betrag Bank',
+  'Amount BT'                   => 'Betrag Buchungen',
   'Amount Due'                  => 'Betrag fällig',
   'Amount and net amount are calculated by kivitendo. "verify_amount" and "verify_netamount" can be used for sanity checks.' => 'Betrag und Nettobetrag werden von kivitendo berechnet. "verify_amount" und "verify_netamount" können für Plausibilitätsprüfungen angegeben werden.',
   'Amount payable'              => 'Noch zu bezahlender Betrag',
@@ -284,6 +296,9 @@ $self->{texts} = {
   'Assign article'              => 'Artikel zuweisen',
   'Assign the following article to all sections' => 'Den folgenden Artikel allen Abschnitten zuweisen',
   'Assignment of articles to sections' => 'Zuweisung von Artikeln zu Abschnitten',
+  'Assign invoice'              => 'Rechnung zuweisen',
+  'Assigned'                    => 'Zugewiesen',
+  'Assigned invoices'           => 'Zugewiesene Rechnungen',
   'Assistant for general ledger corrections' => 'Assistent für die Korrektur von Hauptbucheinträgen',
   'Assume Tax Consultant Data in Tax Computation?' => 'Beraterdaten in UStVA übernehmen?',
   'At least'                    => 'Mindestens',
@@ -308,8 +323,10 @@ $self->{texts} = {
   'Available Prices'            => 'Mögliche Preise',
   'Available qty'               => 'Lagerbestand',
   'BALANCE SHEET'               => 'BILANZ',
+  'BB Balance'                  => 'Saldo Bank',
   'BIC'                         => 'BIC',
   'BOM'                         => 'Stückliste',
+  'BT Balance'                  => 'Saldo Buchungen',
   'BWA'                         => 'BWA',
   'Back'                        => 'Zurück',
   'Back to login'               => 'Zurück zur Anmeldung',
@@ -328,13 +345,19 @@ $self->{texts} = {
   'Bank Code Number'            => 'Bankleitzahl',
   'Bank Connection Tax Office'  => 'Bankverbindung des Finanzamts',
   'Bank Connections'            => 'Bankverbindungen',
+  'Bank Transaction'            => 'Bankkonto',
+  'Bank Transactions'           => 'Bankbewegungen',
   'Bank account'                => 'Bankkonto',
   'Bank accounts'               => 'Bankkonten',
   'Bank code'                   => 'Bankleitzahl',
+  'Bank code of the goal/source' => 'Bankleitzahl von Ziel- oder Quellkonto',
   'Bank collection amount'      => 'Einzugsbetrag',
   'Bank collection payment list for export #1' => 'Bankeinzugszahlungsliste für SEPA-Export #1',
   'Bank collection via SEPA'    => 'Bankeinzug via SEPA',
   'Bank collections via SEPA'   => 'Bankeinzüge via SEPA',
+  'Bank transaction'            => 'Bankbuchung',
+  'Bank transactions'           => 'Bankbewegungen',
+  'Bank transactions MT940'     => 'Elektr. Kontoauszug',
   'Bank transfer amount'        => 'Überweisungssumme',
   'Bank transfer payment list for export #1' => 'Überweisungszahlungsliste für SEPA-Export #1',
   'Bank transfer via SEPA'      => 'Überweisung via SEPA',
@@ -428,6 +451,7 @@ $self->{texts} = {
   'CRM user'                    => 'Admin Benutzer',
   'CSS style for pictures'      => 'CSS Style für Bilder',
   'CSV export -- options'       => 'CSV-Export -- Optionen',
+  'CSV import: bank transactions' => 'CSV Import: Bankbewegungen',
   'CSV import: contacts'        => 'CSV-Import: Ansprechpersonen',
   'CSV import: customers and vendors' => 'CSV-Import: Kunden und Lieferanten',
   'CSV import: inventories'     => 'CSV-Import: Lagerbewegungen/-bestände',
@@ -519,9 +543,12 @@ $self->{texts} = {
   'Choose Outputformat'         => 'Ausgabeformat auswählen...',
   'Choose Vendor'               => 'Händler wählen',
   'Choose a Tax Number'         => 'Bitte eine Steuernummer angeben',
+  'Choose bank account for reconciliation' => 'Wählen Sie das Bankkonto für den Kontenabgleich',
+  'Choose chart'                => 'Konto auswählen',
   'City'                        => 'Stadt',
   'Clear fields'                => 'Felder leeren',
   'Cleared Balance'             => 'abgeschlossen',
+  'Cleared/uncleared only'      => 'Status abgeglichen',
   'Clearing Tax Received (No 71)' => 'Verrechnung des Erstattungsbetrages erwünscht (Zeile 71)',
   'Client'                      => 'Mandant',
   'Client #1'                   => 'Mandant #1',
@@ -581,6 +608,7 @@ $self->{texts} = {
   'Correct taxkey'              => 'Richtiger Steuerschlüssel',
   'Cost'                        => 'Kosten',
   'Costs'                       => 'Kosten',
+  'Could not load GL Transaction' => '',
   'Could not load class #1 (#2): "#3"' => 'Konnte Klasse #1 (#2) nicht laden: "#3"',
   'Could not load class #1, #2' => 'Konnte Klasse #1 nicht laden: "#2"',
   'Could not load employee'     => 'Konnte Benutzer nicht laden',
@@ -588,6 +616,7 @@ $self->{texts} = {
   'Could not load this customer' => 'Konnte diesen Kunden nicht laden',
   'Could not load this vendor'  => 'Konnte diesen Lieferanten nicht laden',
   'Could not print dunning.'    => 'Die Mahnungen konnten nicht gedruckt werden.',
+  'Could not reconciliate chosen elements!' => 'Die gewählten Elemente konnten nicht ausgeglichen werden!',
   'Could not spawn ghostscript.' => 'Die Anwendung "ghostscript" konnte nicht gestartet werden.',
   'Could not spawn the printer command.' => 'Die Druckanwendung konnte nicht gestartet werden.',
   'Could not update prices!'    => 'Preise konnten nicht aktualisiert werden!',
@@ -644,6 +673,7 @@ $self->{texts} = {
   'Create customers and vendors. Edit all vendors. Edit all customers' => 'Kunden und Lieferanten erfassen. Alle Lieferanten bearbeiten. Alle Kunden bearbeiten',
   'Create customers and vendors. Edit all vendors. Edit only customers where salesman equals employee (login)' => 'Kunden und Lieferanten erfassen. Alle Lieferanten bearbeiten. Nur Kunden bearbeiten bei denen der Verkäufer gleich Bearbeiter (login) ist',
   'Create first invoice on'     => 'Erste Rechnung erzeugen am',
+  'Create invoice'              => 'Buchung erstellen',
   'Create invoice?'             => 'Rechnung erstellen?',
   'Create new'                  => 'Neu erfassen',
   'Create new background job'   => 'Neuen Hintergrund-Job anlegen',
@@ -718,6 +748,9 @@ $self->{texts} = {
   'Customer/Vendor (database ID)' => 'Kunde/Lieferant (Datenbank-ID)',
   'Customer/Vendor Name'        => 'Kunde/Lieferant',
   'Customer/Vendor Number'      => 'Kunden-/Lieferantennummer',
+  'Customer/Vendor name'        => 'Kunden-/Lieferantenname',
+  'Customer/Vendor number'      => 'Kunden-/Lieferantennummer',
+  'Customer/Vendor/Remote name' => 'Kunden/Lieferantenname laut Bank',
   'Customername'                => 'Kundenname',
   'Customernumberinit'          => 'Kunden-/Lieferantennummernkreis',
   'Customers'                   => 'Kunden',
@@ -757,6 +790,7 @@ $self->{texts} = {
   'Date Paid'                   => 'Zahlungsdatum',
   'Date and timestamp variables: If the default value equals \'NOW\' then the current date/current timestamp will be used. Otherwise the default value is copied as-is.' => 'Datums- und Uhrzeitvariablen: Wenn der Standardwert \'NOW\' ist, so wird das aktuelle Datum/die aktuelle Uhrzeit eingef&uuml;gt. Andernfalls wird der Standardwert so wie er ist benutzt.',
   'Date missing!'               => 'Datum fehlt!',
+  'Date of transaction'         => 'Buchungsdatum',
   'Date the payment is due in full' => 'Das Datum, bis die Rechnung in voller Höhe bezahlt werden muss',
   'Date the payment is due with discount' => 'Das Datum, bis die Rechnung unter Abzug von Skonto bezahlt werden kann',
   'Datevautomatik'              => 'Datev-Automatik',
@@ -916,7 +950,9 @@ $self->{texts} = {
   'Download picture'            => 'Bild herunterladen',
   'Download sample file'        => 'Beispieldatei herunterladen',
   'Draft for this Letter saved!' => 'Briefentwurf gespeichert!',
+  'Draft from:'                 => 'Entwurf vom:',
   'Draft saved.'                => 'Entwurf gespeichert.',
+  'Draft suggestions'           => 'Entwurfsvorschläge',
   'Drawing'                     => 'Zeichnung',
   'Dropdown Limit'              => 'Auswahllistenbegrenzung',
   'Due'                         => 'Fällig',
@@ -970,6 +1006,7 @@ $self->{texts} = {
   'Edit Employee #1'            => 'Benutzer #1 bearbeiten',
   'Edit Follow-Up'              => 'Wiedervorlage bearbeiten',
   'Edit Follow-Up for #1'       => 'Wiedervorlage f&uuml;r #1 bearbeiten',
+  'Edit GL Transaction with id' => '',
   'Edit General Ledger Transaction' => 'Buchung im Hauptbuch bearbeiten',
   'Edit Group'                  => 'Warengruppe editieren',
   'Edit Language'               => 'Sprache bearbeiten',
@@ -1085,6 +1122,7 @@ $self->{texts} = {
   'Error: Invalid delivery terms' => 'Fehler: Lieferbedingungen ungültig',
   'Error: Invalid department'   => 'Fehler: Abteilung ungültig',
   'Error: Invalid language'     => 'Fehler: Sprache ungültig',
+  'Error: Invalid local bank account' => '',
   'Error: Invalid order for this order item' => 'Fehler: Auftrag für diese Position ungültig',
   'Error: Invalid part'         => 'Fehler: Artikel ungültig',
   'Error: Invalid part type'    => 'Fehler: Artikeltyp ungültig',
@@ -1281,7 +1319,9 @@ $self->{texts} = {
   'I'                           => 'I',
   'IBAN'                        => 'IBAN',
   'ID'                          => 'Buchungsnummer',
+  'ID of own bank account'      => '',
   'ID-Nummer'                   => 'ID-Nummer (intern)',
+  'ID/Acc_ID'                   => '',
   'II'                          => 'II',
   'III'                         => 'III',
   'IV'                          => 'IV',
@@ -1479,6 +1519,7 @@ $self->{texts} = {
   'Link to the following project:' => 'Mit dem folgenden Projekt verknüpfen:',
   'Linked Records'              => 'Verknüpfte Belege',
   'Liquidity projection'        => 'Liquiditätsübersicht',
+  'Linked invoices'             => 'Verknüpfte Rechnungen',
   'List Accounts'               => 'Konten anzeigen',
   'List Languages'              => 'Sprachen anzeigen',
   'List Price'                  => 'Listenpreis',
@@ -1489,6 +1530,7 @@ $self->{texts} = {
   'List export'                 => 'Export anzeigen',
   'List of bank accounts'       => 'Liste der Bankkonten',
   'List of bank collections'    => 'Bankeinzugsliste',
+  'List of bank transactions'   => 'Liste der Bankbewegungen',
   'List of bank transfers'      => 'Überweisungsliste',
   'List of custom variables'    => 'Liste der benutzerdefinierten Variablen',
   'List of database upgrades to be applied:' => 'Liste der noch einzuspielenden Datenbankupgrades:',
@@ -1498,7 +1540,11 @@ $self->{texts} = {
   'Load letter draft'           => 'Briefentwurf laden',
   'Load profile'                => 'Profil laden',
   'Loading...'                  => 'Wird geladen...',
+  'Local Bank Code'             => 'Lokale Bankleitzahl',
   'Local Tax Office Preferences' => 'Angaben zum Finanzamt',
+  'Local account number'        => 'Lokale Kontonummer',
+  'Local bank account'          => '',
+  'Local bank code'             => 'Lokale Bankleitzahl',
   'Lock System'                 => 'System sperren',
   'Lock and unlock installation' => 'Installation sperren/entsperren',
   'Lock file handling failed. Please verify that the directory "#1" is writeable by the webserver.' => 'Die Lockdateibehandlung schlug fehl. Bitte stellen Sie sicher, dass der Webserver das Verzeichnis "#1" beschreiben darf.',
@@ -1590,6 +1636,9 @@ $self->{texts} = {
   'Name in Selected Records'    => 'Name in gewählten Belegen',
   'Negative reductions are possible to model price increases.' => 'Negative Abschläge sind möglich um Aufschläge zu modellieren.',
   'Neither sections nor function blocks have been created yet.' => 'Es wurden bisher weder Abschnitte noch Funktionsblöcke angelegt.',
+  'Name of the goal/source'     => 'Name des Ziel- oder Quellkontos',
+  'National Expenses'           => 'Aufwand Inland',
+  'National Revenues'           => 'Erl&ouml;se Inland',
   'Net Income Statement'        => 'Einnahmenüberschußrechnung',
   'Net amount'                  => 'Nettobetrag',
   'Net amount (for verification)' => 'Nettobetrag (zur Überprüfung)',
@@ -1617,6 +1666,7 @@ $self->{texts} = {
   'Next run at'                 => 'Nächste Ausführung um',
   'No'                          => 'Nein',
   'No %s was found matching the search parameters.' => 'Es wurde kein %s gefunden, auf den die Suchparameter zutreffen.',
+  'No 1:n or n:1 relation'      => 'Keine 1:n oder n:1 Beziehung',
   'No Company Address given'    => 'Keine Firmenadresse hinterlegt!',
   'No Company Name given'       => 'Kein Firmenname hinterlegt!',
   'No Customer was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Endkunde gefunden',
@@ -1625,6 +1675,7 @@ $self->{texts} = {
   'No action defined.'          => 'Keine Aktion definiert.',
   'No articles have been added yet.' => 'Es wurden noch keine Artikel hinzugefügt.',
   'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.',
+  'No bank account chosen!'     => 'Kein Bankkonto ausgewählt!',
   '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 bins have been added to this warehouse yet.' => 'Es wurden zu diesem Lager noch keine Lagerpl&auml;tze angelegt.',
@@ -1638,6 +1689,7 @@ $self->{texts} = {
   'No default currency'         => 'Keine Standardwährung',
   'No delivery term has been created yet.' => 'Es wurden noch keine Lieferbedingungen angelegt',
   'No department has been created yet.' => 'Es wurde noch keine Abteilung erfasst.',
+  'No draft was found.'         => 'Kein Entwurf gefunden.',
   'No dunnings have been selected for printing.' => 'Es wurden keine Mahnungen zum Drucken ausgew&auml;hlt.',
   'No file has been uploaded yet.' => 'Es wurde noch keine Datei hochgeladen.',
   'No function blocks have been created yet.' => 'Es wurden noch keine Funktionsblöcke angelegt.',
@@ -1669,6 +1721,7 @@ $self->{texts} = {
   'No text blocks have been created for this position.' => 'Für diese Position wurden noch keine Textblöcke angelegt.',
   'No text has been entered yet.' => 'Es wurde noch kein Text eingegeben.',
   'No title yet'                => 'Bisher ohne Titel',
+  'No transaction on chart bank chosen!' => '',
   'No transaction selected!'    => 'Keine Transaktion ausgewählt',
   'No transactions yet.'        => 'Bisher keine Buchungen.',
   'No transfers were executed in this export.' => 'In diesem SEPA-Export wurden keine Überweisungen ausgeführt.',
@@ -1775,14 +1828,17 @@ $self->{texts} = {
   'Otherwise the variable is only available for printing.' => 'Andernfalls steht die Variable nur beim Ausdruck zur Verfügung.',
   'Otherwise you can simply check create warehouse and bins and define a name for the warehouse (Bins will be created automatically) and then continue' => 'Andernfalls einfach <b>"Automatisches Zuweisen der Lagerplätze"</b>  anhaken und einen Namen für das Lager vergeben, bzw. per Auswahl auswählen (Lagerplätze werden dann automatisch hinzugefügt) danach auf weiter',
   'Out of balance transaction!' => 'Buchung ist nicht ausgeglichen!',
-  'Out of balance!'             => 'Summen stimmen nicht berein!',
+  'Out of balance!'             => 'Summen stimmen nicht überein!',
   'Output Number Format'        => 'Zahlenformat (Ausgabe)',
   'Outputformat'                => 'Ausgabeformat',
   'Overdue sales quotations and requests for quotations' => 'Überfällige Angebote und Preisanfragen',
   'Override'                    => 'Override',
   'Override invoice language'   => 'Diese Sprache verwenden',
+  'Overview'                    => 'Übersicht',
+  'Own bank account number'     => 'Eigene Kontonummer',
+  'Own bank code'               => 'Eigene Bankleitzahl',
   'Owner of account'            => 'Kontoinhaber',
-  'PAYMENT POSTED'              => 'Rechnung gebucht',
+  'PAYMENT POSTED'              => 'Rechung gebucht',
   'PDF'                         => 'PDF',
   'PDF (OpenDocument/OASIS)'    => 'PDF (OpenDocument/OASIS)',
   'PDF export -- options'       => 'PDF-Export -- Optionen',
@@ -1989,6 +2045,8 @@ $self->{texts} = {
   'Project type'                => 'Projekttyp',
   'Projects'                    => 'Projekte',
   'Projecttransactions'         => 'Projektbuchungen',
+  'Proposal'                    => 'Vorschlag',
+  'Proposals'                   => 'Vorschläge',
   'Prozentual/Absolut'          => 'Prozentual/Absolut',
   'Purchase Delivery Orders'    => 'Einkaufslieferscheine',
   'Purchase Delivery Orders deleteable' => 'Einkaufslieferscheine löschbar',
@@ -2009,6 +2067,7 @@ $self->{texts} = {
   'Purchase price total'        => 'EK-Betrag',
   'Purchasing & Sales'          => 'Einkauf & Verkauf',
   'Purpose'                     => 'Verwendungszweck',
+  'Purpose/Reference'           => 'Verwendungszweck und Referenz',
   'Qty'                         => 'Menge',
   'Qty according to delivery order' => 'Menge laut Lieferschein',
   'Qty equal or less than #1'   => 'Menge gleich oder kleiner als #1',
@@ -2046,13 +2105,17 @@ $self->{texts} = {
   'Ranges of numbers'           => 'Nummernkreise',
   'Re-numbering all sections and function blocks in the order they are currently shown cannot be undone.' => 'Das Neu-Nummerieren aller Abschnitte und Funktionsblöcke kann nicht rückgängig gemacht werden.',
   'Re-run analysis'             => 'Analyse wiederholen',
+  'Really cancel link?'         => 'Verknüpfung wirklich aufheben?',
   'Receipt'                     => 'Zahlungseingang',
   'Receipt posted!'             => 'Beleg gebucht!',
   'Receipt, payment, reconciliation' => 'Zahlungseingang, Zahlungsausgang, Kontenabgleich',
   'Receipts'                    => 'Zahlungseingänge',
   'Receivables'                 => 'Forderungen',
+  'Rechnung/Buchung'            => 'Invoice/transaction',
   'Rechnungsnummer'             => 'Rechnungsnummer',
+  'Reconciliate'                => 'Abgleichen',
   'Reconciliation'              => 'Kontenabgleich',
+  'Reconciliation with bank'    => 'Kontenabgleich mit Bank',
   'Record Vendor Invoice'       => 'Einkaufsrechnung erfassen',
   'Record in'                   => 'Buchen auf',
   'Record number'               => 'Belegnummer',
@@ -2069,6 +2132,12 @@ $self->{texts} = {
   'Remaining Amount'            => 'abzurechnender Betrag',
   'Remaining Net Amount'        => 'abzurechnender Nettobetrag',
   'Remittance information prefix' => 'Verwendungszweckvorbelegung (Präfix)',
+  'Remote Bank Code'            => 'Fremde Bankleitzahl',
+  'Remote Name/Customer/Description' => 'Kunden/Lieferantenname und Beschreibung',
+  'Remote account number'       => 'Fremde Kontonummer',
+  'Remote bank code'            => 'Fremde Bankleitzahl',
+  'Remote name'                 => 'Fremder Kontoinhaber',
+  'Remote name 1'               => 'Fremder Kontoname 1',
   'Removal'                     => 'Entnahme',
   'Removal from Warehouse'      => 'Lagerentnahme',
   'Removal from warehouse'      => 'Entnahme aus Lager',
@@ -2211,7 +2280,9 @@ $self->{texts} = {
   'Save as new'                 => 'als neu speichern',
   'Save document in WebDAV repository' => 'Dokument in WebDAV-Ablage speichern',
   'Save draft'                  => 'Entwurf speichern',
+  'Save invoices'               => 'Rechnungen speichern',
   'Save profile'                => 'Profil speichern',
+  'Save proposals'              => 'Vorschläge speichern',
   'Save settings as'            => 'Einstellungen speichern unter',
   'Saving failed. Error message from the database: #1' => 'Speichern schlug fehl. Fehlermeldung der Datenbank: #1',
   'Saving the file \'%s\' failed. OS error message: %s' => 'Das Speichern der Datei \'%s\' schlug fehl. Fehlermeldung des Betriebssystems: %s',
@@ -2219,6 +2290,7 @@ $self->{texts} = {
   'Search'                      => 'Suchen',
   'Search AP Aging'             => 'Offene Verbindlichkeiten',
   'Search AR Aging'             => 'Offene Forderungen',
+  'Search bank transactions'    => 'Filter für Bankbuchungen',
   'Search contacts'             => 'Ansprechpersonensuche',
   'Search projects'             => 'Projektsuche',
   'Search term'                 => 'Suchbegriff',
@@ -2303,6 +2375,7 @@ $self->{texts} = {
   'Show Bestbefore'             => 'Mindesthaltbarkeit anzeigen',
   'Show Filter'                 => 'Filter zeigen',
   'Show Salesman'               => 'Verkäufer anzeigen',
+  'Show Stornos'                => 'Stornos anzeigen',
   'Show TODO list'              => 'Aufgabenliste anzeigen',
   'Show Transfer via default'   => 'Ein- / Auslagern über Standardlagerplatz anzeigen (zusätzlicher Knopf in Beleg Lieferschein)',
   'Show Value of Goods for Delivery Plan' => 'Warenverkaufswert im Lieferplan anzeigen.',
@@ -2399,6 +2472,7 @@ $self->{texts} = {
   'Subtotal cannot distinguish betweens record types. Only one of the selected record types will be displayed: #1' => 'Zwischensummen können nicht zwischen den einzelnen Belegen unterscheiden, es wird nur "#1" angezeigt',
   'Subtotals per quarter'       => 'Zwischensummen pro Quartal',
   'Such entries cannot be exported into the DATEV format and have to be fixed as well.' => 'Solche Einträge sind aber nicht DATEV-exportiertbar und müssen ebenfalls korrigiert werden.',
+  'Suggested invoice'           => 'Rechnungsvorschlag',
   'Sum Credit'                  => 'Summe Haben',
   'Sum Debit'                   => 'Summe Soll',
   'Sum for'                     => 'Summe für',
@@ -2765,6 +2839,7 @@ $self->{texts} = {
   'There are entries in tax where taxkey is NULL.' => 'In der Datenbank sind Steuern ohne Steuerschlüssel vorhanden (in der Tabelle tax Spalte taxkey).',
   'There are invalid taxnumbers in use.' => 'Es werden ungültige Steuerautomatik-Konten benutzt.',
   'There are invalid transactions in your database.' => 'Sie haben ungültige Buchungen in Ihrer Datenbank.',
+  'There are invoices which could not be payed by bank transaction #1 (Account number: #2, bank code: #3)!' => 'Einige Rechnungen konnten nicht durch die Bankbewegung #1 (Kontonummer: #2, Bankleitzahl: #3) bezahlt werden!',
   'There are no entries in the background job history.' => 'Es gibt keine Einträge im Hintergrund-Job-Verlauf.',
   'There are no items in stock.' => 'Dieser Artikel ist nicht eingelagert.',
   'There are no items on your TODO list at the moment.' => 'Ihre Aufgabenliste enth&auml;lt momentan keine Eintr&auml;ge.',
@@ -2871,6 +2946,7 @@ $self->{texts} = {
   'Transaction'                 => 'Buchung',
   'Transaction %d cancelled.'   => 'Buchung %d erfolgreich storniert.',
   'Transaction Date missing!'   => 'Buchungsdatum fehlt!',
+  'Transaction ID'              => 'ID der Bankbewegung',
   'Transaction ID missing.'     => 'Die Buchungs-ID fehlt.',
   'Transaction deleted!'        => 'Buchung gelöscht!',
   'Transaction description'     => 'Vorgangsbezeichnung',
@@ -2885,6 +2961,8 @@ $self->{texts} = {
   'Transdate is #1'             => 'Belegdatum ist #1',
   'Transdate is after #1'       => 'Belegdatum ist nach #1',
   'Transdate is before #1'      => 'Belegdatum ist vor #1',
+  'Transdate from'              => 'Kontoauszugsdatum von',
+  'Transdate to'                => 'Buchungsdatum bis',
   'Transfer'                    => 'Umlagern',
   'Transfer Quantity'           => 'Umlagermenge',
   'Transfer To Stock'           => 'Lagereingang',
@@ -2986,6 +3064,10 @@ $self->{texts} = {
   'Valid/Obsolete'              => 'Gültig/ungültig',
   'Value'                       => 'Wert',
   'Value of transferred goods'  => 'Verkaufswert der ausgelagerten Waren',
+  'Valuta'                      => 'Valuta',
+  'Valutadate'                  => 'Valutadatum',
+  'Valutadate from'             => 'Valutadatum von',
+  'Valutadate to'               => 'Valutadatum bis',
   'Variable'                    => 'Variable',
   'Variable Description'        => 'Datenfeldbezeichnung',
   'Variable Name'               => 'Datenfeldname (intern)',
@@ -3001,6 +3083,7 @@ $self->{texts} = {
   'Vendor Order Number'         => 'Bestellnummer beim Lieferanten',
   'Vendor deleted!'             => 'Lieferant gelöscht!',
   'Vendor details'              => 'Lieferantendetails',
+  'Vendor filter for AP transaction drafts' => 'Filter für Entwürfe',
   'Vendor missing!'             => 'Lieferant fehlt!',
   'Vendor not on file or locked!' => 'Dieser Lieferant existiert nicht oder ist gesperrt.',
   'Vendor not on file!'         => 'Lieferant ist nicht in der Datenbank!',
@@ -3127,6 +3210,7 @@ $self->{texts} = {
   'accrual'                     => 'Soll-Versteuerung',
   'action= not defined!'        => 'action= nicht definiert!',
   'active'                      => 'aktiv',
+  'all'                         => 'Alle',
   'all entries'                 => 'alle Einträge',
   'and'                         => 'und',
   'ap_aging_list'               => 'liste_offene_verbindlichkeiten',
@@ -3152,6 +3236,7 @@ $self->{texts} = {
   'chart_of_accounts'           => 'kontenuebersicht',
   'choice'                      => 'auswählen',
   'choice part'                 => 'Artikel auswählen',
+  'cleared'                     => 'Abgeglichen',
   'click here to edit cvars'    => 'Klicken Sie hier, um nach benutzerdefinierten Variablen zu suchen',
   'close'                       => 'schließen',
   'closed'                      => 'geschlossen',
@@ -3329,6 +3414,7 @@ $self->{texts} = {
   'transferred in / out'        => 'ein- / ausgelagert',
   'transferred out'             => 'ausgelagert',
   'trial_balance'               => 'susa',
+  'uncleared'                   => 'Nicht abgeglichen',
   'unconfigured'                => 'unkonfiguriert',
   'uncorrect partnumber '       => 'Unbekannte Teilenummer ',
   'use program settings'        => 'benutze Programmeinstellungen',
index e337809..37b4f80 100644 (file)
@@ -386,15 +386,16 @@ action=search
 
 
 [Cash]
-ACCESS=cash
 
 [Cash--Receipt]
+ACCESS=cash
 module=cp.pl
 action=payment
 type=receipt
 vc=customer
 
 [Cash--Payment]
+ACCESS=cash
 module=cp.pl
 action=payment
 type=check
@@ -406,40 +407,62 @@ module=rc.pl
 action=reconciliation
 
 [Cash--Bank collection via SEPA]
+ACCESS=cash
 module=sepa.pl
 action=bank_transfer_add
 vc=customer
 
 [Cash--Bank transfer via SEPA]
+ACCESS=cash
 module=sepa.pl
 action=bank_transfer_add
 vc=vendor
 
+[Cash--Bank transactions MT940]
+ACCESS=bank_transaction
+module=controller.pl
+action=BankTransaction/search
+
+[Cash--Reconciliation with bank]
+ACCESS=bank_transaction
+module=controller.pl
+action=Reconciliation/search
+next_sub=Reconciliation/reconciliation
+
 [Cash--Reports]
 module=menu.pl
 action=acc_menu
 submenu=1
 
 [Cash--Reports--Receipts]
+ACCESS=cash
 module=rp.pl
 action=report
 report=receipts
 
 [Cash--Reports--Payments]
+ACCESS=cash
 module=rp.pl
 action=report
 report=payments
 
 [Cash--Reports--Bank collections via SEPA]
+ACCESS=cash
 module=sepa.pl
 action=bank_transfer_search
 vc=customer
 
 [Cash--Reports--Bank transfers via SEPA]
+ACCESS=cash
 module=sepa.pl
 action=bank_transfer_search
 vc=vendor
 
+[Cash--Reports--Bank transactions]
+ACCESS=bank_transaction
+module=controller.pl
+action=BankTransaction/list_all
+
 [Reports]
 
 [Reports--Chart of Accounts]
@@ -734,6 +757,16 @@ module=menu.pl
 action=acc_menu
 submenu=1
 
+[System--Import CSV--Bank Transactions]
+module=controller.pl
+action=CsvImport/new
+profile.type=bank_transactions
+
+[System--Import CSV--MT940]
+module=controller.pl
+action=CsvImport/new
+profile.type=mt940
+
 [System--Import CSV--Customers and vendors]
 module=controller.pl
 action=CsvImport/new
diff --git a/sql/Pg-upgrade2/automatic_reconciliation.sql b/sql/Pg-upgrade2/automatic_reconciliation.sql
new file mode 100644 (file)
index 0000000..a911e07
--- /dev/null
@@ -0,0 +1,14 @@
+-- @tag: automatic_reconciliation
+-- @description: Erstellt Tabelle reconiliation_links für den automatischen Kontenabgleich.
+-- @depends: release_3_0_0 bank_transactions
+
+CREATE TABLE reconciliation_links (
+  id                      integer NOT NULL DEFAULT nextval('id'),
+  bank_transaction_id     integer NOT NULL,
+  acc_trans_id            bigint  NOT NULL,
+  rec_group               integer NOT NULL,
+
+  PRIMARY KEY (id),
+  FOREIGN KEY (bank_transaction_id)      REFERENCES bank_transactions (id),
+  FOREIGN KEY (acc_trans_id)             REFERENCES acc_trans (acc_trans_id)
+);
diff --git a/sql/Pg-upgrade2/bank_transactions.sql b/sql/Pg-upgrade2/bank_transactions.sql
new file mode 100644 (file)
index 0000000..4b57741
--- /dev/null
@@ -0,0 +1,23 @@
+-- @tag: bank_transactions
+-- @description: Erstellen der Tabelle bank_transactions.
+-- @depends: release_3_0_0 currencies
+
+CREATE TABLE bank_transactions (
+  id SERIAL PRIMARY KEY,
+  transaction_id INTEGER,
+  remote_bank_code TEXT,
+  remote_account_number TEXT,
+  transdate DATE NOT NULL,
+  valutadate DATE NOT NULL,
+  amount numeric(15,5) NOT NULL,
+  remote_name TEXT,
+  remote_name_1 TEXT,
+  purpose TEXT,
+  invoice_amount numeric(15,5) DEFAULT 0,
+  local_bank_account_id INTEGER NOT NULL,
+  currency_id INTEGER NOT NULL,
+  cleared BOOLEAN NOT NULL DEFAULT FALSE,
+
+  FOREIGN KEY (currency_id)            REFERENCES currencies (id),
+  FOREIGN KEY (local_bank_account_id)  REFERENCES bank_accounts (id)
+);
diff --git a/templates/webpages/bank_transactions/_filter.html b/templates/webpages/bank_transactions/_filter.html
new file mode 100644 (file)
index 0000000..a72ff33
--- /dev/null
@@ -0,0 +1,75 @@
+[%- USE T8 %]
+[%- USE L %]
+[%- USE LxERP %]
+[%- USE HTML %]
+<form action='controller.pl' method='post'>
+<div class='filter_toggle'>
+<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Show Filter' | $T8 %]</a>
+  [% SELF.filter_summary | html %]
+</div>
+<div class='filter_toggle' style='display:none'>
+<a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a>
+ <table id='filter_table'>
+    <tr>
+     <th align="right">[% 'Bank account' | $T8 %]</th>
+     <td>[% L.select_tag('filter.local_bank_account_id', BANK_ACCOUNTS, default=filter.local_bank_account_id, title_sub=\label_sub, with_empty=1, style='width:250px') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Transdate from' | $T8 %]</th>
+     <td>[% L.date_tag('filter.transdate:date::ge', filter.transdate_date__ge) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Transdate to' | $T8 %]</th>
+     <td>[% L.date_tag('filter.transdate:date::le', filter.transdate_date__le) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Valutadate from' | $T8 %]</th>
+     <td>[% L.date_tag('filter.valutadate:date::ge', filter.valutadate_date__ge) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Valutadate to' | $T8 %]</th>
+     <td>[% L.date_tag('filter.valutadate:date::le', filter.valutadate_date__le) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Remote name' | $T8 %]</th>
+     <td>[% L.input_tag('filter.remote_name:substr::ilike', filter.remote_name_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Remote account number' | $T8 %]</th>
+     <td>[% L.input_tag('filter.remote_account_number:substr::ilike', filter.remote_account_number_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Remote bank code' | $T8 %]</th>
+     <td>[% L.input_tag('filter.remote_bank_code:substr::ilike', filter.remote_bank_code_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Amount' | $T8 %]</th>
+     <td>[% L.input_tag('filter.amount:number', filter.amount_number, size = 20) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Purpose' | $T8 %]</th>
+     <td>[% L.input_tag('filter.purpose:substr::ilike', filter.purpose_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+ </table>
+
+[% L.hidden_tag('action', 'BankTransaction/dispatch') %]
+[% L.hidden_tag('sort_by', FORM.sort_by) %]
+[% L.hidden_tag('sort_dir', FORM.sort_dir) %]
+[% L.hidden_tag('page', FORM.page) %]
+[% L.input_tag('action_list_all', LxERP.t8('Continue'), type = 'submit', class='submit')%]
+
+
+<a href='#' onClick='javascript:$("#filter_table input").val("");$("#filter_table input[type=checkbox]").prop("checked", 0);'>[% 'Reset' | $T8 %]</a>
+
+</div>
+
+</form>
diff --git a/templates/webpages/bank_transactions/add_list.html b/templates/webpages/bank_transactions/add_list.html
new file mode 100644 (file)
index 0000000..08eaf5b
--- /dev/null
@@ -0,0 +1,37 @@
+[%- USE T8 -%][%- USE HTML -%][%- USE LxERP -%][%- USE P -%][%- USE L -%]
+[%- IF !INVOICES.size %]
+  <p class="message_hint">[% 'No data was found.' | $T8 %]</p>
+[%- ELSE %]
+  <table width="100%">
+   <tr class="listheading">
+    <th>[% L.checkbox_tag('invoices_check_all') %]</th>
+    <th>[%- LxERP.t8("Invoice number") %]</th>
+    <th>[%- LxERP.t8("Amount") %]</th>
+    <th>[%- LxERP.t8("Open amount") %]</th>
+    <th>[%- LxERP.t8("Transdate") %]</th>
+    <th>[%- LxERP.t8("Customer/Vendor number") %]</th>
+    <th>[%- LxERP.t8("Customer/Vendor name") %]</th>
+   </tr>
+
+  [%- FOREACH invoice = INVOICES %]
+   <tr class="listrow[% loop.count % 2 %]">
+    <td>[% L.checkbox_tag('invoice_id[]', value=invoice.id) %]</td>
+    <td>[%- invoice.invnumber %]</td>
+    <td align="right">[%- LxERP.format_amount(invoice.amount, 2) %]</td>
+    <td align="right">[%- LxERP.format_amount(invoice.amount - invoice.paid, 2) %]</td>
+    <td align="right">[%- invoice.transdate_as_date %]</td>
+    <td>[%- invoice.vendor.vendornumber %][%- invoice.customer.customernumber %]</td>
+    <td>[%- invoice.vendor.name %][%- invoice.customer.name %]</td>
+   </tr>
+  [%- END %]
+  </table>
+
+<script type="text/javascript">
+<!--
+$(function() {
+    $('#invoices_check_all').checkall('INPUT[name="invoice_id[]"]');
+    });
+-->
+</script>
+[%- END %]
+
diff --git a/templates/webpages/bank_transactions/assign_invoice.html b/templates/webpages/bank_transactions/assign_invoice.html
new file mode 100644 (file)
index 0000000..f8a6697
--- /dev/null
@@ -0,0 +1,102 @@
+[%- USE HTML %][%- USE L %][%- USE LxERP %][%- USE T8 %]
+
+<form method="post" action="javascript:filter_invoices();">
+  <b>Transaction</b>
+  <table>
+   <tr class="listheading">
+    <td>[%- LxERP.t8("ID") %]:</td>
+    <td>[%- LxERP.t8("Amount") %]:</td>
+    <td>[%- LxERP.t8("Remote bank code") %]:</td>
+    <td>[%- LxERP.t8("Remote account number") %]:</td>
+    <td>[%- LxERP.t8("Remote name") %]:</td>
+    <td>[%- LxERP.t8("Purpose") %]:</td>
+    <td>[%- LxERP.t8("Transdate") %]:</td>
+   </tr>
+
+   <tr class="listrow">
+    <td>[% SELF.transaction.id %]</td>
+    <td>[% LxERP.format_amount(SELF.transaction.amount, 2) %]</td>
+    <td>[% SELF.transaction.remote_bank_code %]</td>
+    <td>[% SELF.transaction.remote_account_number %]</td>
+    <td>[% SELF.transaction.remote_name %]</td>
+    <td>[% SELF.transaction.purpose %]</td>
+    <td>[% SELF.transaction.transdate_as_date %]</td>
+   </tr>
+  </table>
+
+  <b>Filter</b>
+  <table>
+   <tr>
+    <th align="right">[%- LxERP.t8("Invoice number") %]</th>
+    <td>[% L.input_tag('invnumber', '', style=style) %]</td>
+
+    <th align="right">[%- LxERP.t8("Customer/Vendor name") %]</th>
+    <td>[% L.input_tag('vcname', '', style=style) %]</td>
+   </tr>
+
+   <tr>
+    <th align="right">[%- LxERP.t8("Amount") %]</th>
+    <td>[% L.input_tag('amount', '', style=style) %]</td>
+
+    <th align="right">[%- LxERP.t8("Customer/Vendor number") %]</th>
+    <td>[% L.input_tag('vcnumber', '', style=style) %]</td>
+   </tr>
+
+   <tr>
+    <th align="right">[%- LxERP.t8("Transdate from") %]</th>
+    <td>[% L.date_tag('transdatefrom') %]</td>
+
+    <th align="right">[%- LxERP.t8("to (date)") %]</th>
+    <td>[% L.date_tag('transdateto') %]</td>
+   </tr>
+  </table>
+
+  <p>
+   [% L.submit_tag('', LxERP.t8("Search")) %]
+   [% L.button_tag('add_selected_invoices()', LxERP.t8("Add invoices"), id='add_selected_record_links_button') %]
+   <a href="#" onclick="assign_invoice_reset_form();">[%- LxERP.t8("Reset") %]</a>
+   <a href="#" onclick="$('#assign_invoice_window').dialog('close');">[% LxERP.t8("Cancel") %]</a>
+  </p>
+
+  <hr>
+
+  <div id="record_list_filtered_list"></div>
+
+</form>
+
+<script type="text/javascript">
+<!--
+
+function filter_invoices() {
+  var url="controller.pl?action=BankTransaction/ajax_add_list&" + $("#assign_invoice_window form").serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $("#record_list_filtered_list").html(new_data['html']);
+    }
+  });
+}
+
+function add_selected_invoices() {
+  var url="controller.pl?action=BankTransaction/ajax_accept_invoices&" + 'bt_id=[% SELF.transaction.id %]&' + $("#assign_invoice_window form").serialize();
+  $.ajax({
+    url: url,
+    success: function(new_html) {
+      var invoices = document.getElementById('assigned_invoices_[% SELF.transaction.id %]');
+      if (invoices.innerHTML == '') {
+        invoices.innerHTML = new_html;
+      } else {
+        invoices.innerHTML += '<br />' + new_html;
+      }
+      $('#assign_invoice_window').dialog('close');
+    }
+  });
+}
+
+function assign_invoice_reset_form() {
+  $('#assign_invoice_window form input[type=text]').val('');
+}
+
+//-->
+</script>
+
diff --git a/templates/webpages/bank_transactions/create_invoice.html b/templates/webpages/bank_transactions/create_invoice.html
new file mode 100644 (file)
index 0000000..09af08e
--- /dev/null
@@ -0,0 +1,101 @@
+[%- USE HTML %][%- USE L %][%- USE LxERP %][%- USE T8 %]
+
+  <b>Transaction</b>
+  <table>
+   <tr class="listheading">
+    <td>[%- LxERP.t8("ID") %]:</td>
+    <td>[%- LxERP.t8("Amount") %]:</td>
+    <td>[%- LxERP.t8("Date") %]:</td>
+    <td>[%- LxERP.t8("Remote name") %]:</td>
+    <td>[%- LxERP.t8("Purpose") %]:</td>
+    <td>[%- LxERP.t8("Remote bank code") %]:</td>
+    <td>[%- LxERP.t8("Remote account number") %]:</td>
+   </tr>
+
+   <tr class="listrow">
+    <td>[% SELF.transaction.id %]</td>
+    <td>[% LxERP.format_amount(SELF.transaction.amount, 2) %]</td>
+    <td>[% SELF.transaction.valutadate_as_date %]</td>
+    <td>[% SELF.transaction.remote_name %]</td>
+    <td>[% SELF.transaction.purpose %]</td>
+    <td>[% SELF.transaction.remote_bank_code %]</td>
+    <td>[% SELF.transaction.remote_account_number %]</td>
+   </tr>
+  </table>
+
+
+<br>
+[% 'Vendor filter for AP transaction drafts' | $T8 %]:
+
+<form method="post" action="javascript:filter_drafts();">
+[% L.hidden_tag('bt_id', SELF.transaction.id) %]
+  <table>
+   <tr>
+    <th align="right">[%- LxERP.t8("Vendor") %]</th>
+    <td>
+            [%- INCLUDE 'generic/multibox.html'
+                 name          = 'vendor',
+                 select_name   = 'vendor_id',
+                 default       = ALL_VENDORS.size < limit ? vendor_id : vendor_name,
+                 style         = 'width: 250px',
+                 DATA          = ALL_VENDORS,
+                 id_key        = 'id',
+                 label_key     = 'name',
+                 limit         = limit,
+                 show_empty    = 1,
+                 allow_textbox = 1,
+                 class         = 'initial_focus',
+                 onChange      = 'filter_drafts();',
+                 -%]
+    </td>
+   </tr>
+  </table>
+</form>
+
+  <p>
+   <a href="#" onclick="$('#create_invoice_window').dialog('close');">[% LxERP.t8("Cancel") %]</a>
+  </p>
+
+  <hr>
+<div id="drafts">
+[% IF DRAFTS.size %]
+[% 'Draft suggestions' | $T8 %]:
+
+
+  <table>
+   <tr>
+    <th class="listheading">[% 'Description' | $T8 %]</th>
+    <th class="listheading">[% 'Vendor' | $T8 %]</th>
+    <th class="listheading">[% 'Employee' | $T8 %]</th>
+    <th class="listheading">[% 'Draft from:' | $T8 %]</th>
+   </tr>
+
+   [% FOREACH draft = DRAFTS %]
+    <tr class="listrow[% loop.count % 2 %]">
+     <td><a href="[% draft.module %].pl?action=load_draft&id=[% HTML.url(draft.id) %]&amount_1=[% -1 * SELF.transaction.amount_as_number %]&transdate=[% HTML.url(SELF.transaction.transdate_as_date) %]&duedate=[% HTML.url(SELF.transaction.transdate_as_date) %]&datepaid_1=[% HTML.url(SELF.transaction.transdate_as_date) %]&paid_1=[% -1 * SELF.transaction.amount_as_number %]&currency=[% HTML.url(SELF.transaction.currency.name) %]&AP_paid_1=[% HTML.url(SELF.transaction.local_bank_account.chart.accno) %]&remove_draft=0&callback=[% HTML.url(callback) %]">[% HTML.escape(draft.description) %]</a></td>
+     <td>[% HTML.escape(draft.vendor) %]</td>
+     <td>[% HTML.escape(draft.employee.name) %]</td>
+     <td>[% HTML.escape(draft.itime_as_date) %]</td>
+    </tr>
+   [% END %]
+  </table>
+[% ELSE %]
+  <p class="message_hint">[% 'No draft was found.' | $T8 %]</p>
+[% END %]
+</div>
+
+<script type="text/javascript">
+<!--
+
+function filter_drafts() {
+  var url="controller.pl?action=BankTransaction/filter_drafts&" + $("#create_invoice_window form").serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $("#drafts").html(new_data['html']);
+    }
+  });
+}
+//-->
+</script>
+
diff --git a/templates/webpages/bank_transactions/filter_drafts.html b/templates/webpages/bank_transactions/filter_drafts.html
new file mode 100644 (file)
index 0000000..7b2c8de
--- /dev/null
@@ -0,0 +1,23 @@
+[%- USE T8 -%][%- USE HTML -%][%- USE LxERP -%][%- USE P -%][%- USE L -%]
+[%- IF !DRAFTS.size %]
+  <p class="message_hint">[% 'No draft was found.' | $T8 %]</p>
+[%- ELSE %]
+  <table>
+   <tr>
+    <th class="listheading">[% 'Date' | $T8 %]</th>
+    <th class="listheading">[% 'Description' | $T8 %]</th>
+    <th class="listheading">[% 'Employee' | $T8 %]</th>
+    <th class="listheading">[% 'Vendor' | $T8 %]</th>
+   </tr>
+
+   [% FOREACH draft = DRAFTS %]
+    <tr class="listrow[% loop.count % 2 %]">
+     <td>[% HTML.escape(draft.itime_as_date) %]</td>
+     <td><a href="[% draft.module %].pl?action=load_draft&id=[% HTML.url(draft.id) %]&amount_1=[% SELF.transaction.amount_as_number %]&datepaid_1=[% HTML.url(SELF.transaction.transdate_as_date) %]&paid_1=[% SELF.transaction.amount_as_number %]&remove_draft=0&callback=[% HTML.url(callback) %]">[% HTML.escape(draft.description) %]</a></td>
+     <td>[% HTML.escape(draft.employee.name) %]</td>
+     <td>[% HTML.escape(draft.vendor) %]</td>
+    </tr>
+   [% END %]
+  </table>
+[%- END %]
+
diff --git a/templates/webpages/bank_transactions/invoices.html b/templates/webpages/bank_transactions/invoices.html
new file mode 100644 (file)
index 0000000..6ba716c
--- /dev/null
@@ -0,0 +1,8 @@
+[% USE L %]
+[% FOREACH invoice = INVOICES %]
+  <div id="[% bt_id %].[% invoice.id %]">
+    [% L.hidden_tag('invoice_ids.' _ bt_id _'[]', invoice.id) %]
+    [% invoice.invnumber %]
+    <a href=# onclick="delete_invoice([% bt_id %], [% invoice.id %])">x</a>
+  </div>
+[% END %]
diff --git a/templates/webpages/bank_transactions/list.html b/templates/webpages/bank_transactions/list.html
new file mode 100644 (file)
index 0000000..d9d5238
--- /dev/null
@@ -0,0 +1,104 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+<script type="text/javascript" src="js/wz_tooltip.js"></script>
+
+<h1>[% title %]</h1>
+
+[%- INCLUDE 'common/flash.html' %]
+
+<p>[% 'Account number' | $T8 %] [% bank_account.account_number %], [% 'Bank code' | $T8 %] [% bank_account.bank_code %], [% 'Bank' | $T8 %] [% bank_account.bank %]</p>
+<p>
+[% IF FORM.filter.fromdate %] [% 'From' | $T8 %] [% FORM.filter.fromdate %] [% END %]
+[% IF FORM.filter.todate %]   [% 'to (date)' | $T8 %] [% FORM.filter.todate %][% END %]
+</p>
+
+<form id="list_form">
+[% L.hidden_tag('filter.bank_account', FORM.filter.bank_account) %]
+[% L.hidden_tag('filter.fromdate', FORM.filter.fromdate) %]
+[% L.hidden_tag('filter.todate',   FORM.filter.todate) %]
+
+<div class="tabwidget">
+  <ul>
+    <li><a href="#all" onclick="show_invoice_button();">[% 'All transactions' | $T8 %]</a></li>
+    <li><a href="#automatic" onclick="show_proposal_button();">[% 'Proposals' | $T8 %]</a></li>
+  </ul>
+
+  <div id="all">[% PROCESS "bank_transactions/tabs/all.html" %]</div>
+  <div id="automatic">[% PROCESS "bank_transactions/tabs/automatic.html" %]</div>
+</div>
+
+[% L.hidden_tag('action', 'BankTransaction/dispatch') %]
+[% L.submit_tag('action_save_invoices', LxERP.t8('Save invoices')) %]
+[% L.submit_tag('action_save_proposals', LxERP.t8('Save proposals'), style='display: none') %]
+
+</form>
+
+<script type="text/javascript">
+<!--
+
+$(function() {
+  $('#check_all').checkall('INPUT[name^="proposal_ids"]');
+});
+
+$(function() {
+  $('.sort_link').each(function() {
+    var _href = $(this).attr("href");
+    $(this).attr("href", _href + "&filter.fromdate=" + "[% FORM.filter.fromdate %]" + "&filter.todate=" + "[% FORM.filter.todate %]");
+  });
+});
+
+function show_invoice_button () {
+  $("#action_save_proposals").hide();
+  $("#action_save_invoices").show();
+}
+
+function show_proposal_button () {
+  $("#action_save_invoices").hide();
+  $("#action_save_proposals").show();
+}
+
+function assign_invoice(bt_id) {
+  kivi.popup_dialog({
+    url:    'controller.pl?action=BankTransaction/assign_invoice',
+    data:   '&bt_id=' + bt_id,
+    type:   'POST',
+    id:     'assign_invoice_window',
+    dialog: { title: kivi.t8('Assign invoice') }
+  });
+  return true;
+}
+
+function add_invoices(bt_id, prop_id, prop_invnumber) {
+  //prop_id is a proposed invoice_id
+  var number_of_elements = document.getElementsByName(prop_id).length;
+  for( var i = 0; i < number_of_elements; i++ ) {
+    var node = document.getElementsByName(prop_id)[0];
+    node.parentNode.removeChild(node);
+  }
+  UnTip();
+  var invoices = document.getElementById('assigned_invoices_' + bt_id);
+  var div_element = '<div id="' + bt_id + '.' + prop_id + '">';
+  var hidden_element = '<input type="hidden" name="invoice_ids.' + bt_id + '[]" value="' + prop_id + '">' + prop_invnumber;
+  var link_element   = '<a href=# onclick="delete_invoice(' + bt_id + ',' + prop_id + ');">x</a>';
+  var new_html = div_element + hidden_element + link_element + '</div>';
+  invoices.innerHTML += new_html;
+}
+
+function delete_invoice(bt_id, prop_id) {
+  $( "#" + bt_id + "\\." + prop_id ).remove();
+}
+
+function create_invoice(bt_id) {
+  kivi.popup_dialog({
+    url:    'controller.pl?action=BankTransaction/create_invoice',
+    data:   '&bt_id=' + bt_id + "&filter.bank_account=[% FORM.filter.bank_account %]&filter.todate=[% FORM.filter.todate %]&filter.fromdate=[% FORM.filter.fromdate %]",
+    type:   'POST',
+    id:     'create_invoice_window',
+    dialog: { title: kivi.t8('Create invoice') }
+  });
+  return true;
+}
+
+//-->
+</script>
+
diff --git a/templates/webpages/bank_transactions/report_bottom.html b/templates/webpages/bank_transactions/report_bottom.html
new file mode 100644 (file)
index 0000000..8868ff1
--- /dev/null
@@ -0,0 +1,2 @@
+[% USE L %]
+[%- L.paginate_controls(models=SELF.models) %]
diff --git a/templates/webpages/bank_transactions/report_top.html b/templates/webpages/bank_transactions/report_top.html
new file mode 100644 (file)
index 0000000..f6fbce1
--- /dev/null
@@ -0,0 +1,3 @@
+[%- USE L %]
+[%- PROCESS 'bank_transactions/_filter.html' filter=SELF.filter %]
+ <hr>
diff --git a/templates/webpages/bank_transactions/search.html b/templates/webpages/bank_transactions/search.html
new file mode 100644 (file)
index 0000000..adab511
--- /dev/null
@@ -0,0 +1,73 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <form method="post" action="controller.pl">
+
+  <div class="listtop">[% 'Search bank transactions' | $T8 %]</div>
+
+  <p>
+   <table>
+<!--
+    <tr>
+     <th align="right">[% 'Valutadate from' | $T8 %]</th>
+     <td>[% L.date_tag('filter.valutadate:date::ge', filter.valutadate_date__ge) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Valutadate to' | $T8 %]</th>
+     <td>[% L.date_tag('filter.valutadate:date::le', filter.valutadate_date__le) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Remote name' | $T8 %]</th>
+     <td>[% L.input_tag('filter.remote_name:substr::ilike', filter.remote_name_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Remote account number' | $T8 %]</th>
+     <td>[% L.input_tag('filter.remote_account_number:substr::ilike', filter.remote_account_number_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Remote bank code' | $T8 %]</th>
+     <td>[% L.input_tag('filter.remote_bank_code:substr::ilike', filter.remote_bank_code_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Amount' | $T8 %]</th>
+     <td>[% L.input_tag('filter.amount:number', filter.amount_number, size = 20) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Purpose' | $T8 %]</th>
+     <td>[% L.input_tag('filter.purpose:substr::ilike', filter.purpose_substr__ilike, size=60, class='initial_focus') %]</td>
+    </tr>
+    -->
+
+    <tr>
+     <th align="right">[% 'Bank account' | $T8 %]</th>
+     <td>[% L.select_tag('filter.bank_account', BANK_ACCOUNTS, default=bank_acount, title_sub=\label_sub, with_empty=0, style='width:450px') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Transdate from' | $T8 %]</th>
+     <td>[% L.date_tag('filter.fromdate', filter.fromdate) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'to (date)' | $T8 %]</th>
+     <td>[% L.date_tag('filter.todate', filter.todate) %]</td>
+    </tr>
+   </table>
+  </p>
+
+  <hr size="3" noshade>
+
+  [% L.hidden_tag('action', 'BankTransaction/list') %]
+
+  <p>[% L.submit_tag('dummy', LxERP.t8('Continue')) %]</p>
+ </form>
diff --git a/templates/webpages/bank_transactions/tabs/all.html b/templates/webpages/bank_transactions/tabs/all.html
new file mode 100644 (file)
index 0000000..a392bb8
--- /dev/null
@@ -0,0 +1,98 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+ <table id="bt_list">
+  <thead>
+   <tr class="listheading">
+    <th></th>
+    <th></th>
+    <th>[% 'Assigned invoices' | $T8 %]</th>
+    <th>[% IF FORM.sort_by == 'proposal'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=proposal&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Proposal' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=proposal&sort_dir=0" class="sort_link">
+            [% 'Proposal' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% IF FORM.sort_by == 'remote_bank_code'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=remote_bank_code&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Remote bank code' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=remote_bank_code&sort_dir=0" class="sort_link">
+            [% 'Remote bank code' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% IF FORM.sort_by == 'remote_account_number'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=remote_account_number&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Remote account number' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=remote_account_number&sort_dir=0" class="sort_link">
+            [% 'Remote account number' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% IF FORM.sort_by == 'transdate'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=transdate&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Transdate' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=transdate&sort_dir=0" class="sort_link">
+            [% 'Transdate' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% IF FORM.sort_by == 'valutadate'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=valutadate&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Valutadate' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=valutadate&sort_dir=0" class="sort_link">
+            [% 'Valutadate' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% IF FORM.sort_by == 'amount'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=amount&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Amount' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=amount&sort_dir=0" class="sort_link">
+            [% 'Amount' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% 'Assigned' | $T8 %]</th>
+    <th>[% 'Currency' | $T8 %]</th>
+    <th>[% IF FORM.sort_by == 'remote_name'%]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=remote_name&sort_dir=[% 1 - FORM.sort_dir %]" class="sort_link">
+            [% 'Remote name' | $T8 %][% IF FORM.sort_dir == 0 %]<img border="0" src="image/down.png">[% ELSE %]<img border="0" src="image/up.png">[% END %]</a>
+        [% ELSE %]
+          <a href="controller.pl?action=BankTransaction/list&filter.bank_account=[% bank_account.id %]&sort_by=remote_name&sort_dir=0" class="sort_link">
+            [% 'Remote name' | $T8 %]</a>
+        [% END %]
+    </th>
+    <th>[% 'Remote name 1' | $T8 %]</th>
+    <th>[% 'Purpose' | $T8 %]</th>
+   </tr>
+  </thead>
+
+  <tbody>
+   [%- FOREACH bt = BANK_TRANSACTIONS %]
+    <tr class="listrow" id="bt_id_[% bt.id %]">
+     <td><a href=# onclick="assign_invoice('[% bt.id %]'); return false;">[% 'Assign invoice' | $T8 %]</a></td>
+     <td><a href=# onclick="create_invoice('[% bt.id %]'); return false;">[% 'Create invoice' | $T8 %]</a></td>
+     <td id="assigned_invoices_[% bt.id %]"></td>
+     <td>
+      [% FOREACH prop = bt.proposals %]
+        <div name='[% prop.id %]'> <a href=# onclick="add_invoices('[% bt.id %]', '[% prop.id %]', '[% HTML.escape(prop.invnumber) %]');"
+              onmouseover="Tip('<table><tr><th></th><td>[% 'Suggested invoice' | $T8 %][% IF !prop.is_sales %]&nbsp;[% '(Purchase)' | $T8 %][% END %]</td><td>[% 'Bank transaction' | $T8 %]</td></tr><tr><th>[% 'Amount' | $T8 %]</th><td>[% LxERP.format_amount(prop.amount, 2) %]</td><td>[% LxERP.format_amount(bt.amount, 2) %]</td></tr><tr><th>[% 'Customer/Vendor' | $T8 %]</th><td>[% HTML.escape(prop.customer.name) %][% HTML.escape(prop.vendor.name) %]</td><td>[% HTML.escape(bt.remote_name) %]</td></tr><tr><th>[% 'Customer/Vendor Number' | $T8 %]</th><td>[% HTML.escape(prop.customer.customernumber) %][% HTML.escape(prop.vendor.vendornumber) %]</td><td></td></tr><tr><th>[% 'Invoice Date' | $T8 %]</th><td>[% HTML.escape(prop.transdate_as_date) %]</td><td>[% HTML.escape(bt.transdate_as_date) %] ([% HTML.escape(bt.transdate.utc_rd_days - prop.transdate.utc_rd_days) %])</td></tr><tr><th>[% 'Invoice Number' | $T8 %]</th><td>[% HTML.escape(prop.invnumber) %]</td><td>[% HTML.escape(bt.purpose) %]</td></tr></table>')" onmouseout="UnTip()"
+              class=[% IF bt.agreement >= 5 %]"green"[% ELSIF bt.agreement < 5 and bt.agreement >= 3 %]"orange"[% ELSE %]"red"[% END %]>&larr;[% HTML.escape(prop.invnumber)%]</a></div>
+      [% END %]
+     </td>
+     <td>[% HTML.escape(bt.remote_bank_code) %]</td>
+     <td>[% HTML.escape(bt.remote_account_number) %]</td>
+     <td align=right>[% bt.transdate_as_date %]</td>
+     <td align=right>[% bt.valutadate_as_date %]</td>
+     <td align=right>[% bt.amount_as_number %]</td>
+     <td align=right>[% bt.invoice_amount_as_number %]</td>
+     <td align=center>[% HTML.escape(bt.currency.name) %]</td>
+     <td>[% HTML.escape(bt.remote_name) %]</td>
+     <td>[% HTML.escape(bt.remote_name_1) %]</td>
+     <td>[% HTML.escape(bt.purpose) %]</td>
+    </tr>
+    [%- END %]
+  </tbody>
+ </table>
diff --git a/templates/webpages/bank_transactions/tabs/automatic.html b/templates/webpages/bank_transactions/tabs/automatic.html
new file mode 100644 (file)
index 0000000..3ce007a
--- /dev/null
@@ -0,0 +1,51 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+<table>
+  <thead>
+    <tr class="listheading">
+      <th>[% L.checkbox_tag('check_all') %]</th>
+
+      <th>[% 'Typ' | $T8 %]</th>
+      <th>[% 'ID' | $T8 %]</th>
+      <th>[% 'Transdate' | $T8 %]</th>
+      <th>[% 'Amount' | $T8 %]</th>
+      <th>[% 'Purpose/Reference' | $T8 %]</th>
+      <th>[% 'Customer/Vendor/Remote name' | $T8 %]</th>
+    </tr>
+  </thead>
+  [% IF !PROPOSALS.size %]
+    <tbody class="listrow">
+      <td colspan="7"><p class="message_hint">[% 'No data was found.' | $T8 %]</p></td>
+    </tbody>
+  [% ELSE %]
+    [% FOREACH proposal = PROPOSALS %]
+      <tbody class="listrow">
+        <tr>
+          <td rowspan=2 style="valign:center;">
+            [% L.checkbox_tag('proposal_ids[]', checked=0, value=proposal.id) %]
+          </td>
+
+          <td>[% 'Bank transaction' | $T8 %]</td>
+          <td>[% proposal.id %]</td>
+          <td>[% proposal.transdate_as_date %]</td>
+          <td>[% proposal.amount_as_number %]</td>
+          <td>[% HTML.escape(proposal.purpose) %]</td>
+          <td>[% HTML.escape(proposal.remote_name) %]</td>
+        </tr>
+
+      [% FOREACH proposed_invoice = proposal.proposals %]
+        <tr>
+
+          <td>[% 'Rechnung/Buchung' | $T8 %]</td>
+          <td>[% proposed_invoice.id %]</td>
+          <td>[% proposed_invoice.transdate_as_date %]</td>
+          <td>[% proposed_invoice.amount_as_number %]</td>
+          <td>[% HTML.escape(proposed_invoice.invnumber) %]</td>
+          <td>[% HTML.escape(proposed_invoice.customer.name) %][% HTML.escape(proposed_invoice.vendor.name) %]</td>
+        </tr>
+            [% L.hidden_tag("proposed_invoice_" _ proposal.id, proposed_invoice.id) %]
+      [% END %]
+      </tbody>
+    [% END %]
+  [% END %]
+</table>
index cb592ed..4b705cd 100644 (file)
@@ -14,3 +14,4 @@
 [%- PROCESS output title=LxERP.t8('Error')       type='error'   messages = FLASH.error %]
 [%- PROCESS output title=LxERP.t8('Warning')     type='warning' messages = FLASH.warning %]
 [%- PROCESS output title=LxERP.t8('Information') type='info'    messages = FLASH.info %]
+[%- PROCESS output title=LxERP.t8('Ok')          type='ok'      messages = FLASH.ok %]
diff --git a/templates/webpages/csv_import/_form_mt940.html b/templates/webpages/csv_import/_form_mt940.html
new file mode 100644 (file)
index 0000000..652bc98
--- /dev/null
@@ -0,0 +1,9 @@
+
+<script>
+$(function() {
+    $("input[name=sep_char][value='semicolon']").prop('checked', true);
+    $('#settings_numberformat option')[3].selected = true;
+});
+</script>
+
+
index 8535f1a..91b227d 100644 (file)
  [%- INCLUDE 'csv_import/_form_inventories.html' %]
 [%- ELSIF SELF.type == 'orders' %]
  [%- INCLUDE 'csv_import/_form_orders.html' %]
+[%- ELSIF SELF.type == 'mt940' %]
+ [%- INCLUDE 'csv_import/_form_mt940.html' %]
 [%- END %]
 
    <tr>
diff --git a/templates/webpages/reconciliation/_linked_transactions.html b/templates/webpages/reconciliation/_linked_transactions.html
new file mode 100644 (file)
index 0000000..29eaa08
--- /dev/null
@@ -0,0 +1,94 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+[% IF !SELF.LINKED_TRANSACTIONS.size %]
+  <tbody class="listrow">
+    <td colspan="11"><p class="message_hint">[% 'No data was found.' | $T8 %]</p></td>
+  </tbody>
+[% ELSE %]
+  [% FOREACH link = SELF.LINKED_TRANSACTIONS %]
+    [% IF link.type == 'Link' %]
+      <tbody class="listrow">
+        [% FOREACH bt = link.BT %]
+          <tr>
+            [% IF loop.count == 1 %]
+              <td rowspan=[% link.BT.size + link.BB.size %] style="valign:center;">
+                [% L.button_tag('delete_reconciliation(' _ link.rec_group  _ ')', LxERP.t8("X")) %]
+              </td>
+            [% END %]
+
+            <td><img width="16px" height="16px" src="image/bank-building.jpg"></td>
+            <td>[% 'Bank Transaction' | $T8 %]</td>
+            <td>[% HTML.escape(bt.id) %]</td>
+            <td align="right" class="[% HTML.escape(bt.class) %]">[% HTML.escape(bt.transdate_as_date) %]</td>
+            <td align="right" class="[% HTML.escape(bt.class) %]">[% HTML.escape(bt.amount_as_number) %]</td>
+            <td></td>
+            <td>[% HTML.escape(bt.remote_name) %]</td>
+            <td>[% HTML.escape(bt.purpose) %]</td>
+            <td>[% HTML.escape(bt.remote_account_number) %]</td>
+            <td>[% HTML.escape(bt.remote_bank_code) %]</td>
+            <td></td>
+          </tr>
+        [% END %]
+        [% FOREACH bb = link.BB %]
+          <tr>
+            <td><div class="icon16 general-ledger--reports--journal"></div></td>
+            <td>[% 'Acc Transaction' | $T8 %]</td>
+            <td>[% HTML.escape(bb.acc_trans_id) %]</td>
+            <td align="right" class="[% HTML.escape(bb.class) %]">[% HTML.escape(bb.transdate_as_date) %]</td>
+            <td></td>
+            <td align="right" class="[% HTML.escape(bb.class) %]">[% LxERP.format_amount(-1 * bb.amount, 2) %]</td>
+            <td>[% HTML.escape(bb.get_transaction.customer.name) %][% HTML.escape(bb.get_transaction.vendor.name) %][% HTML.escape(bb.get_transaction.description) %]</td>
+            <td>[% bb.get_transaction.link %]</td>
+            <td></td>
+            <td></td>
+            <td>[% HTML.escape(bb.source) %]</td>
+          </tr>
+        [% END %]
+      </tbody>
+    [% ELSE %]
+      <tbody class="listrow_error">
+        [% FOREACH bt = link.BT %]
+          <tr>
+            <td>
+              [%- L.checkbox_tag('bt_ids[]', value=link.id, onchange='update_reconciliation_table();')     %]
+            </td>
+
+            <td><img width="16px" height="16px" src="image/bank-building.jpg"></td>
+            <td>[% 'Bank Transaction' | $T8 %]</td>
+            <td>[% HTML.escape(bt.id) %]</td>
+            <td align="right">[% HTML.escape(bt.transdate_as_date) %]</td>
+            <td align="right">[% HTML.escape(bt.amount_as_number) %]</td>
+            <td></td>
+            <td>[% HTML.escape(bt.remote_name) %]</td>
+            <td>[% HTML.escape(bt.purpose) %]</td>
+            <td>[% HTML.escape(bt.remote_account_number) %]</td>
+            <td>[% HTML.escape(bt.remote_bank_code) %]</td>
+            <td></td>
+          </tr>
+        [% END %]
+        [% FOREACH bb = link.BB %]
+          <tr>
+            <td>
+              [%- L.checkbox_tag('bb_ids[]', value=link.id, onchange='update_reconciliation_table();')     %]
+            </td>
+
+            <td><div class="icon16 general-ledger--reports--journal"></div></td>
+            <td>[% 'Acc Transaction' | $T8 %]</td>
+            <td>[% HTML.escape(bb.acc_trans_id) %]</td>
+            <td align="right">[% HTML.escape(bb.transdate_as_date) %]</td>
+            <td></td>
+            <td align="right">[% LxERP.format_amount(-1 * bb.amount, 2) %]</td>
+            <td>[% HTML.escape(bb.get_transaction.customer.name) %][% HTML.escape(bb.get_transaction.vendor.name) %][% HTML.escape(bb.get_transaction.description) %]</td>
+            <td>[% bb.get_transaction.link %]</td>
+            <td></td>
+            <td></td>
+            <td>[% HTML.escape(bb.source) %]</td>
+          </tr>
+        [% END %]
+      </tbody>
+    [% END %]
+  [% END %]
+[% END %]
diff --git a/templates/webpages/reconciliation/assigning_table.html b/templates/webpages/reconciliation/assigning_table.html
new file mode 100644 (file)
index 0000000..2f6d4de
--- /dev/null
@@ -0,0 +1,39 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+[% IF SELF.ELEMENTS.size %]
+<table>
+  <thead>
+    <tr class="listheading">
+      <th></th>
+      <th>[% 'ID' | $T8 %]</th>
+      <th>[% 'Type' | $T8 %]</th>
+      <th>[% 'Transdate' | $T8 %]</th>
+      <th>[% 'Amount BT' | $T8 %]</th>
+      <th>[% 'Amount BB' | $T8 %]</th>
+    </tr>
+  </thead>
+  [% FOREACH element = SELF.ELEMENTS %]
+    <tbody id="assigned_elements">
+      <tr class="listrow" id='element[% element.type %][% element.id %]'>
+       <td><a href=# onclick="delete_element('[% element.id %]', '[% element.type %]')">x</a></td>
+       <td>[% HTML.escape(element.id) %]</td>
+       <td>[% IF element.type == 'BT' %][% 'Bank transaction' | $T8 %][% ELSE %][% 'Acc transaction' | $T8 %][% END %]</td>
+       <td>[% HTML.escape(element.transdate_as_date) %]</td>
+       <td align="right">[% IF element.type == 'BT' %][% HTML.escape(element.amount_as_number) %][% END %]</td>
+       <td align="right">[% IF element.type == 'BB' %][% LxERP.format_amount(-1 * element.amount, 2) %][% END %]</td>
+      </tr>
+    </tbody>
+  [% END %]
+  <tbody>
+    <tr class="listrow">
+      <td class="listtotal top_border"></td>
+      <td class="listtotal top_border"></td>
+      <td class="listtotal top_border"></td>
+      <td class="listtotal top_border"></td>
+      <td class="listtotal top_border">[% bt_sum %]</td>
+      <td class="listtotal top_border">[% bb_sum %]</td>
+    </tr>
+  </tbody>
+</table>
+  [% IF show_button %][% L.button_tag("submit_with_action('reconciliate')", LxERP.t8("Reconciliate")) %][% END %]
+[% END %]
diff --git a/templates/webpages/reconciliation/form.html b/templates/webpages/reconciliation/form.html
new file mode 100644 (file)
index 0000000..2c1a4db
--- /dev/null
@@ -0,0 +1,123 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+<style type="text/css">
+<!--
+html, body {
+  height: 100%;
+}
+.top_border {
+  border-top: solid black;
+  border-width: 4px;
+}
+.bottom_border {
+  border-bottom: solid black;
+  border-width: 4px;
+}
+#content.html-menu { height: 100%; }
+.out_of_balance {
+  color: #888888;
+}
+-->
+</style>
+
+<div class="listtop">[% title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+<form id="reconciliation_form" method="post" action="controller.pl" style="height:100%">
+  <table>
+    <tr>
+     <th align="right">[% 'Bank account' | $T8 %]</th>
+     <td>[% L.select_tag('filter.local_bank_account_id:number',
+                          SELF.BANK_ACCOUNTS,
+                          default=FORM.filter.local_bank_account_id_number,
+                          title_sub=\label_sub, with_empty=0,
+                          style='width:450px',
+                          onchange='filter_table();') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'From' | $T8 %]</th>
+     <td>[% L.date_tag('filter.fromdate:date::ge', FORM.filter.fromdate_date__ge, onchange='filter_table();') %]</td>
+     <th align="right">[% 'to (date)' | $T8 %]</th>
+     <td>[% L.date_tag('filter.todate:date::le', FORM.filter.todate_date__le, onchange='filter_table();') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Cleared/uncleared only' | $T8 %]</th>
+     <td>[% L.select_tag('filter.cleared:eq_ignore_empty', SELF.cleared, value_key = 'value', title_key = 'title', default=FORM.filter.cleared_eq_ignore_empty, onchange='filter_table();') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Show Stornos' | $T8 %]</th>
+     <td>[% L.checkbox_tag('filter.show_stornos', checked=FORM.filter.show_stornos, value='1', onchange='filter_table();') %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Absolute BT Balance' | $T8 %]</th>
+     <td class='absolut_bt_balance'>[% LxERP.format_amount(SELF.absolut_bt_balance, 2) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'Absolute BB Balance' | $T8 %]</th>
+     <td class='absolut_bb_balance'>[% LxERP.format_amount(-1 * SELF.absolut_bb_balance, 2) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'BT Balance' | $T8 %]</th>
+     <td class='bt_balance'>[% LxERP.format_amount(SELF.bt_balance, 2) %]</td>
+    </tr>
+
+    <tr>
+     <th align="right">[% 'BB Balance' | $T8 %]</th>
+     <td class='bb_balance'>[% LxERP.format_amount(-1 * SELF.bb_balance, 2) %]</td>
+    </tr>
+  </table>
+
+  [% L.submit_tag('submit_filter', LxERP.t8("Filter"), onclick='filter_table();return false;', style='display: none') %]
+
+  <div class="tabwidget" style="height:100%">
+    <ul>
+      <li><a href="#overview" onclick="load_overview();">[% 'Overview' | $T8 %]</a></li>
+      <li><a href="#automatic" onclick="load_proposals();">[% 'Proposals' | $T8 %]</a></li>
+    </ul>
+
+    <div id="overview" style="height:calc(100% - 60px);overflow: auto;">[% PROCESS "reconciliation/tabs/overview.html" %]</div>
+    <div id="automatic" style="height:calc(100% - 60px);overflow: auto;"></div>
+  </div>
+
+</form>
+
+<script type="text/javascript">
+<!--
+
+function load_proposals () {
+  var url="controller.pl?action=Reconciliation/load_proposals&" + $('#reconciliation_form') . serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $('#overview').html('');
+      $('#automatic').html(new_data['html']);
+      $('#set_cleared').html('');
+    }
+  });
+}
+
+function load_overview () {
+  var url="controller.pl?action=Reconciliation/load_overview&" + $('#reconciliation_form') . serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $('#overview').html(new_data['html']);
+      $('#automatic').html('');
+      $('#set_cleared').html('');
+    }
+  });
+}
+
+//-->
+</script>
+
diff --git a/templates/webpages/reconciliation/proposals.html b/templates/webpages/reconciliation/proposals.html
new file mode 100644 (file)
index 0000000..10f9a13
--- /dev/null
@@ -0,0 +1,51 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+[% IF !SELF.PROPOSALS.size %]
+  <tbody class="listrow">
+    <td colspan="11"><p class="message_hint">[% 'No data was found.' | $T8 %]</p></td>
+  </tbody>
+[% ELSE %]
+  [% FOREACH proposal = SELF.PROPOSALS %]
+      <tbody class="listrow">
+        <tr>
+          <td rowspan=[% proposal.BB.size + 1 %] style="valign:center;">
+            [% L.checkbox_tag('bt_ids[]', checked=0, value=proposal.BT.id) %]
+          </td>
+
+          <td><img width="16px" height="16px" src="image/bank-building.jpg"></td>
+          <td>[% 'Bank Transaction' | $T8 %]</td>
+          <td>[% HTML.escape(proposal.BT.id) %]</td>
+          <td align="right">[% HTML.escape(proposal.BT.transdate_as_date) %]</td>
+          <td align="right">[% HTML.escape(proposal.BT.amount_as_number) %]</td>
+          <td></td>
+          <td>[% HTML.escape(proposal.BT.remote_name) %]</td>
+          <td>[% HTML.escape(proposal.BT.purpose) %]</td>
+          <td>[% HTML.escape(proposal.BT.remote_account_number) %]</td>
+          <td>[% HTML.escape(proposal.BT.remote_bank_code) %]</td>
+          <td></td>
+          [% L.hidden_tag('proposal_list.' _ proposal.BT.id _ '.BT', proposal.BT.id) %]
+        </tr>
+
+        [% FOREACH bb = proposal.BB %]
+          <tr>
+            <td><div class="icon16 general-ledger--reports--journal"></div></td>
+            <td>[% 'Acc Transaction' | $T8 %]</td>
+            <td>[% HTML.escape(bb.acc_trans_id) %]</td>
+            <td align="right">[% HTML.escape(bb.transdate_as_date) %]</td>
+            <td></td>
+            <td align="right">[% LxERP.format_amount(-1 * bb.amount, 2) %]</td>
+            <td>[% HTML.escape(bb.get_transaction.customer.name) %][% HTML.escape(bb.get_transaction.vendor.name) %][% HTML.escape(bb.get_transaction.description) %]</td>
+            <td>[% bb.get_transaction.link %]</td>
+            <td></td>
+            <td></td>
+            <td>[% HTML.escape(bb.source) %]</td>
+            [% L.hidden_tag('proposal_list.' _ proposal.BT.id _ '.BB[]', bb.acc_trans_id) %]
+          </tr>
+        [% END %]
+      </tbody>
+  [% END %]
+[% END %]
+
diff --git a/templates/webpages/reconciliation/search.html b/templates/webpages/reconciliation/search.html
new file mode 100644 (file)
index 0000000..b87ed4a
--- /dev/null
@@ -0,0 +1,44 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+<form method="post" action="controller.pl">
+
+<div class="listtop">[% 'Choose bank account for reconciliation' | $T8 %]</div>
+
+<p>
+ <table>
+  <tr>
+   <th align="right">[% 'Bank account' | $T8 %]</th>
+   <td>[% L.select_tag('filter.local_bank_account_id:number', SELF.BANK_ACCOUNTS, default=bank_account, title_sub=\label_sub, with_empty=0, style='width:450px') %]</td>
+  </tr>
+
+  <tr>
+   <th align="right">[% 'From' | $T8 %]</th>
+   <td>[% L.date_tag('filter.fromdate:date::ge') %]</td>
+  </tr>
+
+  <tr>
+   <th align="right">[% 'to (date)' | $T8 %]</th>
+   <td>[% L.date_tag('filter.todate:date::le') %]</td>
+  </tr>
+
+  <tr>
+   <th align="right">[% 'Cleared/uncleared only' | $T8 %]</th>
+   <td>[% L.select_tag('filter.cleared:eq_ignore_empty', SELF.cleared, value_key = 'value', title_key = 'title') %]</td>
+  </tr>
+
+  <tr>
+   <th align="right">[% 'Show Stornos' | $T8 %]</th>
+   <td>[% L.checkbox_tag('filter.show_stornos', value='1') %]</td>
+  </tr>
+ </table>
+</p>
+
+<hr size="3" noshade>
+
+[% L.hidden_tag('action', FORM.next_sub) %]
+
+<p>[% L.submit_tag('dummy', LxERP.t8('Continue')) %]</p>
+</form>
diff --git a/templates/webpages/reconciliation/tabs/automatic.html b/templates/webpages/reconciliation/tabs/automatic.html
new file mode 100644 (file)
index 0000000..6bff8c0
--- /dev/null
@@ -0,0 +1,64 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+<table width=100% id="proposal_table">
+  <thead>
+    <tr class="listheading">
+      <th>[% L.checkbox_tag('proposal_check_all') %]</th>
+
+      <th></th>
+      <th>[% 'Type' | $T8 %]</th>
+      <th>[% 'ID/Acc_ID' | $T8 %]</th>
+      <th>[% 'Transdate' | $T8 %]</th>
+      <th>[% 'Amount BT' | $T8 %]</th>
+      <th>[% 'Amount BB' | $T8 %]</th>
+      <th>[% 'Remote Name/Customer/Description' | $T8 %]</th>
+      <th>[% 'Purpose/Reference' | $T8 %]</th>
+      <th>[% 'Remote account number' | $T8 %]</th>
+      <th>[% 'Remote bank code' | $T8 %]</th>
+      <th>[% 'Source' | $T8 %]</th>
+    </tr>
+  </thead>
+
+  [% PROCESS "reconciliation/proposals.html" %]
+<table>
+
+[% L.button_tag("reconciliate_proposals()", LxERP.t8("Reconciliate")) %]
+
+<script type="text/javascript">
+<!--
+
+function filter_table () {
+  var url="controller.pl?action=Reconciliation/filter_proposals&" + $('#reconciliation_form') . serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $("tbody[class^='listrow']").remove();
+      $("#proposal_table").append(new_data['html']);
+      $(".absolut_bt_balance").html(new_data['absolut_bt_balance']);
+      $(".absolut_bb_balance").html(new_data['absolut_bb_balance']);
+      $(".bt_balance").html(new_data['bt_balance']);
+      $(".bb_balance").html(new_data['bb_balance']);
+    }
+  });
+}
+
+function reconciliate_proposals() {
+  $('<input>').attr({
+    id : "action",
+    name : "action",
+    type : "hidden",
+    value : "Reconciliation/reconciliate_proposals"
+  }).appendTo('#reconciliation_form');
+  $("#reconciliation_form").submit();
+}
+
+$(function() {
+  $('#proposal_check_all').checkall('INPUT[name^="bt_ids"]');
+});
+
+//-->
+</script>
+
diff --git a/templates/webpages/reconciliation/tabs/overview.html b/templates/webpages/reconciliation/tabs/overview.html
new file mode 100644 (file)
index 0000000..68539b3
--- /dev/null
@@ -0,0 +1,121 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+  <div style="height:60%;overflow:auto;">
+    <table width=100% id="link_table">
+      <thead>
+        <tr class="listheading">
+          <th></th>
+
+          <th></th>
+          <th>[% 'Type' | $T8 %]</th>
+          <th>[% 'ID/Acc_ID' | $T8 %]</th>
+          <th>[% 'Transdate' | $T8 %]</th>
+          <th>[% 'Amount BT' | $T8 %]</th>
+          <th>[% 'Amount BB' | $T8 %]</th>
+          <th>[% 'Remote Name/Customer/Description' | $T8 %]</th>
+          <th>[% 'Purpose/Reference' | $T8 %]</th>
+          <th>[% 'Remote account number' | $T8 %]</th>
+          <th>[% 'Remote bank code' | $T8 %]</th>
+          <th>[% 'Source' | $T8 %]</th>
+        </tr>
+      </thead>
+
+      [% PROCESS 'reconciliation/_linked_transactions.html' %]
+
+      <tfoot>
+        <tr class="listtotal">
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="bt_balance top_border" align="right">[% LxERP.format_amount(SELF.bt_balance, 2) %]</td>
+          <td class="bb_balance top_border" align="right">[% LxERP.format_amount(-1 * SELF.bb_balance, 2) %]</td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+          <td class="top_border"></td>
+        </tr>
+      </tfoot>
+    </table>
+  </div>
+
+  <hr size="3" noshade>
+
+  <div id="assigned_elements"></div>
+
+<script type="text/javascript">
+<!--
+
+function filter_table () {
+  var url="controller.pl?action=Reconciliation/filter_overview&" + $('#reconciliation_form') . serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $("tbody[class^='listrow']").remove();
+      $("#assigned_elements").html('');
+      $("#link_table").append(new_data['html']);
+      $(".absolut_bt_balance").html(new_data['absolut_bt_balance']);
+      $(".absolut_bb_balance").html(new_data['absolut_bb_balance']);
+      $(".bt_balance").html(new_data['bt_balance']);
+      $(".bb_balance").html(new_data['bb_balance']);
+    }
+  });
+}
+
+function update_reconciliation_table () {
+  var url="controller.pl?action=Reconciliation/update_reconciliation_table&" + $('#reconciliation_form') . serialize();
+  $.ajax({
+    url: url,
+    success: function(new_data) {
+      $('#assigned_elements').html(new_data['html']);
+    }
+  });
+}
+
+function delete_element (id, type) {
+  if (type == 'BT') {
+    $("input[name^='bt_ids'][value=" + id + "]").attr('checked', false);
+  }
+  if (type == 'BB') {
+    $("input[name^='bb_ids'][value=" + id + "]").attr('checked', false);
+  }
+  update_reconciliation_table();
+}
+
+function submit_with_action(action) {
+  $('<input>').attr({
+    id : "action",
+    name : "action",
+    type : "hidden",
+    value : "Reconciliation/reconciliate"
+  }).appendTo('#reconciliation_form');
+  $("#reconciliation_form").submit();
+}
+
+function delete_reconciliation(rec_group) {
+  var check = confirm('[% 'Really cancel link?' | $T8 %]');
+  if (check == true) {
+    var url="controller.pl?action=Reconciliation/delete_reconciliation&rec_group=" + rec_group + "&" + $('#reconciliation_form') . serialize();
+    $.ajax({
+      url: url,
+      success: function(new_data) {
+        $("tbody[class^='listrow']").remove();
+        $("#assigned_elements").html('');
+        $("#link_table").append(new_data['html']);
+        $(".absolut_bt_balance").html(new_data['absolut_bt_balance']);
+        $(".absolut_bb_balance").html(new_data['absolut_bb_balance']);
+        $(".bt_balance").html(new_data['bt_balance']);
+        $(".bb_balance").html(new_data['bb_balance']);
+      }
+    });
+  }
+}
+
+//-->
+</script>
+
diff --git a/templates/webpages/reconciliation/tabs/set_cleared.html b/templates/webpages/reconciliation/tabs/set_cleared.html
new file mode 100644 (file)
index 0000000..5e5644d
--- /dev/null
@@ -0,0 +1,8 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+<div id="set_cleared">
+Set cleared
+</div>