From: Niclas Zimmermann
Date: Mon, 2 Sep 2013 15:25:16 +0000 (+0200)
Subject: Bankerweiterung - Zwischenstand, erster Entwurf
X-Git-Tag: release-3.3.0beta~100^2~9
X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=6a12a968761127af91e9da8db7579be2836bcaaa;p=kivitendo-erp.git
Bankerweiterung - Zwischenstand, erster Entwurf
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
---
diff --git a/SL/Auth.pm b/SL/Auth.pm
index 8439a2c4f..e202b98e5 100644
--- a/SL/Auth.pm
+++ b/SL/Auth.pm
@@ -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
index 000000000..afa06e9d4
--- /dev/null
+++ b/SL/Controller/BankTransaction.pm
@@ -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;
diff --git a/SL/Controller/CsvImport.pm b/SL/Controller/CsvImport.pm
index 19922109f..3f65d0ef2 100644
--- a/SL/Controller/CsvImport.pm
+++ b/SL/Controller/CsvImport.pm
@@ -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 = ; # discard original header line
+ while () {
+ $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
index 000000000..ae496169c
--- /dev/null
+++ b/SL/Controller/CsvImport/BankTransaction.pm
@@ -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;
diff --git a/SL/Controller/CsvImport/Base.pm b/SL/Controller/CsvImport/Base.pm
index 971269790..b896d9930 100644
--- a/SL/Controller/CsvImport/Base.pm
+++ b/SL/Controller/CsvImport/Base.pm
@@ -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) = @_;
diff --git a/SL/Controller/Helper/GetModels/Sorted.pm b/SL/Controller/Helper/GetModels/Sorted.pm
index 3b03e04f7..73144dbc4 100644
--- a/SL/Controller/Helper/GetModels/Sorted.pm
+++ b/SL/Controller/Helper/GetModels/Sorted.pm
@@ -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) ],
diff --git a/SL/Controller/Project.pm b/SL/Controller/Project.pm
index ba7bf1db6..2c2498b04 100644
--- a/SL/Controller/Project.pm
+++ b/SL/Controller/Project.pm
@@ -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
index 000000000..cd5707b20
--- /dev/null
+++ b/SL/Controller/Reconciliation.pm
@@ -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;
diff --git a/SL/DB/AccTransaction.pm b/SL/DB/AccTransaction.pm
index 053a30adc..f25db8ee5 100644
--- a/SL/DB/AccTransaction.pm
+++ b/SL/DB/AccTransaction.pm
@@ -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
index 000000000..cc6a1ee0a
--- /dev/null
+++ b/SL/DB/BankTransaction.pm
@@ -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;
diff --git a/SL/DB/GLTransaction.pm b/SL/DB/GLTransaction.pm
index 52c8b6e2e..67a4d3aca 100644
--- a/SL/DB/GLTransaction.pm
+++ b/SL/DB/GLTransaction.pm
@@ -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;
diff --git a/SL/DB/Helper/ALL.pm b/SL/DB/Helper/ALL.pm
index 4e59b9222..878300693 100644
--- a/SL/DB/Helper/ALL.pm
+++ b/SL/DB/Helper/ALL.pm
@@ -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;
diff --git a/SL/DB/Helper/Mappings.pm b/SL/DB/Helper/Mappings.pm
index 70e73a421..c29cc0a44 100644
--- a/SL/DB/Helper/Mappings.pm
+++ b/SL/DB/Helper/Mappings.pm
@@ -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',
diff --git a/SL/DB/Invoice.pm b/SL/DB/Invoice.pm
index 3e6c65a49..3ecc9575e 100644
--- a/SL/DB/Invoice.pm
+++ b/SL/DB/Invoice.pm
@@ -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
index 000000000..50a23197b
--- /dev/null
+++ b/SL/DB/Manager/BankTransaction.pm
@@ -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;
diff --git a/SL/DB/Manager/Invoice.pm b/SL/DB/Manager/Invoice.pm
index 03c2296da..5dc943f85 100644
--- a/SL/DB/Manager/Invoice.pm
+++ b/SL/DB/Manager/Invoice.pm
@@ -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";
}
diff --git a/SL/DB/Manager/OrderItem.pm b/SL/DB/Manager/OrderItem.pm
index e964f4ff3..ce673db95 100644
--- a/SL/DB/Manager/OrderItem.pm
+++ b/SL/DB/Manager/OrderItem.pm
@@ -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
index 000000000..8f63a1cd1
--- /dev/null
+++ b/SL/DB/Manager/ReconciliationLink.pm
@@ -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
index 000000000..fefe1e587
--- /dev/null
+++ b/SL/DB/MetaSetup/BankTransaction.pm
@@ -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
index 000000000..796d308c5
--- /dev/null
+++ b/SL/DB/MetaSetup/ReconciliationLink.pm
@@ -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;
+;
diff --git a/SL/DB/PurchaseInvoice.pm b/SL/DB/PurchaseInvoice.pm
index 19808eba5..78034c7f8 100644
--- a/SL/DB/PurchaseInvoice.pm
+++ b/SL/DB/PurchaseInvoice.pm
@@ -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
index 000000000..0632d5187
--- /dev/null
+++ b/SL/DB/ReconciliationLink.pm
@@ -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;
diff --git a/SL/Helper/Flash.pm b/SL/Helper/Flash.pm
index 07b22c081..77dda7846 100644
--- a/SL/Helper/Flash.pm
+++ b/SL/Helper/Flash.pm
@@ -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)),
);
#
diff --git a/SL/Presenter.pm b/SL/Presenter.pm
index 7b83e3693..deeed1eb4 100644
--- a/SL/Presenter.pm
+++ b/SL/Presenter.pm
@@ -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
index 000000000..13a8cb28b
--- /dev/null
+++ b/SL/Presenter/BankAccount.pm
@@ -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;
diff --git a/SL/Presenter/Invoice.pm b/SL/Presenter/Invoice.pm
index c07845016..b18c064b7 100644
--- a/SL/Presenter/Invoice.pm
+++ b/SL/Presenter/Invoice.pm
@@ -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) = @_;
diff --git a/SL/Presenter/Record.pm b/SL/Presenter/Record.pm
index b46896d47..b395e822a 100644
--- a/SL/Presenter/Record.pm
+++ b/SL/Presenter/Record.pm
@@ -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) = @_;
diff --git a/SL/RC.pm b/SL/RC.pm
index 9c73d45e2..89904d868 100644
--- 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 e4604e3af..928d2243c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.2.1
+3.2.1-rb
diff --git a/bin/mozilla/drafts.pl b/bin/mozilla/drafts.pl
index 83cf6be90..5b1661bd2 100644
--- a/bin/mozilla/drafts.pl
+++ b/bin/mozilla/drafts.pl
@@ -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();
diff --git a/css/kivitendo/main.css b/css/kivitendo/main.css
index b20f60d53..d09f72b53 100644
--- a/css/kivitendo/main.css
+++ b/css/kivitendo/main.css
@@ -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
index 000000000..1de86ee7f
Binary files /dev/null and b/image/bank-building.jpg differ
diff --git a/js/locale/de.js b/js/locale/de.js
index 95b135720..0d8484815 100644
--- a/js/locale/de.js
+++ b/js/locale/de.js
@@ -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",
diff --git a/locale/de/all b/locale/de/all
index 620fe6c51..c9bb4401f 100755
--- a/locale/de/all
+++ b/locale/de/all
@@ -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ü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ü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ü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ü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ö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ä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ä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 "Automatisches Zuweisen der Lagerplätze" 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ält momentan keine Einträ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',
diff --git a/menus/erp.ini b/menus/erp.ini
index e3378099a..37b4f8013 100644
--- a/menus/erp.ini
+++ b/menus/erp.ini
@@ -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
index 000000000..a911e07d8
--- /dev/null
+++ b/sql/Pg-upgrade2/automatic_reconciliation.sql
@@ -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
index 000000000..4b57741d8
--- /dev/null
+++ b/sql/Pg-upgrade2/bank_transactions.sql
@@ -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
index 000000000..a72ff3399
--- /dev/null
+++ b/templates/webpages/bank_transactions/_filter.html
@@ -0,0 +1,75 @@
+[%- USE T8 %]
+[%- USE L %]
+[%- USE LxERP %]
+[%- USE HTML %]
+
diff --git a/templates/webpages/bank_transactions/add_list.html b/templates/webpages/bank_transactions/add_list.html
new file mode 100644
index 000000000..08eaf5b9b
--- /dev/null
+++ b/templates/webpages/bank_transactions/add_list.html
@@ -0,0 +1,37 @@
+[%- USE T8 -%][%- USE HTML -%][%- USE LxERP -%][%- USE P -%][%- USE L -%]
+[%- IF !INVOICES.size %]
+ [% 'No data was found.' | $T8 %]
+[%- ELSE %]
+
+
+ [% L.checkbox_tag('invoices_check_all') %] |
+ [%- LxERP.t8("Invoice number") %] |
+ [%- LxERP.t8("Amount") %] |
+ [%- LxERP.t8("Open amount") %] |
+ [%- LxERP.t8("Transdate") %] |
+ [%- LxERP.t8("Customer/Vendor number") %] |
+ [%- LxERP.t8("Customer/Vendor name") %] |
+
+
+ [%- FOREACH invoice = INVOICES %]
+
+ [% L.checkbox_tag('invoice_id[]', value=invoice.id) %] |
+ [%- invoice.invnumber %] |
+ [%- LxERP.format_amount(invoice.amount, 2) %] |
+ [%- LxERP.format_amount(invoice.amount - invoice.paid, 2) %] |
+ [%- invoice.transdate_as_date %] |
+ [%- invoice.vendor.vendornumber %][%- invoice.customer.customernumber %] |
+ [%- invoice.vendor.name %][%- invoice.customer.name %] |
+
+ [%- END %]
+
+
+
+[%- END %]
+
diff --git a/templates/webpages/bank_transactions/assign_invoice.html b/templates/webpages/bank_transactions/assign_invoice.html
new file mode 100644
index 000000000..f8a6697aa
--- /dev/null
+++ b/templates/webpages/bank_transactions/assign_invoice.html
@@ -0,0 +1,102 @@
+[%- USE HTML %][%- USE L %][%- USE LxERP %][%- USE T8 %]
+
+
+
+
+
diff --git a/templates/webpages/bank_transactions/create_invoice.html b/templates/webpages/bank_transactions/create_invoice.html
new file mode 100644
index 000000000..09af08ec8
--- /dev/null
+++ b/templates/webpages/bank_transactions/create_invoice.html
@@ -0,0 +1,101 @@
+[%- USE HTML %][%- USE L %][%- USE LxERP %][%- USE T8 %]
+
+ Transaction
+
+
+ [%- LxERP.t8("ID") %]: |
+ [%- LxERP.t8("Amount") %]: |
+ [%- LxERP.t8("Date") %]: |
+ [%- LxERP.t8("Remote name") %]: |
+ [%- LxERP.t8("Purpose") %]: |
+ [%- LxERP.t8("Remote bank code") %]: |
+ [%- LxERP.t8("Remote account number") %]: |
+
+
+
+ [% SELF.transaction.id %] |
+ [% LxERP.format_amount(SELF.transaction.amount, 2) %] |
+ [% SELF.transaction.valutadate_as_date %] |
+ [% SELF.transaction.remote_name %] |
+ [% SELF.transaction.purpose %] |
+ [% SELF.transaction.remote_bank_code %] |
+ [% SELF.transaction.remote_account_number %] |
+
+
+
+
+
+[% 'Vendor filter for AP transaction drafts' | $T8 %]:
+
+
+
+
+ [% LxERP.t8("Cancel") %]
+
+
+
+
+[% IF DRAFTS.size %]
+[% 'Draft suggestions' | $T8 %]:
+
+
+
+
+ [% 'Description' | $T8 %] |
+ [% 'Vendor' | $T8 %] |
+ [% 'Employee' | $T8 %] |
+ [% 'Draft from:' | $T8 %] |
+
+
+ [% FOREACH draft = DRAFTS %]
+
+ [% HTML.escape(draft.description) %] |
+ [% HTML.escape(draft.vendor) %] |
+ [% HTML.escape(draft.employee.name) %] |
+ [% HTML.escape(draft.itime_as_date) %] |
+
+ [% END %]
+
+[% ELSE %]
+
[% 'No draft was found.' | $T8 %]
+[% END %]
+
+
+
+
diff --git a/templates/webpages/bank_transactions/filter_drafts.html b/templates/webpages/bank_transactions/filter_drafts.html
new file mode 100644
index 000000000..7b2c8deae
--- /dev/null
+++ b/templates/webpages/bank_transactions/filter_drafts.html
@@ -0,0 +1,23 @@
+[%- USE T8 -%][%- USE HTML -%][%- USE LxERP -%][%- USE P -%][%- USE L -%]
+[%- IF !DRAFTS.size %]
+ [% 'No draft was found.' | $T8 %]
+[%- ELSE %]
+
+
+ [% 'Date' | $T8 %] |
+ [% 'Description' | $T8 %] |
+ [% 'Employee' | $T8 %] |
+ [% 'Vendor' | $T8 %] |
+
+
+ [% FOREACH draft = DRAFTS %]
+
+ [% HTML.escape(draft.itime_as_date) %] |
+ [% HTML.escape(draft.description) %] |
+ [% HTML.escape(draft.employee.name) %] |
+ [% HTML.escape(draft.vendor) %] |
+
+ [% END %]
+
+[%- END %]
+
diff --git a/templates/webpages/bank_transactions/invoices.html b/templates/webpages/bank_transactions/invoices.html
new file mode 100644
index 000000000..6ba716c2b
--- /dev/null
+++ b/templates/webpages/bank_transactions/invoices.html
@@ -0,0 +1,8 @@
+[% USE L %]
+[% FOREACH invoice = INVOICES %]
+
+ [% L.hidden_tag('invoice_ids.' _ bt_id _'[]', invoice.id) %]
+ [% invoice.invnumber %]
+
x
+
+[% END %]
diff --git a/templates/webpages/bank_transactions/list.html b/templates/webpages/bank_transactions/list.html
new file mode 100644
index 000000000..d9d52388c
--- /dev/null
+++ b/templates/webpages/bank_transactions/list.html
@@ -0,0 +1,104 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+
+
+[% title %]
+
+[%- INCLUDE 'common/flash.html' %]
+
+[% 'Account number' | $T8 %] [% bank_account.account_number %], [% 'Bank code' | $T8 %] [% bank_account.bank_code %], [% 'Bank' | $T8 %] [% bank_account.bank %]
+
+[% IF FORM.filter.fromdate %] [% 'From' | $T8 %] [% FORM.filter.fromdate %] [% END %]
+[% IF FORM.filter.todate %] [% 'to (date)' | $T8 %] [% FORM.filter.todate %][% END %]
+
+
+
+
+
+
diff --git a/templates/webpages/bank_transactions/report_bottom.html b/templates/webpages/bank_transactions/report_bottom.html
new file mode 100644
index 000000000..8868ff1e6
--- /dev/null
+++ b/templates/webpages/bank_transactions/report_bottom.html
@@ -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
index 000000000..f6fbce1d6
--- /dev/null
+++ b/templates/webpages/bank_transactions/report_top.html
@@ -0,0 +1,3 @@
+[%- USE L %]
+[%- PROCESS 'bank_transactions/_filter.html' filter=SELF.filter %]
+
diff --git a/templates/webpages/bank_transactions/search.html b/templates/webpages/bank_transactions/search.html
new file mode 100644
index 000000000..adab511e3
--- /dev/null
+++ b/templates/webpages/bank_transactions/search.html
@@ -0,0 +1,73 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+[%- INCLUDE 'common/flash.html' %]
+
+
+
+
+
+ [% L.hidden_tag('action', 'BankTransaction/list') %]
+
+ [% L.submit_tag('dummy', LxERP.t8('Continue')) %]
+
diff --git a/templates/webpages/bank_transactions/tabs/all.html b/templates/webpages/bank_transactions/tabs/all.html
new file mode 100644
index 000000000..a392bb89f
--- /dev/null
+++ b/templates/webpages/bank_transactions/tabs/all.html
@@ -0,0 +1,98 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+
diff --git a/templates/webpages/bank_transactions/tabs/automatic.html b/templates/webpages/bank_transactions/tabs/automatic.html
new file mode 100644
index 000000000..3ce007a01
--- /dev/null
+++ b/templates/webpages/bank_transactions/tabs/automatic.html
@@ -0,0 +1,51 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+
+
+
+ [% L.checkbox_tag('check_all') %] |
+
+ [% 'Typ' | $T8 %] |
+ [% 'ID' | $T8 %] |
+ [% 'Transdate' | $T8 %] |
+ [% 'Amount' | $T8 %] |
+ [% 'Purpose/Reference' | $T8 %] |
+ [% 'Customer/Vendor/Remote name' | $T8 %] |
+
+
+ [% IF !PROPOSALS.size %]
+
+ [% 'No data was found.' | $T8 %] |
+
+ [% ELSE %]
+ [% FOREACH proposal = PROPOSALS %]
+
+
+
+ [% L.checkbox_tag('proposal_ids[]', checked=0, value=proposal.id) %]
+ |
+
+ [% 'Bank transaction' | $T8 %] |
+ [% proposal.id %] |
+ [% proposal.transdate_as_date %] |
+ [% proposal.amount_as_number %] |
+ [% HTML.escape(proposal.purpose) %] |
+ [% HTML.escape(proposal.remote_name) %] |
+
+
+ [% FOREACH proposed_invoice = proposal.proposals %]
+
+
+ [% 'Rechnung/Buchung' | $T8 %] |
+ [% proposed_invoice.id %] |
+ [% proposed_invoice.transdate_as_date %] |
+ [% proposed_invoice.amount_as_number %] |
+ [% HTML.escape(proposed_invoice.invnumber) %] |
+ [% HTML.escape(proposed_invoice.customer.name) %][% HTML.escape(proposed_invoice.vendor.name) %] |
+
+ [% L.hidden_tag("proposed_invoice_" _ proposal.id, proposed_invoice.id) %]
+ [% END %]
+
+ [% END %]
+ [% END %]
+
diff --git a/templates/webpages/common/flash.html b/templates/webpages/common/flash.html
index cb592edc3..4b705cdfc 100644
--- a/templates/webpages/common/flash.html
+++ b/templates/webpages/common/flash.html
@@ -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
index 000000000..652bc98af
--- /dev/null
+++ b/templates/webpages/csv_import/_form_mt940.html
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/templates/webpages/csv_import/form.html b/templates/webpages/csv_import/form.html
index 8535f1a23..91b227d65 100644
--- a/templates/webpages/csv_import/form.html
+++ b/templates/webpages/csv_import/form.html
@@ -260,6 +260,8 @@
[%- 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 %]
diff --git a/templates/webpages/reconciliation/_linked_transactions.html b/templates/webpages/reconciliation/_linked_transactions.html
new file mode 100644
index 000000000..29eaa08e0
--- /dev/null
+++ b/templates/webpages/reconciliation/_linked_transactions.html
@@ -0,0 +1,94 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+[% IF !SELF.LINKED_TRANSACTIONS.size %]
+
+ [% 'No data was found.' | $T8 %] |
+
+[% ELSE %]
+ [% FOREACH link = SELF.LINKED_TRANSACTIONS %]
+ [% IF link.type == 'Link' %]
+
+ [% FOREACH bt = link.BT %]
+
+ [% IF loop.count == 1 %]
+
+ [% L.button_tag('delete_reconciliation(' _ link.rec_group _ ')', LxERP.t8("X")) %]
+ |
+ [% END %]
+
+ |
+ [% 'Bank Transaction' | $T8 %] |
+ [% HTML.escape(bt.id) %] |
+ [% HTML.escape(bt.transdate_as_date) %] |
+ [% HTML.escape(bt.amount_as_number) %] |
+ |
+ [% HTML.escape(bt.remote_name) %] |
+ [% HTML.escape(bt.purpose) %] |
+ [% HTML.escape(bt.remote_account_number) %] |
+ [% HTML.escape(bt.remote_bank_code) %] |
+ |
+
+ [% END %]
+ [% FOREACH bb = link.BB %]
+
+ |
+ [% 'Acc Transaction' | $T8 %] |
+ [% HTML.escape(bb.acc_trans_id) %] |
+ [% HTML.escape(bb.transdate_as_date) %] |
+ |
+ [% LxERP.format_amount(-1 * bb.amount, 2) %] |
+ [% HTML.escape(bb.get_transaction.customer.name) %][% HTML.escape(bb.get_transaction.vendor.name) %][% HTML.escape(bb.get_transaction.description) %] |
+ [% bb.get_transaction.link %] |
+ |
+ |
+ [% HTML.escape(bb.source) %] |
+
+ [% END %]
+
+ [% ELSE %]
+
+ [% FOREACH bt = link.BT %]
+
+
+ [%- L.checkbox_tag('bt_ids[]', value=link.id, onchange='update_reconciliation_table();') %]
+ |
+
+ |
+ [% 'Bank Transaction' | $T8 %] |
+ [% HTML.escape(bt.id) %] |
+ [% HTML.escape(bt.transdate_as_date) %] |
+ [% HTML.escape(bt.amount_as_number) %] |
+ |
+ [% HTML.escape(bt.remote_name) %] |
+ [% HTML.escape(bt.purpose) %] |
+ [% HTML.escape(bt.remote_account_number) %] |
+ [% HTML.escape(bt.remote_bank_code) %] |
+ |
+
+ [% END %]
+ [% FOREACH bb = link.BB %]
+
+
+ [%- L.checkbox_tag('bb_ids[]', value=link.id, onchange='update_reconciliation_table();') %]
+ |
+
+ |
+ [% 'Acc Transaction' | $T8 %] |
+ [% HTML.escape(bb.acc_trans_id) %] |
+ [% HTML.escape(bb.transdate_as_date) %] |
+ |
+ [% LxERP.format_amount(-1 * bb.amount, 2) %] |
+ [% HTML.escape(bb.get_transaction.customer.name) %][% HTML.escape(bb.get_transaction.vendor.name) %][% HTML.escape(bb.get_transaction.description) %] |
+ [% bb.get_transaction.link %] |
+ |
+ |
+ [% HTML.escape(bb.source) %] |
+
+ [% END %]
+
+ [% END %]
+ [% END %]
+[% END %]
diff --git a/templates/webpages/reconciliation/assigning_table.html b/templates/webpages/reconciliation/assigning_table.html
new file mode 100644
index 000000000..2f6d4de34
--- /dev/null
+++ b/templates/webpages/reconciliation/assigning_table.html
@@ -0,0 +1,39 @@
+[%- USE HTML -%][%- USE LxERP -%][%- USE L -%][%- USE T8 -%]
+
+[% IF SELF.ELEMENTS.size %]
+
+
+
+ |
+ [% 'ID' | $T8 %] |
+ [% 'Type' | $T8 %] |
+ [% 'Transdate' | $T8 %] |
+ [% 'Amount BT' | $T8 %] |
+ [% 'Amount BB' | $T8 %] |
+
+
+ [% FOREACH element = SELF.ELEMENTS %]
+
+
+ x |
+ [% HTML.escape(element.id) %] |
+ [% IF element.type == 'BT' %][% 'Bank transaction' | $T8 %][% ELSE %][% 'Acc transaction' | $T8 %][% END %] |
+ [% HTML.escape(element.transdate_as_date) %] |
+ [% IF element.type == 'BT' %][% HTML.escape(element.amount_as_number) %][% END %] |
+ [% IF element.type == 'BB' %][% LxERP.format_amount(-1 * element.amount, 2) %][% END %] |
+
+
+ [% END %]
+
+
+ |
+ |
+ |
+ |
+ [% bt_sum %] |
+ [% bb_sum %] |
+
+
+
+ [% 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
index 000000000..2c1a4dbb2
--- /dev/null
+++ b/templates/webpages/reconciliation/form.html
@@ -0,0 +1,123 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+
+
+[% title %]
+
+[%- INCLUDE 'common/flash.html' %]
+
+
+
+
+
diff --git a/templates/webpages/reconciliation/proposals.html b/templates/webpages/reconciliation/proposals.html
new file mode 100644
index 000000000..10f9a1371
--- /dev/null
+++ b/templates/webpages/reconciliation/proposals.html
@@ -0,0 +1,51 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+[% IF !SELF.PROPOSALS.size %]
+
+ [% 'No data was found.' | $T8 %] |
+
+[% ELSE %]
+ [% FOREACH proposal = SELF.PROPOSALS %]
+
+
+
+ [% L.checkbox_tag('bt_ids[]', checked=0, value=proposal.BT.id) %]
+ |
+
+ |
+ [% 'Bank Transaction' | $T8 %] |
+ [% HTML.escape(proposal.BT.id) %] |
+ [% HTML.escape(proposal.BT.transdate_as_date) %] |
+ [% HTML.escape(proposal.BT.amount_as_number) %] |
+ |
+ [% HTML.escape(proposal.BT.remote_name) %] |
+ [% HTML.escape(proposal.BT.purpose) %] |
+ [% HTML.escape(proposal.BT.remote_account_number) %] |
+ [% HTML.escape(proposal.BT.remote_bank_code) %] |
+ |
+ [% L.hidden_tag('proposal_list.' _ proposal.BT.id _ '.BT', proposal.BT.id) %]
+
+
+ [% FOREACH bb = proposal.BB %]
+
+ |
+ [% 'Acc Transaction' | $T8 %] |
+ [% HTML.escape(bb.acc_trans_id) %] |
+ [% HTML.escape(bb.transdate_as_date) %] |
+ |
+ [% LxERP.format_amount(-1 * bb.amount, 2) %] |
+ [% HTML.escape(bb.get_transaction.customer.name) %][% HTML.escape(bb.get_transaction.vendor.name) %][% HTML.escape(bb.get_transaction.description) %] |
+ [% bb.get_transaction.link %] |
+ |
+ |
+ [% HTML.escape(bb.source) %] |
+ [% L.hidden_tag('proposal_list.' _ proposal.BT.id _ '.BB[]', bb.acc_trans_id) %]
+
+ [% END %]
+
+ [% END %]
+[% END %]
+
diff --git a/templates/webpages/reconciliation/search.html b/templates/webpages/reconciliation/search.html
new file mode 100644
index 000000000..b87ed4ac2
--- /dev/null
+++ b/templates/webpages/reconciliation/search.html
@@ -0,0 +1,44 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE L %]
+[%- USE LxERP %]
+
+
diff --git a/templates/webpages/reconciliation/tabs/automatic.html b/templates/webpages/reconciliation/tabs/automatic.html
new file mode 100644
index 000000000..6bff8c09b
--- /dev/null
+++ b/templates/webpages/reconciliation/tabs/automatic.html
@@ -0,0 +1,64 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+
+
+
+ [% L.checkbox_tag('proposal_check_all') %] |
+
+ |
+ [% 'Type' | $T8 %] |
+ [% 'ID/Acc_ID' | $T8 %] |
+ [% 'Transdate' | $T8 %] |
+ [% 'Amount BT' | $T8 %] |
+ [% 'Amount BB' | $T8 %] |
+ [% 'Remote Name/Customer/Description' | $T8 %] |
+ [% 'Purpose/Reference' | $T8 %] |
+ [% 'Remote account number' | $T8 %] |
+ [% 'Remote bank code' | $T8 %] |
+ [% 'Source' | $T8 %] |
+
+
+
+ [% PROCESS "reconciliation/proposals.html" %]
+
+
+[% L.button_tag("reconciliate_proposals()", LxERP.t8("Reconciliate")) %]
+
+
+
diff --git a/templates/webpages/reconciliation/tabs/overview.html b/templates/webpages/reconciliation/tabs/overview.html
new file mode 100644
index 000000000..68539b3f4
--- /dev/null
+++ b/templates/webpages/reconciliation/tabs/overview.html
@@ -0,0 +1,121 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+
+
+
+
+ |
+
+ |
+ [% 'Type' | $T8 %] |
+ [% 'ID/Acc_ID' | $T8 %] |
+ [% 'Transdate' | $T8 %] |
+ [% 'Amount BT' | $T8 %] |
+ [% 'Amount BB' | $T8 %] |
+ [% 'Remote Name/Customer/Description' | $T8 %] |
+ [% 'Purpose/Reference' | $T8 %] |
+ [% 'Remote account number' | $T8 %] |
+ [% 'Remote bank code' | $T8 %] |
+ [% 'Source' | $T8 %] |
+
+
+
+ [% PROCESS 'reconciliation/_linked_transactions.html' %]
+
+
+
+ |
+ |
+ |
+ |
+ |
+ [% LxERP.format_amount(SELF.bt_balance, 2) %] |
+ [% LxERP.format_amount(-1 * SELF.bb_balance, 2) %] |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/webpages/reconciliation/tabs/set_cleared.html b/templates/webpages/reconciliation/tabs/set_cleared.html
new file mode 100644
index 000000000..5e5644d63
--- /dev/null
+++ b/templates/webpages/reconciliation/tabs/set_cleared.html
@@ -0,0 +1,8 @@
+[%- USE T8 %]
+[%- USE HTML %]
+[%- USE LxERP %]
+[%- USE L %]
+
+
+Set cleared
+