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 %] +
+
+[% 'Show Filter' | $T8 %] + [% SELF.filter_summary | 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 %] + + + + + + + + + + + + [%- FOREACH invoice = INVOICES %] + + + + + + + + + + [%- END %] +
[% 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") %]
[% 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 %] + 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 %] + +
+ Transaction + + + + + + + + + + + + + + + + + + + + +
[%- LxERP.t8("ID") %]:[%- LxERP.t8("Amount") %]:[%- LxERP.t8("Remote bank code") %]:[%- LxERP.t8("Remote account number") %]:[%- LxERP.t8("Remote name") %]:[%- LxERP.t8("Purpose") %]:[%- LxERP.t8("Transdate") %]:
[% SELF.transaction.id %][% LxERP.format_amount(SELF.transaction.amount, 2) %][% SELF.transaction.remote_bank_code %][% SELF.transaction.remote_account_number %][% SELF.transaction.remote_name %][% SELF.transaction.purpose %][% SELF.transaction.transdate_as_date %]
+ + Filter + + + + + + + + + + + + + + + + + + + + + + + + +
[%- LxERP.t8("Invoice number") %][% L.input_tag('invnumber', '', style=style) %][%- LxERP.t8("Customer/Vendor name") %][% L.input_tag('vcname', '', style=style) %]
[%- LxERP.t8("Amount") %][% L.input_tag('amount', '', style=style) %][%- LxERP.t8("Customer/Vendor number") %][% L.input_tag('vcnumber', '', style=style) %]
[%- LxERP.t8("Transdate from") %][% L.date_tag('transdatefrom') %][%- LxERP.t8("to (date)") %][% L.date_tag('transdateto') %]
+ +

+ [% L.submit_tag('', LxERP.t8("Search")) %] + [% L.button_tag('add_selected_invoices()', LxERP.t8("Add invoices"), id='add_selected_record_links_button') %] + [%- LxERP.t8("Reset") %] + [% LxERP.t8("Cancel") %] +

+ +
+ +
+ +
+ + + 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 %]: + +
+[% L.hidden_tag('bt_id', SELF.transaction.id) %] + + + + + +
[%- LxERP.t8("Vendor") %] + [%- INCLUDE 'generic/multibox.html' + name = 'vendor', + select_name = 'vendor_id', + default = ALL_VENDORS.size < limit ? vendor_id : vendor_name, + style = 'width: 250px', + DATA = ALL_VENDORS, + id_key = 'id', + label_key = 'name', + limit = limit, + show_empty = 1, + allow_textbox = 1, + class = 'initial_focus', + onChange = 'filter_drafts();', + -%] +
+
+ +

+ [% LxERP.t8("Cancel") %] +

+ +
+
+[% IF DRAFTS.size %] +[% 'Draft suggestions' | $T8 %]: + + + + + + + + + + + [% FOREACH draft = DRAFTS %] + + + + + + + [% END %] +
[% 'Description' | $T8 %][% 'Vendor' | $T8 %][% 'Employee' | $T8 %][% 'Draft from:' | $T8 %]
[% HTML.escape(draft.description) %][% HTML.escape(draft.vendor) %][% HTML.escape(draft.employee.name) %][% HTML.escape(draft.itime_as_date) %]
+[% 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 %] + + + + + + + + + [% FOREACH draft = DRAFTS %] + + + + + + + [% END %] +
[% 'Date' | $T8 %][% 'Description' | $T8 %][% 'Employee' | $T8 %][% 'Vendor' | $T8 %]
[% HTML.escape(draft.itime_as_date) %][% HTML.escape(draft.description) %][% HTML.escape(draft.employee.name) %][% HTML.escape(draft.vendor) %]
+[%- 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 %] +

+ +
+[% L.hidden_tag('filter.bank_account', FORM.filter.bank_account) %] +[% L.hidden_tag('filter.fromdate', FORM.filter.fromdate) %] +[% L.hidden_tag('filter.todate', FORM.filter.todate) %] + +
+ + +
[% PROCESS "bank_transactions/tabs/all.html" %]
+
[% PROCESS "bank_transactions/tabs/automatic.html" %]
+
+ +[% L.hidden_tag('action', 'BankTransaction/dispatch') %] +[% L.submit_tag('action_save_invoices', LxERP.t8('Save invoices')) %] +[% L.submit_tag('action_save_proposals', LxERP.t8('Save proposals'), style='display: none') %] + +
+ + + 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' %] + +
+ +
[% 'Search bank transactions' | $T8 %]
+ +

+ + + + + + + + + + + + + + + + + +
[% 'Bank account' | $T8 %][% L.select_tag('filter.bank_account', BANK_ACCOUNTS, default=bank_acount, title_sub=\label_sub, with_empty=0, style='width:450px') %]
[% 'Transdate from' | $T8 %][% L.date_tag('filter.fromdate', filter.fromdate) %]
[% 'to (date)' | $T8 %][% L.date_tag('filter.todate', filter.todate) %]
+

+ +
+ + [% 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 -%] + + + + + + + + + + + + + + + + + + + + + + + [%- FOREACH bt = BANK_TRANSACTIONS %] + + + + + + + + + + + + + + + + + [%- END %] + +
[% 'Assigned invoices' | $T8 %][% IF FORM.sort_by == 'proposal'%] + + [% 'Proposal' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Proposal' | $T8 %] + [% END %] + [% IF FORM.sort_by == 'remote_bank_code'%] + + [% 'Remote bank code' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Remote bank code' | $T8 %] + [% END %] + [% IF FORM.sort_by == 'remote_account_number'%] + + [% 'Remote account number' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Remote account number' | $T8 %] + [% END %] + [% IF FORM.sort_by == 'transdate'%] + + [% 'Transdate' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Transdate' | $T8 %] + [% END %] + [% IF FORM.sort_by == 'valutadate'%] + + [% 'Valutadate' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Valutadate' | $T8 %] + [% END %] + [% IF FORM.sort_by == 'amount'%] + + [% 'Amount' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Amount' | $T8 %] + [% END %] + [% 'Assigned' | $T8 %][% 'Currency' | $T8 %][% IF FORM.sort_by == 'remote_name'%] + + [% 'Remote name' | $T8 %][% IF FORM.sort_dir == 0 %][% ELSE %][% END %] + [% ELSE %] + + [% 'Remote name' | $T8 %] + [% END %] + [% 'Remote name 1' | $T8 %][% 'Purpose' | $T8 %]
[% 'Assign invoice' | $T8 %][% 'Create invoice' | $T8 %] + [% FOREACH prop = bt.proposals %] + + [% END %] + [% HTML.escape(bt.remote_bank_code) %][% HTML.escape(bt.remote_account_number) %][% bt.transdate_as_date %][% bt.valutadate_as_date %][% bt.amount_as_number %][% bt.invoice_amount_as_number %][% HTML.escape(bt.currency.name) %][% HTML.escape(bt.remote_name) %][% HTML.escape(bt.remote_name_1) %][% HTML.escape(bt.purpose) %]
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 -%] + + + + + + + + + + + + + + + [% IF !PROPOSALS.size %] + + + + [% ELSE %] + [% FOREACH proposal = PROPOSALS %] + + + + + + + + + + + + + [% FOREACH proposed_invoice = proposal.proposals %] + + + + + + + + + + [% L.hidden_tag("proposed_invoice_" _ proposal.id, proposed_invoice.id) %] + [% END %] + + [% END %] + [% END %] +
[% L.checkbox_tag('check_all') %][% 'Typ' | $T8 %][% 'ID' | $T8 %][% 'Transdate' | $T8 %][% 'Amount' | $T8 %][% 'Purpose/Reference' | $T8 %][% 'Customer/Vendor/Remote name' | $T8 %]

[% 'No data was found.' | $T8 %]

+ [% 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) %]
[% '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) %]
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 %] + + + + + + + + + + + + [% FOREACH element = SELF.ELEMENTS %] + + + + + + + + + + + [% END %] + + + + + + + + + + +
[% 'ID' | $T8 %][% 'Type' | $T8 %][% 'Transdate' | $T8 %][% 'Amount BT' | $T8 %][% 'Amount BB' | $T8 %]
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 %]
[% 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' %] + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[% 'Bank account' | $T8 %][% L.select_tag('filter.local_bank_account_id:number', + SELF.BANK_ACCOUNTS, + default=FORM.filter.local_bank_account_id_number, + title_sub=\label_sub, with_empty=0, + style='width:450px', + onchange='filter_table();') %]
[% 'From' | $T8 %][% L.date_tag('filter.fromdate:date::ge', FORM.filter.fromdate_date__ge, onchange='filter_table();') %][% 'to (date)' | $T8 %][% L.date_tag('filter.todate:date::le', FORM.filter.todate_date__le, onchange='filter_table();') %]
[% 'Cleared/uncleared only' | $T8 %][% L.select_tag('filter.cleared:eq_ignore_empty', SELF.cleared, value_key = 'value', title_key = 'title', default=FORM.filter.cleared_eq_ignore_empty, onchange='filter_table();') %]
[% 'Show Stornos' | $T8 %][% L.checkbox_tag('filter.show_stornos', checked=FORM.filter.show_stornos, value='1', onchange='filter_table();') %]
[% 'Absolute BT Balance' | $T8 %][% LxERP.format_amount(SELF.absolut_bt_balance, 2) %]
[% 'Absolute BB Balance' | $T8 %][% LxERP.format_amount(-1 * SELF.absolut_bb_balance, 2) %]
[% 'BT Balance' | $T8 %][% LxERP.format_amount(SELF.bt_balance, 2) %]
[% 'BB Balance' | $T8 %][% LxERP.format_amount(-1 * SELF.bb_balance, 2) %]
+ + [% L.submit_tag('submit_filter', LxERP.t8("Filter"), onclick='filter_table();return false;', style='display: none') %] + +
+ + +
[% PROCESS "reconciliation/tabs/overview.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 %] + +
+ +
[% 'Choose bank account for reconciliation' | $T8 %]
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
[% 'Bank account' | $T8 %][% L.select_tag('filter.local_bank_account_id:number', SELF.BANK_ACCOUNTS, default=bank_account, title_sub=\label_sub, with_empty=0, style='width:450px') %]
[% 'From' | $T8 %][% L.date_tag('filter.fromdate:date::ge') %]
[% 'to (date)' | $T8 %][% L.date_tag('filter.todate:date::le') %]
[% 'Cleared/uncleared only' | $T8 %][% L.select_tag('filter.cleared:eq_ignore_empty', SELF.cleared, value_key = 'value', title_key = 'title') %]
[% 'Show Stornos' | $T8 %][% L.checkbox_tag('filter.show_stornos', value='1') %]
+

+ +
+ +[% L.hidden_tag('action', FORM.next_sub) %] + +

[% L.submit_tag('dummy', LxERP.t8('Continue')) %]

+
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 %] + + + + + + + + + + + + + + + + + + + + + [% PROCESS "reconciliation/proposals.html" %] +
[% 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 %]
+ +[% 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 %] + +
+
+ + + + + + + + + + + + + + + + + + + [% PROCESS 'reconciliation/_linked_transactions.html' %] + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + 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 +