Version 1.1: PDF-Kontoauszüge, Dashboard, Menü-Integration
- Mehrfach-Upload von PDF-Kontoauszügen mit automatischer Metadaten-Erkennung - Dashboard mit Übersichts-Widgets (letzte Buchungen und Kontoauszüge) - Menü-Integration unter "Banken und Kasse" statt eigenem Top-Menü - Erinnerungsfunktion bei veralteten Kontoauszügen (konfigurierbar) - Verknüpfung von Buchungen mit PDF-Kontoauszügen - Auszugsnummer wird automatisch aus dem Zeitraum abgeleitet (Monat/Jahr) - Jahrfilter zeigt nur Jahre mit vorhandenen Kontoauszügen - Modul-Icon auf fa-money-check-alt gesetzt - README und ChangeLog aktualisiert - .gitignore für Kontoauszüge und Build-Artefakte hinzugefügt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3de2fb6fa3
commit
1fc10d3781
371 changed files with 1123 additions and 394 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Uploaded bank statements
|
||||
/Kontoauszüge/
|
||||
|
||||
# Build artifacts
|
||||
/bin/
|
||||
16
ChangeLog.md
16
ChangeLog.md
|
|
@ -1,5 +1,21 @@
|
|||
# CHANGELOG MODULE BANKIMPORT FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||
|
||||
## 1.1
|
||||
|
||||
- Mehrfach-Upload von PDF-Kontoauszügen
|
||||
- Automatische Metadaten-Erkennung aus PDF-Kontoauszügen (Auszugsnummer, Zeitraum, Saldo)
|
||||
- Erinnerungsfunktion bei veralteten Kontoauszügen (konfigurierbar im Admin-Bereich)
|
||||
- Verknüpfung von Buchungen mit PDF-Kontoauszügen
|
||||
- Dashboard mit Übersichts-Widgets (letzte Buchungen und Kontoauszüge)
|
||||
- Menü-Integration unter "Banken und Kasse"
|
||||
- Jahrfilter zeigt nur Jahre mit vorhandenen Kontoauszügen
|
||||
- Auszugsnummer wird automatisch aus dem Zeitraum abgeleitet (Monat/Jahr)
|
||||
|
||||
## 1.0
|
||||
|
||||
Initial version
|
||||
- FinTS/HBCI-Anbindung mit SecureGo Plus TAN-Verfahren
|
||||
- Import von Kontobuchungen
|
||||
- Automatischer Import per Cronjob
|
||||
- Buchungszuordnung zu Rechnungen
|
||||
- PDF-Kontoauszüge Upload
|
||||
|
|
|
|||
133
README.md
133
README.md
|
|
@ -1,96 +1,83 @@
|
|||
# BANKIMPORT FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org)
|
||||
|
||||
Dolibarr-Modul zum Import von Kontoauszügen und Buchungen über die FinTS/HBCI-Schnittstelle deutscher Banken.
|
||||
|
||||
## Features
|
||||
|
||||
Description of the module...
|
||||
- **FinTS/HBCI-Anbindung**: Automatischer Abruf von Kontobuchungen über die FinTS-Schnittstelle (getestet mit VR-Banken/Atruvia)
|
||||
- **TAN-Verfahren**: Unterstützung von SecureGo Plus (Decoupled TAN) für die TAN-Bestätigung per App
|
||||
- **Automatischer Import**: Cronjob-basierter Import von Buchungen (täglich, zweimal wöchentlich oder wöchentlich)
|
||||
- **Buchungszuordnung**: Automatische Zuordnung importierter Buchungen zu Rechnungen anhand von Referenznummern, Beträgen, Namen und IBAN
|
||||
- **PDF-Kontoauszüge**: Upload und Verwaltung von PDF-Kontoauszügen mit automatischer Metadaten-Erkennung (Auszugsnummer, Zeitraum, Saldo)
|
||||
- **Mehrfach-Upload**: Gleichzeitiger Upload mehrerer PDF-Kontoauszüge
|
||||
- **Dashboard**: Übersichtsseite mit den letzten Buchungen und Kontoauszügen
|
||||
- **Erinnerungsfunktion**: Konfigurierbare Warnung wenn Kontoauszüge nicht aktuell sind
|
||||
- **Integration**: Einbindung in das Dolibarr-Menü "Banken und Kasse"
|
||||
|
||||
<!--
|
||||
{imgmd}
|
||||
-->
|
||||
|
||||
Other external modules are available on [Dolistore.com](https://www.dolistore.com).
|
||||
|
||||
## Translations
|
||||
|
||||
Translations can be completed manually by editing files in the module directories under `langs`.
|
||||
|
||||
<!--
|
||||
This module contains also a sample configuration for Transifex, under the hidden directory [.tx](.tx), so it is possible to manage translation using this service.
|
||||
|
||||
For more information, see the [translator's documentation](https://wiki.dolibarr.org/index.php/Translator_documentation).
|
||||
|
||||
There is a [Transifex project](https://transifex.com/projects/p/dolibarr-module-template) for this module.
|
||||
-->
|
||||
## Voraussetzungen
|
||||
|
||||
- Dolibarr ERP & CRM >= 16.0
|
||||
- PHP >= 8.0
|
||||
- `pdfinfo` und `pdftotext` (Paket `poppler-utils`) für die PDF-Metadaten-Erkennung
|
||||
- Zugang zu einer Bank mit FinTS/HBCI-Schnittstelle
|
||||
|
||||
## Installation
|
||||
|
||||
Prerequisites: You must have Dolibarr ERP & CRM software installed. You can download it from [Dolistore.org](https://www.dolibarr.org).
|
||||
You can also get a ready-to-use instance in the cloud from https://saas.dolibarr.org
|
||||
|
||||
|
||||
### From the ZIP file and GUI interface
|
||||
|
||||
If the module is a ready-to-deploy zip file, so with a name `module_xxx-version.zip` (e.g., when downloading it from a marketplace like [Dolistore](https://www.dolistore.com)),
|
||||
go to menu `Home> Setup> Modules> Deploy external module` and upload the zip file.
|
||||
|
||||
<!--
|
||||
|
||||
Note: If this screen tells you that there is no "custom" directory, check that your setup is correct:
|
||||
|
||||
- In your Dolibarr installation directory, edit the `htdocs/conf/conf.php` file and check that following lines are not commented:
|
||||
|
||||
```php
|
||||
//$dolibarr_main_url_root_alt ...
|
||||
//$dolibarr_main_document_root_alt ...
|
||||
```
|
||||
|
||||
- Uncomment them if necessary (delete the leading `//`) and assign the proper value according to your Dolibarr installation
|
||||
|
||||
For example :
|
||||
|
||||
- UNIX:
|
||||
```php
|
||||
$dolibarr_main_url_root_alt = '/custom';
|
||||
$dolibarr_main_document_root_alt = '/var/www/Dolibarr/htdocs/custom';
|
||||
```
|
||||
|
||||
- Windows:
|
||||
```php
|
||||
$dolibarr_main_url_root_alt = '/custom';
|
||||
$dolibarr_main_document_root_alt = 'C:/My Web Sites/Dolibarr/htdocs/custom';
|
||||
```
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
### From a GIT repository
|
||||
|
||||
Clone the repository in `$dolibarr_main_document_root_alt/bankimport`
|
||||
### Aus dem Git-Repository
|
||||
|
||||
```shell
|
||||
cd ....../custom
|
||||
git clone git@github.com:gitlogin/bankimport.git bankimport
|
||||
cd /path/to/dolibarr/custom
|
||||
git clone <repository-url> bankimport
|
||||
cd bankimport
|
||||
composer install
|
||||
```
|
||||
|
||||
-->
|
||||
### Aktivierung
|
||||
|
||||
### Final steps
|
||||
1. In Dolibarr als Administrator anmelden
|
||||
2. Unter "Einstellungen" > "Module/Applikationen" das Modul "Bankimport" aktivieren
|
||||
3. Unter "Banken und Kasse" > "Bankimport" die FinTS-Verbindungsdaten konfigurieren
|
||||
|
||||
Using your browser:
|
||||
## Konfiguration
|
||||
|
||||
- Log into Dolibarr as a super-administrator
|
||||
- Go to "Setup"> "Modules"
|
||||
- You should now be able to find and enable the module
|
||||
### FinTS-Verbindung
|
||||
|
||||
- **FinTS Server URL**: Die FinTS-URL Ihrer Bank (z.B. `https://fints1.atruvia.de/cgi-bin/hbciservlet` für VR-Banken)
|
||||
- **Bankleitzahl (BLZ)**: 8-stellige Bankleitzahl
|
||||
- **Benutzerkennung**: Ihre Online-Banking Benutzerkennung
|
||||
- **PIN**: Wird verschlüsselt in der Datenbank gespeichert
|
||||
- **IBAN**: Kontonummer/IBAN des abzurufenden Kontos
|
||||
|
||||
### Automatischer Import
|
||||
|
||||
## Licenses
|
||||
Der automatische Import kann im Admin-Bereich aktiviert werden. Die Buchungen werden dann per Dolibarr-Cronjob abgerufen. Unterstützte Intervalle: täglich, zweimal wöchentlich, wöchentlich.
|
||||
|
||||
### Main code
|
||||
### PDF-Upload Einstellungen
|
||||
|
||||
GPLv3 or (at your option) any later version. See file COPYING for more information.
|
||||
- **Upload-Modus**: Automatisch (Metadaten aus PDF extrahieren) oder Manuell
|
||||
- **Erinnerung**: Konfigurierbare Warnung wenn der letzte Kontoauszug älter als X Monate ist
|
||||
|
||||
### Documentation
|
||||
## Berechtigungen
|
||||
|
||||
All texts and readme's are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html).
|
||||
- **Bankimport lesen**: Buchungen und Kontoauszüge ansehen
|
||||
- **Bankimport schreiben**: Kontoauszüge abrufen und PDF hochladen
|
||||
- **Bankimport löschen**: Buchungen und Kontoauszüge löschen
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Verwendete Bibliotheken
|
||||
|
||||
- [nemiah/php-fints](https://github.com/nemiah/php-fints) - PHP FinTS/HBCI Bibliothek
|
||||
|
||||
### Datenbank-Tabellen
|
||||
|
||||
- `llx_bankimport_transaction` - Importierte Buchungen
|
||||
- `llx_bankimport_statement` - PDF-Kontoauszüge
|
||||
|
||||
## Lizenz
|
||||
|
||||
GPLv3 oder (nach Wahl) jede spätere Version. Siehe Datei COPYING für weitere Informationen.
|
||||
|
||||
## Autor
|
||||
|
||||
Eduard Wisch - [data IT solution](https://data-it-solution.de)
|
||||
|
|
|
|||
|
|
@ -193,6 +193,23 @@ if ($action == 'update') {
|
|||
$error++;
|
||||
}
|
||||
|
||||
// PDF Upload mode default
|
||||
$res = dolibarr_set_const($db, "BANKIMPORT_UPLOAD_MODE", GETPOST('BANKIMPORT_UPLOAD_MODE', 'alpha'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
$error++;
|
||||
}
|
||||
|
||||
// Reminder setting
|
||||
$res = dolibarr_set_const($db, "BANKIMPORT_REMINDER_ENABLED", GETPOSTINT('BANKIMPORT_REMINDER_ENABLED'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
$error++;
|
||||
}
|
||||
|
||||
$res = dolibarr_set_const($db, "BANKIMPORT_REMINDER_MONTHS", GETPOSTINT('BANKIMPORT_REMINDER_MONTHS'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
$error++;
|
||||
}
|
||||
|
||||
// Automatic import settings
|
||||
$res = dolibarr_set_const($db, "BANKIMPORT_AUTO_ENABLED", GETPOSTINT('BANKIMPORT_AUTO_ENABLED'), 'chaine', 0, '', $conf->entity);
|
||||
if (!($res > 0)) {
|
||||
|
|
@ -371,6 +388,51 @@ print '</tr>';
|
|||
|
||||
print '</table>';
|
||||
|
||||
// PDF Upload Section
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
|
||||
print '<tr class="liste_titre">';
|
||||
print '<td colspan="2">'.$langs->trans("PDFUploadSettings").'</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Default upload mode
|
||||
$defaultUploadMode = getDolGlobalString('BANKIMPORT_UPLOAD_MODE') ?: 'auto';
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="titlefield">'.$langs->trans("DefaultUploadMode").'</td>';
|
||||
print '<td>';
|
||||
$uploadModes = array(
|
||||
'auto' => $langs->trans("UploadModeAuto"),
|
||||
'manual' => $langs->trans("UploadModeManual"),
|
||||
);
|
||||
print $form->selectarray('BANKIMPORT_UPLOAD_MODE', $uploadModes, $defaultUploadMode, 0, 0, 0, '', 0, 0, 0, '', 'minwidth200');
|
||||
print ' <span class="opacitymedium small">'.$langs->trans("DefaultUploadModeHelp").'</span>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Reminder when no statement uploaded
|
||||
$reminderEnabled = getDolGlobalString('BANKIMPORT_REMINDER_ENABLED', '1');
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="titlefield">'.$langs->trans("ReminderEnabled").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="checkbox" name="BANKIMPORT_REMINDER_ENABLED" value="1"'.($reminderEnabled ? ' checked' : '').'>';
|
||||
print ' <span class="opacitymedium small">'.$langs->trans("ReminderEnabledHelp").'</span>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Reminder months threshold
|
||||
$reminderMonths = getDolGlobalInt('BANKIMPORT_REMINDER_MONTHS') ?: 3;
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="titlefield">'.$langs->trans("ReminderMonths").'</td>';
|
||||
print '<td>';
|
||||
$monthOptions = array(1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5', 6 => '6');
|
||||
print $form->selectarray('BANKIMPORT_REMINDER_MONTHS', $monthOptions, $reminderMonths, 0, 0, 0, '', 0, 0, 0, '', 'minwidth75');
|
||||
print ' <span class="opacitymedium small">'.$langs->trans("ReminderMonthsHelp").'</span>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Automatic Import Section
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
|
|
|
|||
0
ajax/checktan.php
Normal file → Executable file
0
ajax/checktan.php
Normal file → Executable file
|
|
@ -1,10 +1,5 @@
|
|||
<?php
|
||||
/* Copyright (C) 2001-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
|
||||
* Copyright (C) 2004-2015 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||
* Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
|
||||
* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
|
||||
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
|
||||
* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -23,16 +18,14 @@
|
|||
/**
|
||||
* \file bankimport/bankimportindex.php
|
||||
* \ingroup bankimport
|
||||
* \brief Home page of bankimport top menu
|
||||
* \brief Dashboard page for BankImport module
|
||||
*/
|
||||
|
||||
// Load Dolibarr environment
|
||||
$res = 0;
|
||||
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
|
||||
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
|
||||
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
|
||||
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
|
||||
$tmp2 = realpath(__FILE__);
|
||||
$i = strlen($tmp) - 1;
|
||||
|
|
@ -47,7 +40,6 @@ if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
|
|||
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
|
||||
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
|
||||
}
|
||||
// Try main.inc.php using relative path
|
||||
if (!$res && file_exists("../main.inc.php")) {
|
||||
$res = @include "../main.inc.php";
|
||||
}
|
||||
|
|
@ -72,185 +64,286 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
|
|||
*/
|
||||
|
||||
// Load translation files required by the page
|
||||
$langs->loadLangs(array("bankimport@bankimport"));
|
||||
$langs->loadLangs(array("bankimport@bankimport", "banks"));
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
|
||||
$now = dol_now();
|
||||
$max = getDolGlobalInt('MAIN_SIZE_SHORTLIST_LIMIT', 5);
|
||||
|
||||
// Security check - Protection if external user
|
||||
// Security check
|
||||
if (!$user->hasRight('bankimport', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
$socid = GETPOSTINT('socid');
|
||||
if (!empty($user->socid) && $user->socid > 0) {
|
||||
$action = '';
|
||||
$socid = $user->socid;
|
||||
}
|
||||
|
||||
// Initialize a technical object to manage hooks. Note that conf->hooks_modules contains array
|
||||
//$hookmanager->initHooks(array($object->element.'index'));
|
||||
|
||||
// Security check (enable the most restrictive one)
|
||||
//if ($user->socid > 0) accessforbidden();
|
||||
//if ($user->socid > 0) $socid = $user->socid;
|
||||
//if (!isModEnabled('bankimport')) {
|
||||
// accessforbidden('Module not enabled');
|
||||
//}
|
||||
//if (! $user->hasRight('bankimport', 'myobject', 'read')) {
|
||||
// accessforbidden();
|
||||
//}
|
||||
//restrictedArea($user, 'bankimport', 0, 'bankimport_myobject', 'myobject', '', 'rowid');
|
||||
//if (empty($user->admin)) {
|
||||
// accessforbidden('Must be admin');
|
||||
//}
|
||||
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
||||
// None
|
||||
|
||||
|
||||
/*
|
||||
* View
|
||||
*/
|
||||
|
||||
$form = new Form($db);
|
||||
$formfile = new FormFile($db);
|
||||
dol_include_once('/bankimport/class/bankstatement.class.php');
|
||||
|
||||
llxHeader("", $langs->trans("BankImportArea"), '', '', 0, 0, '', '', '', 'mod-bankimport page-index');
|
||||
|
||||
print load_fiche_titre($langs->trans("BankImportArea"), '', 'bankimport.png@bankimport');
|
||||
print load_fiche_titre($langs->trans("BankImportArea"), '', 'bank');
|
||||
|
||||
// Reminder: check if statements are outdated
|
||||
$reminderEnabled = getDolGlobalString('BANKIMPORT_REMINDER_ENABLED', '1');
|
||||
if ($reminderEnabled) {
|
||||
$reminderMonths = getDolGlobalInt('BANKIMPORT_REMINDER_MONTHS') ?: 3;
|
||||
$stmtCheck = new BankImportStatement($db);
|
||||
$lastEndDate = $stmtCheck->getLatestStatementEndDate();
|
||||
$thresholdDate = dol_time_plus_duree(dol_now(), -$reminderMonths, 'm');
|
||||
|
||||
if ($lastEndDate === null) {
|
||||
// No statements at all
|
||||
print '<div class="warning">';
|
||||
print img_warning().' '.$langs->trans("ReminderNoStatements");
|
||||
print ' <a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'">'.$langs->trans("UploadPDFStatement").'</a>';
|
||||
print '</div><br>';
|
||||
} elseif ($lastEndDate < $thresholdDate) {
|
||||
$monthsAgo = (int) round((dol_now() - $lastEndDate) / (30 * 24 * 3600));
|
||||
print '<div class="warning">';
|
||||
print img_warning().' '.$langs->trans("ReminderOutdatedStatements", dol_print_date($lastEndDate, 'day'), $monthsAgo);
|
||||
print ' <a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'">'.$langs->trans("UploadPDFStatement").'</a>';
|
||||
print '</div><br>';
|
||||
}
|
||||
}
|
||||
|
||||
print '<div class="fichecenter"><div class="fichethirdleft">';
|
||||
|
||||
// -----------------------------------------------
|
||||
// Widget: Letzte 10 importierte Buchungen
|
||||
// -----------------------------------------------
|
||||
$max = 10;
|
||||
|
||||
/* BEGIN MODULEBUILDER DRAFT MYOBJECT
|
||||
// Draft MyObject
|
||||
if (isModEnabled('bankimport') && $user->hasRight('bankimport', 'read')) {
|
||||
$langs->load("orders");
|
||||
$sql = "SELECT t.rowid, t.ref, t.date_trans, t.name, t.description, t.amount, t.currency, t.status";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX."bankimport_transaction as t";
|
||||
$sql .= " WHERE t.entity IN (".getEntity('banktransaction').")";
|
||||
$sql .= " ORDER BY t.date_trans DESC, t.rowid DESC";
|
||||
$sql .= $db->plimit($max, 0);
|
||||
|
||||
$sql = "SELECT c.rowid, c.ref, c.ref_client, c.total_ht, c.tva as total_tva, c.total_ttc, s.rowid as socid, s.nom as name, s.client, s.canvas";
|
||||
$sql.= ", s.code_client";
|
||||
$sql.= " FROM ".$db->prefix()."commande as c";
|
||||
$sql.= ", ".$db->prefix()."societe as s";
|
||||
$sql.= " WHERE c.fk_soc = s.rowid";
|
||||
$sql.= " AND c.fk_statut = 0";
|
||||
$sql.= " AND c.entity IN (".getEntity('commande').")";
|
||||
if ($socid) $sql.= " AND c.fk_soc = ".((int) $socid);
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$resql = $db->query($sql);
|
||||
if ($resql)
|
||||
{
|
||||
$total = 0;
|
||||
$num = $db->num_rows($resql);
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="4">';
|
||||
print $langs->trans("LastImportedTransactions");
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="3">'.$langs->trans("DraftMyObjects").($num?'<span class="badge marginleftonlyshort">'.$num.'</span>':'').'</th></tr>';
|
||||
|
||||
$var = true;
|
||||
if ($num > 0)
|
||||
{
|
||||
$i = 0;
|
||||
while ($i < $num)
|
||||
{
|
||||
|
||||
$obj = $db->fetch_object($resql);
|
||||
print '<tr class="oddeven"><td class="nowrap">';
|
||||
|
||||
$myobjectstatic->id=$obj->rowid;
|
||||
$myobjectstatic->ref=$obj->ref;
|
||||
$myobjectstatic->ref_client=$obj->ref_client;
|
||||
$myobjectstatic->total_ht = $obj->total_ht;
|
||||
$myobjectstatic->total_tva = $obj->total_tva;
|
||||
$myobjectstatic->total_ttc = $obj->total_ttc;
|
||||
|
||||
print $myobjectstatic->getNomUrl(1);
|
||||
print '</td>';
|
||||
print '<td class="nowrap">';
|
||||
print '</td>';
|
||||
print '<td class="right" class="nowrap">'.price($obj->total_ttc).'</td></tr>';
|
||||
$i++;
|
||||
$total += $obj->total_ttc;
|
||||
}
|
||||
if ($total>0)
|
||||
{
|
||||
|
||||
print '<tr class="liste_total"><td>'.$langs->trans("Total").'</td><td colspan="2" class="right">'.price($total)."</td></tr>";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("NoOrder").'</td></tr>';
|
||||
}
|
||||
print "</table><br>";
|
||||
|
||||
$db->free($resql);
|
||||
}
|
||||
else
|
||||
{
|
||||
dol_print_error($db);
|
||||
// Count total
|
||||
$sqlcount = "SELECT COUNT(*) as total FROM ".MAIN_DB_PREFIX."bankimport_transaction WHERE entity IN (".getEntity('banktransaction').")";
|
||||
$rescount = $db->query($sqlcount);
|
||||
if ($rescount) {
|
||||
$objcount = $db->fetch_object($rescount);
|
||||
if ($objcount->total > 0) {
|
||||
print '<a class="paddingleft" href="'.dol_buildpath('/bankimport/list.php', 1).'?mainmenu=bank&leftmenu=bankimport">';
|
||||
print '<span class="badge">'.$objcount->total.'</span>';
|
||||
print '</a>';
|
||||
}
|
||||
}
|
||||
END MODULEBUILDER DRAFT MYOBJECT */
|
||||
print '</th>';
|
||||
print '</tr>';
|
||||
|
||||
if ($resql) {
|
||||
$num = $db->num_rows($resql);
|
||||
|
||||
if ($num > 0) {
|
||||
$i = 0;
|
||||
while ($i < $num) {
|
||||
$obj = $db->fetch_object($resql);
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
|
||||
// Date
|
||||
print '<td class="nowraponall">';
|
||||
print dol_print_date($db->jdate($obj->date_trans), 'day');
|
||||
print '</td>';
|
||||
|
||||
// Name + Description
|
||||
print '<td class="tdoverflowmax200">';
|
||||
print '<a href="'.dol_buildpath('/bankimport/card.php', 1).'?id='.$obj->rowid.'&mainmenu=bank&leftmenu=bankimport">';
|
||||
print dol_escape_htmltag(dol_trunc($obj->name, 30));
|
||||
print '</a>';
|
||||
if ($obj->description) {
|
||||
print '<br><span class="opacitymedium small">'.dol_escape_htmltag(dol_trunc($obj->description, 40)).'</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Amount
|
||||
print '<td class="right nowraponall">';
|
||||
if ($obj->amount >= 0) {
|
||||
print '<span class="amount" style="color: green;">+'.price($obj->amount, 0, $langs, 1, -1, 2, $obj->currency).'</span>';
|
||||
} else {
|
||||
print '<span class="amount" style="color: red;">'.price($obj->amount, 0, $langs, 1, -1, 2, $obj->currency).'</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Status
|
||||
print '<td class="right nowraponall">';
|
||||
switch ($obj->status) {
|
||||
case 0:
|
||||
print '<span class="badge badge-status4 badge-status">'.$langs->trans("New").'</span>';
|
||||
break;
|
||||
case 1:
|
||||
print '<span class="badge badge-status1 badge-status">'.$langs->trans("Matched").'</span>';
|
||||
break;
|
||||
case 2:
|
||||
print '<span class="badge badge-status6 badge-status">'.$langs->trans("Reconciled").'</span>';
|
||||
break;
|
||||
case 9:
|
||||
print '<span class="badge badge-status5 badge-status">'.$langs->trans("Ignored").'</span>';
|
||||
break;
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="4" class="opacitymedium">'.$langs->trans("NoTransactionsInDatabase").'</td></tr>';
|
||||
}
|
||||
|
||||
$db->free($resql);
|
||||
} else {
|
||||
dol_print_error($db);
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Link "Alle anzeigen"
|
||||
if (!empty($objcount) && $objcount->total > 0) {
|
||||
print '<div class="right" style="margin-top: 5px;">';
|
||||
print '<a href="'.dol_buildpath('/bankimport/list.php', 1).'?mainmenu=bank&leftmenu=bankimport">';
|
||||
print $langs->trans("ShowAll").' »';
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
|
||||
print '</div><div class="fichetwothirdright">';
|
||||
|
||||
|
||||
/* BEGIN MODULEBUILDER LASTMODIFIED MYOBJECT
|
||||
// Last modified myobject
|
||||
if (isModEnabled('bankimport') && $user->hasRight('bankimport', 'read')) {
|
||||
$sql = "SELECT s.rowid, s.ref, s.label, s.date_creation, s.tms";
|
||||
$sql.= " FROM ".$db->prefix()."bankimport_myobject as s";
|
||||
$sql.= " WHERE s.entity IN (".getEntity($myobjectstatic->element).")";
|
||||
//if ($socid) $sql.= " AND s.rowid = $socid";
|
||||
$sql .= " ORDER BY s.tms DESC";
|
||||
$sql .= $db->plimit($max, 0);
|
||||
// -----------------------------------------------
|
||||
// Widget: Letzte 5 PDF-Kontoauszüge
|
||||
// -----------------------------------------------
|
||||
$maxpdf = 5;
|
||||
|
||||
$resql = $db->query($sql);
|
||||
if ($resql)
|
||||
{
|
||||
$num = $db->num_rows($resql);
|
||||
$i = 0;
|
||||
$sql2 = "SELECT s.rowid, s.statement_number, s.statement_year, s.iban, s.date_from, s.date_to,";
|
||||
$sql2 .= " s.opening_balance, s.closing_balance, s.filename, s.filepath, s.filesize, s.datec";
|
||||
$sql2 .= " FROM ".MAIN_DB_PREFIX."bankimport_statement as s";
|
||||
$sql2 .= " WHERE s.entity IN (".getEntity('bankstatement').")";
|
||||
$sql2 .= " ORDER BY s.datec DESC";
|
||||
$sql2 .= $db->plimit($maxpdf, 0);
|
||||
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="2">';
|
||||
print $langs->trans("BoxTitleLatestModifiedMyObjects", $max);
|
||||
print '</th>';
|
||||
print '<th class="right">'.$langs->trans("DateModificationShort").'</th>';
|
||||
print '</tr>';
|
||||
if ($num)
|
||||
{
|
||||
while ($i < $num)
|
||||
{
|
||||
$objp = $db->fetch_object($resql);
|
||||
$resql2 = $db->query($sql2);
|
||||
|
||||
$myobjectstatic->id=$objp->rowid;
|
||||
$myobjectstatic->ref=$objp->ref;
|
||||
$myobjectstatic->label=$objp->label;
|
||||
$myobjectstatic->status = $objp->status;
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th colspan="5">';
|
||||
print $langs->trans("LastPDFStatements");
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="nowrap">'.$myobjectstatic->getNomUrl(1).'</td>';
|
||||
print '<td class="right nowrap">';
|
||||
print "</td>";
|
||||
print '<td class="right nowrap">'.dol_print_date($db->jdate($objp->tms), 'day')."</td>";
|
||||
print '</tr>';
|
||||
$i++;
|
||||
}
|
||||
|
||||
$db->free($resql);
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("None").'</td></tr>';
|
||||
}
|
||||
print "</table><br>";
|
||||
// Count total
|
||||
$sqlcount2 = "SELECT COUNT(*) as total FROM ".MAIN_DB_PREFIX."bankimport_statement WHERE entity IN (".getEntity('bankstatement').")";
|
||||
$rescount2 = $db->query($sqlcount2);
|
||||
if ($rescount2) {
|
||||
$objcount2 = $db->fetch_object($rescount2);
|
||||
if ($objcount2->total > 0) {
|
||||
print '<a class="paddingleft" href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?mainmenu=bank&leftmenu=bankimport">';
|
||||
print '<span class="badge">'.$objcount2->total.'</span>';
|
||||
print '</a>';
|
||||
}
|
||||
}
|
||||
*/
|
||||
print '</th>';
|
||||
print '</tr>';
|
||||
|
||||
if ($resql2) {
|
||||
$num2 = $db->num_rows($resql2);
|
||||
|
||||
if ($num2 > 0) {
|
||||
$i = 0;
|
||||
while ($i < $num2) {
|
||||
$obj2 = $db->fetch_object($resql2);
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
|
||||
// Statement number / Year
|
||||
print '<td class="nowraponall">';
|
||||
print '<strong>'.dol_escape_htmltag($obj2->statement_number).'</strong>/'.$obj2->statement_year;
|
||||
print '</td>';
|
||||
|
||||
// IBAN (shortened)
|
||||
print '<td class="tdoverflowmax150">';
|
||||
if ($obj2->iban) {
|
||||
print dol_escape_htmltag(dol_trunc($obj2->iban, 20));
|
||||
} else {
|
||||
print '<span class="opacitymedium">-</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Period
|
||||
print '<td class="center nowraponall">';
|
||||
if ($obj2->date_from && $obj2->date_to) {
|
||||
print dol_print_date($db->jdate($obj2->date_from), 'day').' - '.dol_print_date($db->jdate($obj2->date_to), 'day');
|
||||
} elseif ($obj2->date_from) {
|
||||
print dol_print_date($db->jdate($obj2->date_from), 'day').' -';
|
||||
} else {
|
||||
print '<span class="opacitymedium">-</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Closing balance
|
||||
print '<td class="right nowraponall">';
|
||||
if ($obj2->closing_balance !== null && $obj2->closing_balance !== '') {
|
||||
$color = (float) $obj2->closing_balance >= 0 ? '' : 'color: red;';
|
||||
print '<span style="'.$color.'">'.price($obj2->closing_balance, 0, $langs, 1, -1, 2, 'EUR').'</span>';
|
||||
} else {
|
||||
print '<span class="opacitymedium">-</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Actions
|
||||
print '<td class="center nowraponall">';
|
||||
if ($obj2->filepath && file_exists($obj2->filepath)) {
|
||||
print '<a class="paddingright" href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?action=view&id='.$obj2->rowid.'&token='.newToken().'" target="_blank" title="'.$langs->trans("View").'">';
|
||||
print img_picto($langs->trans("View"), 'eye');
|
||||
print '</a>';
|
||||
|
||||
print '<a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?action=download&id='.$obj2->rowid.'&token='.newToken().'" title="'.$langs->trans("Download").'">';
|
||||
print img_picto($langs->trans("Download"), 'download');
|
||||
print '</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
$i++;
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="5" class="opacitymedium">'.$langs->trans("NoPDFStatementsFound").'</td></tr>';
|
||||
}
|
||||
|
||||
$db->free($resql2);
|
||||
} else {
|
||||
dol_print_error($db);
|
||||
}
|
||||
|
||||
print '</table>';
|
||||
|
||||
// Links
|
||||
print '<div class="right" style="margin-top: 5px;">';
|
||||
if (!empty($objcount2) && $objcount2->total > 0) {
|
||||
print '<a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?mainmenu=bank&leftmenu=bankimport">';
|
||||
print $langs->trans("ShowAll");
|
||||
print '</a>';
|
||||
print ' | ';
|
||||
}
|
||||
print '<a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?mainmenu=bank&leftmenu=bankimport">';
|
||||
print $langs->trans("UploadNew").' »';
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
|
||||
|
||||
print '</div></div>';
|
||||
|
||||
|
|
|
|||
24
card.php
Normal file → Executable file
24
card.php
Normal file → Executable file
|
|
@ -61,6 +61,11 @@ $ref = GETPOST('ref', 'alpha');
|
|||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('bankimport', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
/*
|
||||
* Actions
|
||||
*/
|
||||
|
|
@ -245,6 +250,25 @@ if ($object->id > 0) {
|
|||
print '</tr>';
|
||||
}
|
||||
|
||||
// Linked PDF statement
|
||||
if (!empty($object->fk_statement)) {
|
||||
dol_include_once('/bankimport/class/bankstatement.class.php');
|
||||
$stmt = new BankImportStatement($db);
|
||||
$stmt->fetch($object->fk_statement);
|
||||
print '<tr>';
|
||||
print '<td>'.$langs->trans("PDFStatement").'</td>';
|
||||
print '<td>';
|
||||
print '<a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?action=view&id='.$stmt->id.'&token='.newToken().'" target="_blank">';
|
||||
print img_picto($langs->trans("ViewPDFStatement"), 'pdf').' ';
|
||||
print $langs->trans("StatementNumber").' '.$stmt->statement_number.'/'.$stmt->statement_year;
|
||||
print '</a>';
|
||||
if ($stmt->date_from && $stmt->date_to) {
|
||||
print ' <span class="opacitymedium">('.dol_print_date($stmt->date_from, 'day').' - '.dol_print_date($stmt->date_to, 'day').')</span>';
|
||||
}
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
}
|
||||
|
||||
// Linked third party
|
||||
if ($object->fk_societe > 0) {
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
|
|
|
|||
0
class/bankimportcron.class.php
Normal file → Executable file
0
class/bankimportcron.class.php
Normal file → Executable file
268
class/bankstatement.class.php
Normal file → Executable file
268
class/bankstatement.class.php
Normal file → Executable file
|
|
@ -480,6 +480,192 @@ class BankImportStatement extends CommonObject
|
|||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse PDF bank statement metadata using pdfinfo and pdftotext
|
||||
*
|
||||
* Extracts: statement number, year, IBAN, date range, opening/closing balance,
|
||||
* account number, bank name, statement date.
|
||||
*
|
||||
* @param string $filepath Path to PDF file
|
||||
* @return array|false Array with extracted data or false on failure
|
||||
*/
|
||||
public static function parsePdfMetadata($filepath)
|
||||
{
|
||||
if (!file_exists($filepath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'statement_number' => '',
|
||||
'statement_year' => 0,
|
||||
'pdf_number' => '', // Original statement number from PDF (e.g. "1" from Nr. 1/2025)
|
||||
'pdf_year' => 0, // Original year from PDF
|
||||
'iban' => '',
|
||||
'date_from' => null,
|
||||
'date_to' => null,
|
||||
'opening_balance' => null,
|
||||
'closing_balance' => null,
|
||||
'statement_date' => null,
|
||||
'account_number' => '',
|
||||
'bank_name' => '',
|
||||
'author' => '',
|
||||
);
|
||||
|
||||
$escapedPath = escapeshellarg($filepath);
|
||||
|
||||
// 1. Extract metadata via pdfinfo
|
||||
$pdfinfo = array();
|
||||
exec("pdfinfo ".$escapedPath." 2>/dev/null", $pdfinfo);
|
||||
|
||||
foreach ($pdfinfo as $line) {
|
||||
if (preg_match('/^Title:\s+(.+)$/', $line, $m)) {
|
||||
// Title format: "000000000000000000000013438147 001/2025" or "Kontoauszug 13438147"
|
||||
if (preg_match('/(\d+)\s+(\d+)\/(\d{4})/', $m[1], $tm)) {
|
||||
$result['account_number'] = ltrim($tm[1], '0');
|
||||
$result['pdf_number'] = (string) intval($tm[2]);
|
||||
$result['pdf_year'] = (int) $tm[3];
|
||||
}
|
||||
}
|
||||
if (preg_match('/^Author:\s+(.+)$/', $line, $m)) {
|
||||
$result['author'] = trim($m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extract text via pdftotext
|
||||
$text = '';
|
||||
exec("pdftotext -layout ".$escapedPath." - 2>/dev/null", $textlines);
|
||||
$text = implode("\n", $textlines);
|
||||
|
||||
// Statement number from text (fallback if not in metadata)
|
||||
if (empty($result['pdf_number']) && preg_match('/Nr\.\s+(\d+)\/(\d{4})/', $text, $m)) {
|
||||
$result['pdf_number'] = (string) intval($m[1]);
|
||||
$result['pdf_year'] = (int) $m[2];
|
||||
}
|
||||
|
||||
// IBAN
|
||||
if (preg_match('/IBAN:\s*([A-Z]{2}\d{2}\s*[\d\s]+)/', $text, $m)) {
|
||||
$result['iban'] = preg_replace('/\s+/', ' ', trim($m[1]));
|
||||
}
|
||||
|
||||
// Account number (fallback)
|
||||
if (empty($result['account_number']) && preg_match('/Kontonummer\s+(\d+)/', $text, $m)) {
|
||||
$result['account_number'] = $m[1];
|
||||
}
|
||||
|
||||
// Date range from Kontoabschluss
|
||||
if (preg_match('/Kontoabschluss vom (\d{2}\.\d{2}\.\d{4}) bis (\d{2}\.\d{2}\.\d{4})/', $text, $m)) {
|
||||
$dateFrom = DateTime::createFromFormat('d.m.Y', $m[1]);
|
||||
$dateTo = DateTime::createFromFormat('d.m.Y', $m[2]);
|
||||
if ($dateFrom) {
|
||||
$result['date_from'] = $dateFrom->setTime(0, 0, 0)->getTimestamp();
|
||||
}
|
||||
if ($dateTo) {
|
||||
$result['date_to'] = $dateTo->setTime(0, 0, 0)->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
// Statement date (erstellt am)
|
||||
if (preg_match('/erstellt am\s+(\d{2}\.\d{2}\.\d{4})/', $text, $m)) {
|
||||
$stmtDate = DateTime::createFromFormat('d.m.Y', $m[1]);
|
||||
if ($stmtDate) {
|
||||
$result['statement_date'] = $stmtDate->setTime(0, 0, 0)->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
// Opening balance: "alter Kontostand [vom DD.MM.YYYY] X.XXX,XX H/S"
|
||||
if (preg_match('/alter Kontostand(?:\s+vom\s+\d{2}\.\d{2}\.\d{4})?\s+([\d.,]+)\s+(H|S)/', $text, $m)) {
|
||||
$amount = self::parseGermanAmount($m[1]);
|
||||
if ($m[2] === 'S') {
|
||||
$amount = -$amount;
|
||||
}
|
||||
$result['opening_balance'] = $amount;
|
||||
}
|
||||
|
||||
// Closing balance: "neuer Kontostand vom DD.MM.YYYY X.XXX,XX H/S"
|
||||
if (preg_match('/neuer Kontostand(?:\s+vom\s+\d{2}\.\d{2}\.\d{4})?\s+([\d.,]+)\s+(H|S)/', $text, $m)) {
|
||||
$amount = self::parseGermanAmount($m[1]);
|
||||
if ($m[2] === 'S') {
|
||||
$amount = -$amount;
|
||||
}
|
||||
$result['closing_balance'] = $amount;
|
||||
}
|
||||
|
||||
// Bank name (first line that contains "Bank" or known patterns)
|
||||
if (preg_match('/(?:VR\s*B\s*ank|Volksbank|Raiffeisenbank|Sparkasse)[^\n]*/i', $text, $m)) {
|
||||
$bankName = trim($m[0]);
|
||||
// Fix OCR artifacts: single chars separated by spaces ("V R B a n k" → "VRBank")
|
||||
// Strategy: collapse all single-space gaps between word chars that look like OCR splitting
|
||||
$bankName = preg_replace('/\b(\w) (\w) (\w) (\w)\b/', '$1$2$3$4', $bankName);
|
||||
$bankName = preg_replace('/\b(\w) (\w) (\w)\b/', '$1$2$3', $bankName);
|
||||
$bankName = preg_replace('/\b(\w) (\w)\b/', '$1$2', $bankName);
|
||||
// Fix common OCR pattern "VR B ank" → "VR Bank", "S chleswig" → "Schleswig"
|
||||
$bankName = preg_replace('/\bB ank\b/', 'Bank', $bankName);
|
||||
$bankName = preg_replace('/\bS (\w)/', 'S$1', $bankName);
|
||||
$bankName = preg_replace('/\bW (\w)/', 'W$1', $bankName);
|
||||
// Clean up multiple spaces and trim address parts after comma
|
||||
$bankName = preg_replace('/\s{2,}/', ' ', $bankName);
|
||||
$bankName = preg_replace('/,.*$/', '', $bankName);
|
||||
$result['bank_name'] = trim($bankName);
|
||||
}
|
||||
|
||||
// Derive statement_number (=month) and statement_year from end date of period
|
||||
if ($result['date_to']) {
|
||||
$result['statement_number'] = (string) intval(date('m', $result['date_to']));
|
||||
$result['statement_year'] = (int) date('Y', $result['date_to']);
|
||||
} elseif ($result['date_from']) {
|
||||
$result['statement_number'] = (string) intval(date('m', $result['date_from']));
|
||||
$result['statement_year'] = (int) date('Y', $result['date_from']);
|
||||
} elseif (!empty($result['pdf_year'])) {
|
||||
// Fallback to PDF metadata if no date range
|
||||
$result['statement_number'] = $result['pdf_number'];
|
||||
$result['statement_year'] = $result['pdf_year'];
|
||||
}
|
||||
|
||||
// Validate: at least statement number or IBAN must be present
|
||||
if (empty($result['statement_number']) && empty($result['iban'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a German formatted amount (e.g., "3.681,45" → 3681.45)
|
||||
*
|
||||
* @param string $amount German formatted amount string
|
||||
* @return float Parsed amount
|
||||
*/
|
||||
private static function parseGermanAmount($amount)
|
||||
{
|
||||
$amount = str_replace('.', '', $amount); // Remove thousands separator
|
||||
$amount = str_replace(',', '.', $amount); // Convert decimal separator
|
||||
return (float) $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a clean filename for a PDF statement
|
||||
*
|
||||
* @param array $parsed Parsed metadata from parsePdfMetadata()
|
||||
* @return string Generated filename
|
||||
*/
|
||||
public static function generateFilename($parsed)
|
||||
{
|
||||
$bank = 'Bank';
|
||||
if (!empty($parsed['bank_name'])) {
|
||||
// Shorten bank name - take first meaningful words
|
||||
$bank = preg_replace('/\s+(eG|AG|e\.G\.).*$/', '', $parsed['bank_name']);
|
||||
$bank = preg_replace('/[^a-zA-Z0-9äöüÄÖÜß-]/', '_', $bank);
|
||||
$bank = preg_replace('/_+/', '_', $bank);
|
||||
$bank = trim($bank, '_');
|
||||
}
|
||||
|
||||
$account = !empty($parsed['account_number']) ? $parsed['account_number'] : 'Konto';
|
||||
$year = !empty($parsed['statement_year']) ? $parsed['statement_year'] : date('Y');
|
||||
$nr = !empty($parsed['statement_number']) ? str_pad($parsed['statement_number'], 3, '0', STR_PAD_LEFT) : '000';
|
||||
|
||||
return sprintf('%s_%s_%d_%s.pdf', $bank, $account, $year, $nr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next available statement number for a year
|
||||
*
|
||||
|
|
@ -501,4 +687,86 @@ class BankImportStatement extends CommonObject
|
|||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end date (date_to) of the most recent statement
|
||||
*
|
||||
* @return int|null Timestamp of latest date_to, or null if none
|
||||
*/
|
||||
public function getLatestStatementEndDate()
|
||||
{
|
||||
$sql = "SELECT MAX(date_to) as last_date";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX."bankimport_statement";
|
||||
$sql .= " WHERE entity = ".((int) $this->entity);
|
||||
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
$obj = $this->db->fetch_object($resql);
|
||||
if ($obj->last_date) {
|
||||
return $this->db->jdate($obj->last_date);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of years that have stored statements
|
||||
*
|
||||
* @return array Array of years (descending)
|
||||
*/
|
||||
public function getAvailableYears()
|
||||
{
|
||||
$sql = "SELECT DISTINCT statement_year";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX."bankimport_statement";
|
||||
$sql .= " WHERE entity = ".((int) $this->entity);
|
||||
$sql .= " ORDER BY statement_year DESC";
|
||||
|
||||
$result = array();
|
||||
$resql = $this->db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $this->db->fetch_object($resql)) {
|
||||
$result[(int) $obj->statement_year] = (string) $obj->statement_year;
|
||||
}
|
||||
$this->db->free($resql);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link transactions to this statement based on date range and IBAN
|
||||
*
|
||||
* Updates all transactions that fall within the statement's date range
|
||||
* and match the IBAN, setting their fk_statement to this statement's ID.
|
||||
*
|
||||
* @return int Number of linked transactions, or -1 on error
|
||||
*/
|
||||
public function linkTransactions()
|
||||
{
|
||||
if (empty($this->id) || empty($this->date_from) || empty($this->date_to)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."bankimport_transaction SET";
|
||||
$sql .= " fk_statement = ".((int) $this->id);
|
||||
$sql .= " WHERE entity = ".((int) $this->entity);
|
||||
$sql .= " AND date_trans >= '".$this->db->idate($this->date_from)."'";
|
||||
$sql .= " AND date_trans <= '".$this->db->idate($this->date_to)."'";
|
||||
$sql .= " AND fk_statement IS NULL"; // Don't overwrite existing links
|
||||
|
||||
// Match by IBAN if available
|
||||
if (!empty($this->iban)) {
|
||||
$ibanClean = preg_replace('/\s+/', '', $this->iban);
|
||||
$sql .= " AND REPLACE(iban, ' ', '') = '".$this->db->escape($ibanClean)."'";
|
||||
}
|
||||
|
||||
dol_syslog(get_class($this)."::linkTransactions", LOG_DEBUG);
|
||||
$resql = $this->db->query($sql);
|
||||
|
||||
if ($resql) {
|
||||
return $this->db->affected_rows($resql);
|
||||
} else {
|
||||
$this->error = $this->db->lasterror();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
class/banktransaction.class.php
Normal file → Executable file
7
class/banktransaction.class.php
Normal file → Executable file
|
|
@ -161,6 +161,11 @@ class BankImportTransaction extends CommonObject
|
|||
*/
|
||||
public $fk_societe;
|
||||
|
||||
/**
|
||||
* @var int Link to llx_bankimport_statement
|
||||
*/
|
||||
public $fk_statement;
|
||||
|
||||
/**
|
||||
* @var int Status (0=new, 1=matched, 2=reconciled, 9=ignored)
|
||||
*/
|
||||
|
|
@ -335,6 +340,7 @@ class BankImportTransaction extends CommonObject
|
|||
$this->fk_don = $obj->fk_don;
|
||||
$this->fk_loan = $obj->fk_loan;
|
||||
$this->fk_societe = $obj->fk_societe;
|
||||
$this->fk_statement = $obj->fk_statement;
|
||||
$this->status = $obj->status;
|
||||
$this->import_key = $obj->import_key;
|
||||
$this->fk_user_creat = $obj->fk_user_creat;
|
||||
|
|
@ -392,6 +398,7 @@ class BankImportTransaction extends CommonObject
|
|||
$sql .= " fk_don = ".($this->fk_don > 0 ? ((int) $this->fk_don) : "NULL").",";
|
||||
$sql .= " fk_loan = ".($this->fk_loan > 0 ? ((int) $this->fk_loan) : "NULL").",";
|
||||
$sql .= " fk_societe = ".($this->fk_societe > 0 ? ((int) $this->fk_societe) : "NULL").",";
|
||||
$sql .= " fk_statement = ".($this->fk_statement > 0 ? ((int) $this->fk_statement) : "NULL").",";
|
||||
$sql .= " status = ".((int) $this->status).",";
|
||||
$sql .= " fk_user_modif = ".((int) $user->id).",";
|
||||
$sql .= " note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
||||
|
|
|
|||
0
class/fints.class.php
Normal file → Executable file
0
class/fints.class.php
Normal file → Executable file
0
composer.json
Normal file → Executable file
0
composer.json
Normal file → Executable file
0
composer.lock
generated
Normal file → Executable file
0
composer.lock
generated
Normal file → Executable file
|
|
@ -76,7 +76,7 @@ class modBankImport extends DolibarrModules
|
|||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@bankimport'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '1.0';
|
||||
$this->version = '1.1';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ class modBankImport extends DolibarrModules
|
|||
// If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
|
||||
// If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
|
||||
// To use a supported fa-xxx css style of font awesome, use this->picto='xxx'
|
||||
$this->picto = 'fa-file';
|
||||
$this->picto = 'fa-money-check-alt';
|
||||
|
||||
// Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
|
||||
$this->module_parts = array(
|
||||
|
|
@ -292,146 +292,98 @@ class modBankImport extends DolibarrModules
|
|||
// Permissions provided by this module
|
||||
$this->rights = array();
|
||||
$r = 0;
|
||||
// Add here entries to declare new permissions
|
||||
/* BEGIN MODULEBUILDER PERMISSIONS */
|
||||
/*
|
||||
$o = 1;
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 1); // Permission id (must not be already used)
|
||||
$this->rights[$r][1] = 'Read objects of BankImport'; // Permission label
|
||||
$this->rights[$r][4] = 'myobject';
|
||||
$this->rights[$r][5] = 'read'; // In php code, permission will be checked by test if ($user->hasRight('bankimport', 'myobject', 'read'))
|
||||
|
||||
// $user->hasRight('bankimport', 'read')
|
||||
$this->rights[$r][0] = $this->numero . '01';
|
||||
$this->rights[$r][1] = 'PermBankImportRead';
|
||||
$this->rights[$r][2] = 'r';
|
||||
$this->rights[$r][3] = 1; // Default enabled
|
||||
$this->rights[$r][4] = 'read';
|
||||
$r++;
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 2); // Permission id (must not be already used)
|
||||
$this->rights[$r][1] = 'Create/Update objects of BankImport'; // Permission label
|
||||
$this->rights[$r][4] = 'myobject';
|
||||
$this->rights[$r][5] = 'write'; // In php code, permission will be checked by test if ($user->hasRight('bankimport', 'myobject', 'write'))
|
||||
|
||||
// $user->hasRight('bankimport', 'write')
|
||||
$this->rights[$r][0] = $this->numero . '02';
|
||||
$this->rights[$r][1] = 'PermBankImportWrite';
|
||||
$this->rights[$r][2] = 'w';
|
||||
$this->rights[$r][3] = 0;
|
||||
$this->rights[$r][4] = 'write';
|
||||
$r++;
|
||||
$this->rights[$r][0] = $this->numero . sprintf("%02d", ($o * 10) + 3); // Permission id (must not be already used)
|
||||
$this->rights[$r][1] = 'Delete objects of BankImport'; // Permission label
|
||||
$this->rights[$r][4] = 'myobject';
|
||||
$this->rights[$r][5] = 'delete'; // In php code, permission will be checked by test if ($user->hasRight('bankimport', 'myobject', 'delete'))
|
||||
|
||||
// $user->hasRight('bankimport', 'delete')
|
||||
$this->rights[$r][0] = $this->numero . '03';
|
||||
$this->rights[$r][1] = 'PermBankImportDelete';
|
||||
$this->rights[$r][2] = 'd';
|
||||
$this->rights[$r][3] = 0;
|
||||
$this->rights[$r][4] = 'delete';
|
||||
$r++;
|
||||
*/
|
||||
/* END MODULEBUILDER PERMISSIONS */
|
||||
|
||||
|
||||
// Main menu entries to add
|
||||
$this->menu = array();
|
||||
$r = 0;
|
||||
// Add here entries to declare new menus
|
||||
/* BEGIN MODULEBUILDER TOPMENU */
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => '', // Will be stored into mainmenu + leftmenu. Use '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
|
||||
'type' => 'top', // This is a Top menu entry
|
||||
'titre' => 'ModuleBankImportName',
|
||||
'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle"'),
|
||||
'mainmenu' => 'bankimport',
|
||||
'leftmenu' => '',
|
||||
'url' => '/bankimport/bankimportindex.php',
|
||||
'langs' => 'bankimport@bankimport', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("bankimport")', // Define condition to show or hide menu entry. Use 'isModEnabled("bankimport")' if entry must be visible if module is enabled.
|
||||
'perms' => '1', // Use 'perms'=>'$user->hasRight("bankimport", "myobject", "read")' if you want your menu with a permission rules
|
||||
'target' => '',
|
||||
'user' => 2, // 0=Menu for internal users, 1=external users, 2=both
|
||||
);
|
||||
/* END MODULEBUILDER TOPMENU */
|
||||
|
||||
/* BEGIN MODULEBUILDER LEFTMENU MYOBJECT */
|
||||
// Left menu entries under "Banken und Kasse" (mainmenu=bank)
|
||||
$r = 0;
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=bankimport',
|
||||
'fk_menu' => 'fk_mainmenu=bank',
|
||||
'type' => 'left',
|
||||
'titre' => 'BankImportMenu',
|
||||
'prefix' => img_picto('', 'download', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'bank',
|
||||
'leftmenu' => 'bankimport',
|
||||
'url' => '/bankimport/bankimportindex.php?mainmenu=bank&leftmenu=bankimport',
|
||||
'langs' => 'bankimport@bankimport',
|
||||
'position' => 200,
|
||||
'enabled' => 'isModEnabled("bankimport")',
|
||||
'perms' => '$user->hasRight("bankimport", "read")',
|
||||
'target' => '',
|
||||
'user' => 2,
|
||||
);
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=bank,fk_leftmenu=bankimport',
|
||||
'type' => 'left',
|
||||
'titre' => 'BankStatements',
|
||||
'prefix' => img_picto('', 'bank_account', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'bankimport',
|
||||
'mainmenu' => 'bank',
|
||||
'leftmenu' => 'bankimport_statements',
|
||||
'url' => '/bankimport/statements.php',
|
||||
'url' => '/bankimport/statements.php?mainmenu=bank&leftmenu=bankimport',
|
||||
'langs' => 'bankimport@bankimport',
|
||||
'position' => 1001,
|
||||
'position' => 201,
|
||||
'enabled' => 'isModEnabled("bankimport")',
|
||||
'perms' => '1',
|
||||
'perms' => '$user->hasRight("bankimport", "write")',
|
||||
'target' => '',
|
||||
'user' => 2,
|
||||
);
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=bankimport',
|
||||
'fk_menu' => 'fk_mainmenu=bank,fk_leftmenu=bankimport',
|
||||
'type' => 'left',
|
||||
'titre' => 'TransactionList',
|
||||
'prefix' => img_picto('', 'list', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'bankimport',
|
||||
'mainmenu' => 'bank',
|
||||
'leftmenu' => 'bankimport_transactions',
|
||||
'url' => '/bankimport/list.php',
|
||||
'url' => '/bankimport/list.php?mainmenu=bank&leftmenu=bankimport',
|
||||
'langs' => 'bankimport@bankimport',
|
||||
'position' => 1002,
|
||||
'position' => 202,
|
||||
'enabled' => 'isModEnabled("bankimport")',
|
||||
'perms' => '1',
|
||||
'perms' => '$user->hasRight("bankimport", "read")',
|
||||
'target' => '',
|
||||
'user' => 2,
|
||||
);
|
||||
$this->menu[$r++] = array(
|
||||
'fk_menu' => 'fk_mainmenu=bankimport',
|
||||
'fk_menu' => 'fk_mainmenu=bank,fk_leftmenu=bankimport',
|
||||
'type' => 'left',
|
||||
'titre' => 'PDFStatements',
|
||||
'prefix' => img_picto('', 'pdf', 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'bankimport',
|
||||
'mainmenu' => 'bank',
|
||||
'leftmenu' => 'bankimport_pdfstatements',
|
||||
'url' => '/bankimport/pdfstatements.php',
|
||||
'url' => '/bankimport/pdfstatements.php?mainmenu=bank&leftmenu=bankimport',
|
||||
'langs' => 'bankimport@bankimport',
|
||||
'position' => 1003,
|
||||
'position' => 203,
|
||||
'enabled' => 'isModEnabled("bankimport")',
|
||||
'perms' => '1',
|
||||
'perms' => '$user->hasRight("bankimport", "read")',
|
||||
'target' => '',
|
||||
'user' => 2,
|
||||
);
|
||||
/*
|
||||
$this->menu[$r++]=array(
|
||||
'fk_menu' => 'fk_mainmenu=bankimport', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
|
||||
'type' => 'left', // This is a Left menu entry
|
||||
'titre' => 'MyObject',
|
||||
'prefix' => img_picto('', $this->picto, 'class="pictofixedwidth valignmiddle paddingright"'),
|
||||
'mainmenu' => 'bankimport',
|
||||
'leftmenu' => 'myobject',
|
||||
'url' => '/bankimport/bankimportindex.php',
|
||||
'langs' => 'bankimport@bankimport', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("bankimport")', // Define condition to show or hide menu entry. Use 'isModEnabled("bankimport")' if entry must be visible if module is enabled.
|
||||
'perms' => '$user->hasRight("bankimport", "myobject", "read")',
|
||||
'target' => '',
|
||||
'user' => 2, // 0=Menu for internal users, 1=external users, 2=both
|
||||
'object' => 'MyObject'
|
||||
);
|
||||
$this->menu[$r++]=array(
|
||||
'fk_menu' => 'fk_mainmenu=bankimport,fk_leftmenu=myobject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
|
||||
'type' => 'left', // This is a Left menu entry
|
||||
'titre' => 'New_MyObject',
|
||||
'mainmenu' => 'bankimport',
|
||||
'leftmenu' => 'bankimport_myobject_new',
|
||||
'url' => '/bankimport/myobject_card.php?action=create',
|
||||
'langs' => 'bankimport@bankimport', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("bankimport")', // Define condition to show or hide menu entry. Use 'isModEnabled("bankimport")' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
|
||||
'perms' => '$user->hasRight("bankimport", "myobject", "write")'
|
||||
'target' => '',
|
||||
'user' => 2, // 0=Menu for internal users, 1=external users, 2=both
|
||||
'object' => 'MyObject'
|
||||
);
|
||||
$this->menu[$r++]=array(
|
||||
'fk_menu' => 'fk_mainmenu=bankimport,fk_leftmenu=myobject', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
|
||||
'type' => 'left', // This is a Left menu entry
|
||||
'titre' => 'List_MyObject',
|
||||
'mainmenu' => 'bankimport',
|
||||
'leftmenu' => 'bankimport_myobject_list',
|
||||
'url' => '/bankimport/myobject_list.php',
|
||||
'langs' => 'bankimport@bankimport', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
|
||||
'position' => 1000 + $r,
|
||||
'enabled' => 'isModEnabled("bankimport")', // Define condition to show or hide menu entry. Use 'isModEnabled("bankimport")' if entry must be visible if module is enabled.
|
||||
'perms' => '$user->hasRight("bankimport", "myobject", "read")'
|
||||
'target' => '',
|
||||
'user' => 2, // 0=Menu for internal users, 1=external users, 2=both
|
||||
'object' => 'MyObject'
|
||||
);
|
||||
*/
|
||||
/* END MODULEBUILDER LEFTMENU MYOBJECT */
|
||||
|
||||
|
||||
// Exports profiles provided by this module
|
||||
|
|
|
|||
62
langs/de_DE/bankimport.lang
Normal file → Executable file
62
langs/de_DE/bankimport.lang
Normal file → Executable file
|
|
@ -174,6 +174,13 @@ DownloadPDF = PDF herunterladen
|
|||
NoPDFStatementsFound = Keine PDF-Kontoauszüge gefunden
|
||||
PDFStatementsImported = %s Kontoauszüge importiert
|
||||
StatementAlreadyExists = Kontoauszug bereits vorhanden
|
||||
DeleteStatement = Kontoauszug löschen
|
||||
ConfirmDeleteStatement = Möchten Sie den Kontoauszug %s wirklich löschen?
|
||||
StatementsInYear = Kontoauszüge im Jahr %s
|
||||
AllStatements = Alle Kontoauszüge
|
||||
OpeningBalance = Anfangssaldo
|
||||
ClosingBalance = Endsaldo
|
||||
StatementUploaded = Kontoauszug erfolgreich hochgeladen
|
||||
|
||||
#
|
||||
# Über-Seite
|
||||
|
|
@ -183,6 +190,59 @@ BankImportAbout = Über Bankimport
|
|||
BankImportAboutPage = Bankimport Info-Seite
|
||||
|
||||
#
|
||||
# Startseite
|
||||
# Startseite / Dashboard
|
||||
#
|
||||
BankImportArea = Bankimport Übersicht
|
||||
LastImportedTransactions = Letzte importierte Buchungen
|
||||
LastPDFStatements = Letzte PDF-Kontoauszüge
|
||||
ShowAll = Alle anzeigen
|
||||
UploadNew = Neuen hochladen
|
||||
BankImportMenu = Bankimport
|
||||
|
||||
#
|
||||
# PDF Kontoauszüge Seite
|
||||
#
|
||||
PDFStatementsInfo = PDF-Kontoauszüge
|
||||
PDFStatementsInfoDesc = Hier können Sie Ihre PDF-Kontoauszüge hochladen, verwalten und einsehen. Die Auszüge werden sicher gespeichert und können jederzeit heruntergeladen werden.
|
||||
UploadPDFStatement = PDF-Kontoauszüge hochladen
|
||||
|
||||
#
|
||||
# Upload-Modus
|
||||
#
|
||||
UploadMode = Upload-Modus
|
||||
UploadModeAuto = Automatisch erkennen
|
||||
UploadModeManual = Manuelle Eingabe
|
||||
PdfAutoDetected = PDF-Metadaten automatisch erkannt
|
||||
ErrorNoFileUploaded = Keine Datei hochgeladen
|
||||
ErrorOnlyPDFAllowed = Nur PDF-Dateien sind erlaubt
|
||||
ErrorFileTooLarge = Datei ist zu groß (max. 10 MB)
|
||||
ErrorFailedToSaveFile = Datei konnte nicht gespeichert werden
|
||||
TransactionsLinked = %s Buchungen dem Kontoauszug zugeordnet
|
||||
StatementsUploaded = %s Kontoauszüge erfolgreich hochgeladen
|
||||
MultipleFilesHint = Sie können mehrere PDF-Dateien gleichzeitig auswählen (Strg+Klick oder Shift+Klick)
|
||||
|
||||
#
|
||||
# Admin - PDF Upload
|
||||
#
|
||||
PDFUploadSettings = PDF-Upload Einstellungen
|
||||
DefaultUploadMode = Standard Upload-Modus
|
||||
DefaultUploadModeHelp = Automatisch: Metadaten werden aus dem PDF extrahiert. Manuell: Alle Felder müssen von Hand ausgefüllt werden.
|
||||
ReminderEnabled = Erinnerung aktivieren
|
||||
ReminderEnabledHelp = Zeigt eine Warnung wenn Kontoauszüge nicht aktuell sind
|
||||
ReminderMonths = Erinnerung nach (Monate)
|
||||
ReminderMonthsHelp = Warnung anzeigen, wenn der letzte Kontoauszug älter als X Monate ist
|
||||
ReminderNoStatements = Es wurden noch keine Kontoauszüge hochgeladen. Bitte laden Sie Ihre Kontoauszüge hoch.
|
||||
ReminderOutdatedStatements = Der letzte Kontoauszug endet am %s (vor %s Monaten). Bitte laden Sie aktuelle Kontoauszüge hoch.
|
||||
|
||||
#
|
||||
# Kontoauszug-Verknüpfung
|
||||
#
|
||||
PDFStatement = PDF-Kontoauszug
|
||||
ViewPDFStatement = PDF-Kontoauszug anzeigen
|
||||
|
||||
#
|
||||
# Berechtigungen
|
||||
#
|
||||
PermBankImportRead = Bankimport: Buchungen und Kontoauszüge ansehen
|
||||
PermBankImportWrite = Bankimport: Kontoauszüge abrufen und PDF hochladen
|
||||
PermBankImportDelete = Bankimport: Buchungen und Kontoauszüge löschen
|
||||
|
|
|
|||
21
list.php
Normal file → Executable file
21
list.php
Normal file → Executable file
|
|
@ -62,6 +62,11 @@ $confirm = GETPOST('confirm', 'alpha');
|
|||
$toselect = GETPOST('toselect', 'array');
|
||||
$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'bankimporttransactionlist';
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('bankimport', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Search parameters
|
||||
$search_ref = GETPOST('search_ref', 'alpha');
|
||||
$search_iban = GETPOST('search_iban', 'alpha');
|
||||
|
|
@ -206,6 +211,10 @@ print ' - ';
|
|||
print '<input type="text" class="flat maxwidth50" name="search_amount_max" placeholder="Max" value="'.dol_escape_htmltag($search_amount_max).'">';
|
||||
print '</td>';
|
||||
|
||||
// Statement
|
||||
print '<td class="liste_titre">';
|
||||
print '</td>';
|
||||
|
||||
// Status
|
||||
print '<td class="liste_titre center">';
|
||||
$statusArray = array(
|
||||
|
|
@ -235,6 +244,7 @@ print_liste_field_titre($langs->trans("Date"), $_SERVER["PHP_SELF"], "date_trans
|
|||
print_liste_field_titre($langs->trans("Counterparty"), $_SERVER["PHP_SELF"], "name", "", $param, "", $sortfield, $sortorder);
|
||||
print_liste_field_titre($langs->trans("Description"), $_SERVER["PHP_SELF"], "description", "", $param, "", $sortfield, $sortorder);
|
||||
print_liste_field_titre($langs->trans("Amount"), $_SERVER["PHP_SELF"], "amount", "", $param, 'class="right"', $sortfield, $sortorder);
|
||||
print_liste_field_titre($langs->trans("PDFStatement"), $_SERVER["PHP_SELF"], "fk_statement", "", $param, 'class="center"', $sortfield, $sortorder);
|
||||
print_liste_field_titre($langs->trans("Status"), $_SERVER["PHP_SELF"], "status", "", $param, 'class="center"', $sortfield, $sortorder);
|
||||
print_liste_field_titre('', $_SERVER["PHP_SELF"], "", "", $param, 'class="center"', $sortfield, $sortorder);
|
||||
print '</tr>';
|
||||
|
|
@ -278,6 +288,15 @@ if (is_array($records) && count($records) > 0) {
|
|||
}
|
||||
print '</td>';
|
||||
|
||||
// Statement link
|
||||
print '<td class="center nowraponall">';
|
||||
if (!empty($obj->fk_statement)) {
|
||||
print '<a href="'.dol_buildpath('/bankimport/pdfstatements.php', 1).'?action=view&id='.$obj->fk_statement.'&token='.newToken().'" target="_blank" title="'.$langs->trans("ViewPDFStatement").'">';
|
||||
print img_picto($langs->trans("ViewPDFStatement"), 'pdf');
|
||||
print '</a>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Status
|
||||
print '<td class="center">';
|
||||
print $obj->getLibStatut(5);
|
||||
|
|
@ -291,7 +310,7 @@ if (is_array($records) && count($records) > 0) {
|
|||
print '</tr>';
|
||||
}
|
||||
} else {
|
||||
print '<tr class="oddeven"><td colspan="8" class="opacitymedium center">';
|
||||
print '<tr class="oddeven"><td colspan="9" class="opacitymedium center">';
|
||||
print $langs->trans("NoTransactionsInDatabase");
|
||||
print '</td></tr>';
|
||||
}
|
||||
|
|
|
|||
355
pdfstatements.php
Normal file → Executable file
355
pdfstatements.php
Normal file → Executable file
|
|
@ -59,10 +59,10 @@ $langs->loadLangs(array("bankimport@bankimport", "banks", "other"));
|
|||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$confirm = GETPOST('confirm', 'alpha');
|
||||
$year = GETPOSTINT('year') ?: (int) date('Y');
|
||||
$year = GETPOSTISSET('year') ? GETPOSTINT('year') : (int) date('Y');
|
||||
|
||||
// Security check
|
||||
if (empty($user->rights->bankimport->statement->read)) {
|
||||
if (!$user->hasRight('bankimport', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
|
|
@ -72,66 +72,217 @@ if (empty($user->rights->bankimport->statement->read)) {
|
|||
|
||||
$statement = new BankImportStatement($db);
|
||||
|
||||
// Upload PDF
|
||||
if ($action == 'upload' && !empty($_FILES['pdffile']['name'])) {
|
||||
$error = 0;
|
||||
// Upload PDF (supports multiple files)
|
||||
if ($action == 'upload' && !empty($_FILES['pdffile'])) {
|
||||
$uploadMode = GETPOST('upload_mode', 'alpha');
|
||||
$isAutoMode = ($uploadMode !== 'manual');
|
||||
|
||||
// Validate required fields
|
||||
$statementNumber = GETPOST('statement_number', 'alpha');
|
||||
$statementYear = GETPOSTINT('statement_year');
|
||||
$statementDate = dol_mktime(0, 0, 0, GETPOSTINT('statement_datemonth'), GETPOSTINT('statement_dateday'), GETPOSTINT('statement_dateyear'));
|
||||
$dateFrom = dol_mktime(0, 0, 0, GETPOSTINT('date_frommonth'), GETPOSTINT('date_fromday'), GETPOSTINT('date_fromyear'));
|
||||
$dateTo = dol_mktime(0, 0, 0, GETPOSTINT('date_tomonth'), GETPOSTINT('date_today'), GETPOSTINT('date_toyear'));
|
||||
// Normalize $_FILES for multi-upload: always work with arrays
|
||||
$fileNames = is_array($_FILES['pdffile']['name']) ? $_FILES['pdffile']['name'] : array($_FILES['pdffile']['name']);
|
||||
$fileTmps = is_array($_FILES['pdffile']['tmp_name']) ? $_FILES['pdffile']['tmp_name'] : array($_FILES['pdffile']['tmp_name']);
|
||||
$fileSizes = is_array($_FILES['pdffile']['size']) ? $_FILES['pdffile']['size'] : array($_FILES['pdffile']['size']);
|
||||
$fileCount = count($fileNames);
|
||||
|
||||
if (empty($statementNumber)) {
|
||||
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesaliases("StatementNumber")), null, 'errors');
|
||||
$error++;
|
||||
}
|
||||
if (empty($statementYear)) {
|
||||
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesaliases("Year")), null, 'errors');
|
||||
$error++;
|
||||
}
|
||||
$uploadedCount = 0;
|
||||
$errorCount = 0;
|
||||
$totalLinked = 0;
|
||||
$lastYear = (int) date('Y');
|
||||
|
||||
if (!$error) {
|
||||
$statement->iban = GETPOST('iban', 'alpha');
|
||||
$statement->statement_number = $statementNumber;
|
||||
$statement->statement_year = $statementYear;
|
||||
$statement->statement_date = $statementDate ?: null;
|
||||
$statement->date_from = $dateFrom ?: null;
|
||||
$statement->date_to = $dateTo ?: null;
|
||||
$statement->opening_balance = GETPOST('opening_balance', 'alpha') !== '' ? (float) price2num(GETPOST('opening_balance', 'alpha')) : null;
|
||||
$statement->closing_balance = GETPOST('closing_balance', 'alpha') !== '' ? (float) price2num(GETPOST('closing_balance', 'alpha')) : null;
|
||||
$statement->import_key = date('YmdHis').'_'.$user->id;
|
||||
for ($fi = 0; $fi < $fileCount; $fi++) {
|
||||
$error = 0;
|
||||
|
||||
// Skip empty file slots
|
||||
if (empty($fileNames[$fi]) || empty($fileTmps[$fi])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate uploaded file
|
||||
if (!is_uploaded_file($fileTmps[$fi])) {
|
||||
setEventMessages($langs->trans("ErrorNoFileUploaded").': '.$fileNames[$fi], null, 'errors');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check MIME type
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $fileTmps[$fi]);
|
||||
finfo_close($finfo);
|
||||
if ($mimeType !== 'application/pdf') {
|
||||
setEventMessages($langs->trans("ErrorOnlyPDFAllowed").': '.$fileNames[$fi], null, 'errors');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check file size (max 10MB)
|
||||
if ($fileSizes[$fi] > 10 * 1024 * 1024) {
|
||||
setEventMessages($langs->trans("ErrorFileTooLarge").': '.$fileNames[$fi], null, 'errors');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse PDF metadata automatically
|
||||
$parsed = BankImportStatement::parsePdfMetadata($fileTmps[$fi]);
|
||||
|
||||
// Determine values: auto mode uses parsed data, manual mode uses form fields
|
||||
if ($isAutoMode && $parsed) {
|
||||
$statementNumber = $parsed['statement_number'];
|
||||
$statementYear = $parsed['statement_year'];
|
||||
$iban = $parsed['iban'];
|
||||
} else {
|
||||
// Manual mode (only for single file upload)
|
||||
$statementNumber = GETPOST('statement_number', 'alpha');
|
||||
$statementYear = GETPOSTINT('statement_year');
|
||||
$iban = GETPOST('iban', 'alpha');
|
||||
// Auto-fill from parsed data if form fields are empty
|
||||
if ($parsed) {
|
||||
if (empty($statementNumber) && !empty($parsed['statement_number'])) {
|
||||
$statementNumber = $parsed['statement_number'];
|
||||
}
|
||||
if (empty($statementYear) && !empty($parsed['statement_year'])) {
|
||||
$statementYear = $parsed['statement_year'];
|
||||
}
|
||||
if (empty($iban) && !empty($parsed['iban'])) {
|
||||
$iban = $parsed['iban'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show auto-detection info
|
||||
if ($parsed) {
|
||||
$autoMsg = $langs->trans("PdfAutoDetected").': '.$fileNames[$fi];
|
||||
if (!empty($statementNumber)) {
|
||||
$autoMsg .= ' | '.$statementNumber.'/'.$statementYear;
|
||||
}
|
||||
if (!empty($parsed['pdf_number'])) {
|
||||
$autoMsg .= ' (PDF-Nr. '.$parsed['pdf_number'].'/'.$parsed['pdf_year'].')';
|
||||
}
|
||||
if (!empty($parsed['iban'])) {
|
||||
$autoMsg .= ' | IBAN: '.$parsed['iban'];
|
||||
}
|
||||
if ($parsed['date_from'] && $parsed['date_to']) {
|
||||
$autoMsg .= ' | '.$langs->trans("Period").': '.dol_print_date($parsed['date_from'], 'day').' - '.dol_print_date($parsed['date_to'], 'day');
|
||||
}
|
||||
setEventMessages($autoMsg, null, 'mesgs');
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (empty($statementNumber)) {
|
||||
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesaliases("StatementNumber")).': '.$fileNames[$fi], null, 'errors');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
if (empty($statementYear)) {
|
||||
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesaliases("Year")).': '.$fileNames[$fi], null, 'errors');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create new statement object for each file
|
||||
$stmt = new BankImportStatement($db);
|
||||
$stmt->iban = $iban;
|
||||
$stmt->statement_number = $statementNumber;
|
||||
$stmt->statement_year = $statementYear;
|
||||
|
||||
// Date fields
|
||||
if ($isAutoMode && $parsed) {
|
||||
$stmt->statement_date = $parsed['statement_date'];
|
||||
$stmt->date_from = $parsed['date_from'];
|
||||
$stmt->date_to = $parsed['date_to'];
|
||||
$stmt->opening_balance = $parsed['opening_balance'];
|
||||
$stmt->closing_balance = $parsed['closing_balance'];
|
||||
} else {
|
||||
$statementDate = dol_mktime(0, 0, 0, GETPOSTINT('statement_datemonth'), GETPOSTINT('statement_dateday'), GETPOSTINT('statement_dateyear'));
|
||||
$dateFrom = dol_mktime(0, 0, 0, GETPOSTINT('date_frommonth'), GETPOSTINT('date_fromday'), GETPOSTINT('date_fromyear'));
|
||||
$dateTo = dol_mktime(0, 0, 0, GETPOSTINT('date_tomonth'), GETPOSTINT('date_today'), GETPOSTINT('date_toyear'));
|
||||
$stmt->statement_date = $statementDate ?: ($parsed ? $parsed['statement_date'] : null);
|
||||
$stmt->date_from = $dateFrom ?: ($parsed ? $parsed['date_from'] : null);
|
||||
$stmt->date_to = $dateTo ?: ($parsed ? $parsed['date_to'] : null);
|
||||
$openBal = GETPOST('opening_balance', 'alpha');
|
||||
$closeBal = GETPOST('closing_balance', 'alpha');
|
||||
$stmt->opening_balance = ($openBal !== '' && $openBal !== null) ? (float) price2num($openBal) : ($parsed ? $parsed['opening_balance'] : null);
|
||||
$stmt->closing_balance = ($closeBal !== '' && $closeBal !== null) ? (float) price2num($closeBal) : ($parsed ? $parsed['closing_balance'] : null);
|
||||
}
|
||||
|
||||
$stmt->import_key = date('YmdHis').'_'.$user->id;
|
||||
|
||||
// Check duplicate
|
||||
if ($statement->exists()) {
|
||||
setEventMessages($langs->trans("StatementAlreadyExists"), null, 'errors');
|
||||
$error++;
|
||||
if ($stmt->exists()) {
|
||||
setEventMessages($langs->trans("StatementAlreadyExists").': '.$statementNumber.'/'.$statementYear, null, 'warnings');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
// Save uploaded file
|
||||
$uploadResult = $statement->saveUploadedPDF($_FILES['pdffile']);
|
||||
// Generate filename and save file
|
||||
$dir = BankImportStatement::getStorageDir();
|
||||
|
||||
if ($uploadResult < 0) {
|
||||
setEventMessages($statement->error, null, 'errors');
|
||||
$error++;
|
||||
if ($parsed) {
|
||||
$newFilename = BankImportStatement::generateFilename($parsed);
|
||||
} else {
|
||||
$ibanPart = !empty($stmt->iban) ? preg_replace('/[^A-Z0-9]/', '', strtoupper($stmt->iban)) : 'KONTO';
|
||||
$newFilename = sprintf('Kontoauszug_%s_%d_%s.pdf',
|
||||
$ibanPart,
|
||||
$stmt->statement_year,
|
||||
str_pad($stmt->statement_number, 3, '0', STR_PAD_LEFT)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$stmt->filepath = $dir.'/'.$newFilename;
|
||||
|
||||
// Avoid overwriting existing files
|
||||
if (file_exists($stmt->filepath)) {
|
||||
$newFilename = pathinfo($newFilename, PATHINFO_FILENAME).'_'.date('His').'.pdf';
|
||||
$stmt->filepath = $dir.'/'.$newFilename;
|
||||
}
|
||||
|
||||
$stmt->filename = $newFilename;
|
||||
|
||||
// Move uploaded file
|
||||
if (!move_uploaded_file($fileTmps[$fi], $stmt->filepath)) {
|
||||
setEventMessages($langs->trans("ErrorFailedToSaveFile").': '.$fileNames[$fi], null, 'errors');
|
||||
$errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmt->filesize = filesize($stmt->filepath);
|
||||
|
||||
// Save to database
|
||||
$result = $statement->create($user);
|
||||
$result = $stmt->create($user);
|
||||
|
||||
if ($result > 0) {
|
||||
setEventMessages($langs->trans("StatementUploaded"), null, 'mesgs');
|
||||
header("Location: ".$_SERVER['PHP_SELF']."?year=".$statementYear);
|
||||
exit;
|
||||
// Link matching transactions to this statement
|
||||
$linked = $stmt->linkTransactions();
|
||||
$totalLinked += max(0, $linked);
|
||||
$uploadedCount++;
|
||||
$lastYear = $stmt->statement_year;
|
||||
} else {
|
||||
setEventMessages($statement->error, null, 'errors');
|
||||
setEventMessages($stmt->error, null, 'errors');
|
||||
$errorCount++;
|
||||
// Clean up file on DB error
|
||||
if (file_exists($stmt->filepath)) {
|
||||
@unlink($stmt->filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Summary message
|
||||
if ($uploadedCount > 0) {
|
||||
if ($uploadedCount == 1) {
|
||||
$msg = $langs->trans("StatementUploaded");
|
||||
} else {
|
||||
$msg = $langs->trans("StatementsUploaded", $uploadedCount);
|
||||
}
|
||||
if ($totalLinked > 0) {
|
||||
$msg .= ' | '.$langs->trans("TransactionsLinked", $totalLinked);
|
||||
}
|
||||
setEventMessages($msg, null, 'mesgs');
|
||||
// Redirect: for single upload use the year, for multi-upload show all
|
||||
if ($uploadedCount == 1) {
|
||||
header("Location: ".$_SERVER['PHP_SELF']."?year=".$lastYear);
|
||||
} else {
|
||||
header("Location: ".$_SERVER['PHP_SELF']."?year=0");
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Download PDF
|
||||
|
|
@ -205,6 +356,25 @@ llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-bankimport page-pdfstatemen
|
|||
|
||||
print load_fiche_titre($title, '', 'bank');
|
||||
|
||||
// Reminder: check if statements are outdated
|
||||
$reminderEnabled = getDolGlobalString('BANKIMPORT_REMINDER_ENABLED', '1');
|
||||
if ($reminderEnabled) {
|
||||
$reminderMonths = getDolGlobalInt('BANKIMPORT_REMINDER_MONTHS') ?: 3;
|
||||
$lastEndDate = $statement->getLatestStatementEndDate();
|
||||
$thresholdDate = dol_time_plus_duree(dol_now(), -$reminderMonths, 'm');
|
||||
|
||||
if ($lastEndDate === null) {
|
||||
print '<div class="warning">';
|
||||
print img_warning().' '.$langs->trans("ReminderNoStatements");
|
||||
print '</div><br>';
|
||||
} elseif ($lastEndDate < $thresholdDate) {
|
||||
$monthsAgo = (int) round((dol_now() - $lastEndDate) / (30 * 24 * 3600));
|
||||
print '<div class="warning">';
|
||||
print img_warning().' '.$langs->trans("ReminderOutdatedStatements", dol_print_date($lastEndDate, 'day'), $monthsAgo);
|
||||
print '</div><br>';
|
||||
}
|
||||
}
|
||||
|
||||
// Info box
|
||||
print '<div class="info" style="margin-bottom: 15px;">';
|
||||
print '<strong>'.$langs->trans("PDFStatementsInfo").'</strong><br>';
|
||||
|
|
@ -230,10 +400,13 @@ if ($action == 'delete') {
|
|||
}
|
||||
|
||||
// Upload form
|
||||
$defaultMode = getDolGlobalString('BANKIMPORT_UPLOAD_MODE') ?: 'auto';
|
||||
$uploadMode = GETPOST('upload_mode', 'alpha') ?: $defaultMode;
|
||||
|
||||
print '<div class="fichecenter">';
|
||||
print '<div class="fichehalfleft">';
|
||||
|
||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'" enctype="multipart/form-data">';
|
||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'" enctype="multipart/form-data" id="uploadform">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="upload">';
|
||||
|
||||
|
|
@ -242,16 +415,28 @@ print '<tr class="liste_titre">';
|
|||
print '<td colspan="2">'.$langs->trans("UploadPDFStatement").'</td>';
|
||||
print '</tr>';
|
||||
|
||||
// PDF file
|
||||
// Upload mode selection
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="titlefield fieldrequired">'.$langs->trans("File").'</td>';
|
||||
print '<td class="titlefield">'.$langs->trans("UploadMode").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="file" name="pdffile" accept=".pdf,application/pdf" required>';
|
||||
print '<label style="margin-right: 15px;"><input type="radio" name="upload_mode" value="auto" id="mode_auto"'.($uploadMode == 'auto' ? ' checked' : '').' onchange="toggleUploadMode()"> '.$langs->trans("UploadModeAuto").'</label>';
|
||||
print '<label><input type="radio" name="upload_mode" value="manual" id="mode_manual"'.($uploadMode == 'manual' ? ' checked' : '').' onchange="toggleUploadMode()"> '.$langs->trans("UploadModeManual").'</label>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// IBAN (optional)
|
||||
// PDF file (always visible, multiple in auto mode)
|
||||
print '<tr class="oddeven">';
|
||||
print '<td class="fieldrequired">'.$langs->trans("File").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="file" name="pdffile[]" id="pdffile_input" accept=".pdf,application/pdf" multiple required>';
|
||||
print '<br><span class="opacitymedium small" id="multi_hint">'.$langs->trans("MultipleFilesHint").'</span>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// --- Manual fields (hidden when auto mode) ---
|
||||
|
||||
// IBAN
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td>'.$langs->trans("IBAN").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="text" class="flat minwidth200" name="iban" value="'.dol_escape_htmltag(GETPOST('iban', 'alpha')).'" placeholder="DE89 3704 0044 0532 0130 00">';
|
||||
|
|
@ -259,7 +444,7 @@ print '</td>';
|
|||
print '</tr>';
|
||||
|
||||
// Year
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td class="fieldrequired">'.$langs->trans("Year").'</td>';
|
||||
print '<td>';
|
||||
$years = array();
|
||||
|
|
@ -271,16 +456,16 @@ print '</td>';
|
|||
print '</tr>';
|
||||
|
||||
// Statement number
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td class="fieldrequired">'.$langs->trans("StatementNumber").'</td>';
|
||||
print '<td>';
|
||||
$nextNum = $statement->getNextStatementNumber($year);
|
||||
print '<input type="text" class="flat width75" name="statement_number" value="'.dol_escape_htmltag(GETPOSTISSET('statement_number') ? GETPOST('statement_number', 'alpha') : $nextNum).'" required>';
|
||||
print '<input type="text" class="flat width75" name="statement_number" value="'.dol_escape_htmltag(GETPOSTISSET('statement_number') ? GETPOST('statement_number', 'alpha') : '').'">';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Statement date
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td>'.$langs->trans("StatementDate").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectDate(GETPOSTISSET('statement_dateday') ? dol_mktime(0, 0, 0, GETPOSTINT('statement_datemonth'), GETPOSTINT('statement_dateday'), GETPOSTINT('statement_dateyear')) : -1, 'statement_date', 0, 0, 1, '', 1, 0);
|
||||
|
|
@ -288,7 +473,7 @@ print '</td>';
|
|||
print '</tr>';
|
||||
|
||||
// Period from
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td>'.$langs->trans("DateFrom").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectDate(GETPOSTISSET('date_fromday') ? dol_mktime(0, 0, 0, GETPOSTINT('date_frommonth'), GETPOSTINT('date_fromday'), GETPOSTINT('date_fromyear')) : -1, 'date_from', 0, 0, 1, '', 1, 0);
|
||||
|
|
@ -296,7 +481,7 @@ print '</td>';
|
|||
print '</tr>';
|
||||
|
||||
// Period to
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td>'.$langs->trans("DateTo").'</td>';
|
||||
print '<td>';
|
||||
print $form->selectDate(GETPOSTISSET('date_today') ? dol_mktime(0, 0, 0, GETPOSTINT('date_tomonth'), GETPOSTINT('date_today'), GETPOSTINT('date_toyear')) : -1, 'date_to', 0, 0, 1, '', 1, 0);
|
||||
|
|
@ -304,7 +489,7 @@ print '</td>';
|
|||
print '</tr>';
|
||||
|
||||
// Opening balance
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td>'.$langs->trans("OpeningBalance").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="text" class="flat width100" name="opening_balance" value="'.dol_escape_htmltag(GETPOST('opening_balance', 'alpha')).'" placeholder="1.234,56">';
|
||||
|
|
@ -313,7 +498,7 @@ print '</td>';
|
|||
print '</tr>';
|
||||
|
||||
// Closing balance
|
||||
print '<tr class="oddeven">';
|
||||
print '<tr class="oddeven manual-field">';
|
||||
print '<td>'.$langs->trans("ClosingBalance").'</td>';
|
||||
print '<td>';
|
||||
print '<input type="text" class="flat width100" name="closing_balance" value="'.dol_escape_htmltag(GETPOST('closing_balance', 'alpha')).'" placeholder="1.345,67">';
|
||||
|
|
@ -329,16 +514,49 @@ print '</div>';
|
|||
|
||||
print '</form>';
|
||||
|
||||
// JavaScript for toggling upload modes
|
||||
print '<script type="text/javascript">
|
||||
function toggleUploadMode() {
|
||||
var isManual = document.getElementById("mode_manual").checked;
|
||||
var manualFields = document.querySelectorAll(".manual-field");
|
||||
var fileInput = document.getElementById("pdffile_input");
|
||||
var multiHint = document.getElementById("multi_hint");
|
||||
for (var i = 0; i < manualFields.length; i++) {
|
||||
manualFields[i].style.display = isManual ? "" : "none";
|
||||
}
|
||||
// In manual mode: single file only. In auto mode: multiple files allowed
|
||||
if (isManual) {
|
||||
fileInput.removeAttribute("multiple");
|
||||
multiHint.style.display = "none";
|
||||
} else {
|
||||
fileInput.setAttribute("multiple", "multiple");
|
||||
multiHint.style.display = "";
|
||||
}
|
||||
}
|
||||
// Initial state
|
||||
document.addEventListener("DOMContentLoaded", function() { toggleUploadMode(); });
|
||||
</script>';
|
||||
|
||||
print '</div>'; // fichehalfleft
|
||||
print '</div>'; // fichecenter
|
||||
|
||||
print '<div class="clearboth"></div><br>';
|
||||
|
||||
// Year filter for list
|
||||
// Year filter for list - only show years that have statements
|
||||
$yearsFilter = array(0 => $langs->trans("AllStatements"));
|
||||
$availableYears = $statement->getAvailableYears();
|
||||
foreach ($availableYears as $yKey => $yVal) {
|
||||
$yearsFilter[$yKey] = $yVal;
|
||||
}
|
||||
// If current year not in list, add it
|
||||
if (!isset($yearsFilter[(int) date('Y')])) {
|
||||
$yearsFilter[(int) date('Y')] = (string) date('Y');
|
||||
krsort($yearsFilter);
|
||||
}
|
||||
print '<form method="GET" action="'.$_SERVER["PHP_SELF"].'">';
|
||||
print '<div class="center" style="margin-bottom: 15px;">';
|
||||
print '<strong>'.$langs->trans("Year").':</strong> ';
|
||||
print $form->selectarray('year', $years, $year, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100');
|
||||
print $form->selectarray('year', $yearsFilter, $year, 0, 0, 0, '', 0, 0, 0, '', 'minwidth100');
|
||||
print ' <input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Filter").'">';
|
||||
print '</div>';
|
||||
print '</form>';
|
||||
|
|
@ -358,8 +576,11 @@ print '<th class="center">'.$langs->trans("DateCreation").'</th>';
|
|||
print '<th class="center" width="150">'.$langs->trans("Actions").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
$filter = array('year' => $year);
|
||||
$records = $statement->fetchAll('statement_number', 'ASC', 100, 0, $filter);
|
||||
$filter = array();
|
||||
if ($year > 0) {
|
||||
$filter['year'] = $year;
|
||||
}
|
||||
$records = $statement->fetchAll('statement_year,statement_number', 'DESC', 100, 0, $filter);
|
||||
|
||||
if (is_array($records) && count($records) > 0) {
|
||||
foreach ($records as $obj) {
|
||||
|
|
@ -472,8 +693,12 @@ $totalCount = $statement->fetchAll('', '', 0, 0, array(), 'count');
|
|||
$yearCount = is_array($records) ? count($records) : 0;
|
||||
|
||||
print '<div class="opacitymedium" style="margin-top: 10px;">';
|
||||
print $langs->trans("Total").': <strong>'.$yearCount.'</strong> '.$langs->trans("StatementsInYear", $year);
|
||||
print ' | '.$langs->trans("AllStatements").': <strong>'.$totalCount.'</strong>';
|
||||
if ($year > 0) {
|
||||
print $langs->trans("Total").': <strong>'.$yearCount.'</strong> '.$langs->trans("StatementsInYear", $year);
|
||||
print ' | '.$langs->trans("AllStatements").': <strong>'.$totalCount.'</strong>';
|
||||
} else {
|
||||
print $langs->trans("Total").': <strong>'.$totalCount.'</strong> '.$langs->trans("AllStatements");
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
llxFooter();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
--
|
||||
-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version.
|
||||
--
|
||||
|
||||
-- v1.1: Add fk_statement to transaction table
|
||||
ALTER TABLE llx_bankimport_transaction ADD COLUMN fk_statement INTEGER AFTER fk_societe;
|
||||
ALTER TABLE llx_bankimport_transaction ADD INDEX idx_bankimport_transaction_fk_statement (fk_statement);
|
||||
|
|
|
|||
0
sql/llx_bankimport_statement.key.sql
Normal file → Executable file
0
sql/llx_bankimport_statement.key.sql
Normal file → Executable file
0
sql/llx_bankimport_statement.sql
Normal file → Executable file
0
sql/llx_bankimport_statement.sql
Normal file → Executable file
1
sql/llx_bankimport_transaction.key.sql
Normal file → Executable file
1
sql/llx_bankimport_transaction.key.sql
Normal file → Executable file
|
|
@ -19,6 +19,7 @@ ALTER TABLE llx_bankimport_transaction ADD INDEX idx_bankimport_transaction_fk_f
|
|||
ALTER TABLE llx_bankimport_transaction ADD INDEX idx_bankimport_transaction_fk_facture_fourn (fk_facture_fourn);
|
||||
ALTER TABLE llx_bankimport_transaction ADD INDEX idx_bankimport_transaction_fk_societe (fk_societe);
|
||||
ALTER TABLE llx_bankimport_transaction ADD INDEX idx_bankimport_transaction_import_key (import_key);
|
||||
ALTER TABLE llx_bankimport_transaction ADD INDEX idx_bankimport_transaction_fk_statement (fk_statement);
|
||||
|
||||
-- Foreign keys (optional, depends on your setup)
|
||||
-- ALTER TABLE llx_bankimport_transaction ADD CONSTRAINT fk_bankimport_transaction_fk_bank FOREIGN KEY (fk_bank) REFERENCES llx_bank(rowid);
|
||||
|
|
|
|||
1
sql/llx_bankimport_transaction.sql
Normal file → Executable file
1
sql/llx_bankimport_transaction.sql
Normal file → Executable file
|
|
@ -45,6 +45,7 @@ CREATE TABLE llx_bankimport_transaction (
|
|||
fk_don INTEGER, -- Link to llx_don (donation)
|
||||
fk_loan INTEGER, -- Link to llx_loan (loan payment)
|
||||
fk_societe INTEGER, -- Link to llx_societe (third party)
|
||||
fk_statement INTEGER, -- Link to llx_bankimport_statement (PDF statement)
|
||||
|
||||
-- Status
|
||||
status SMALLINT DEFAULT 0, -- 0=new, 1=matched, 2=reconciled, 9=ignored
|
||||
|
|
|
|||
5
statements.php
Normal file → Executable file
5
statements.php
Normal file → Executable file
|
|
@ -61,6 +61,11 @@ $langs->loadLangs(array("bankimport@bankimport", "banks"));
|
|||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('bankimport', 'write')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Date filters
|
||||
$date_fromday = GETPOSTINT('date_fromday');
|
||||
$date_frommonth = GETPOSTINT('date_frommonth');
|
||||
|
|
|
|||
0
vendor/autoload.php
vendored
Normal file → Executable file
0
vendor/autoload.php
vendored
Normal file → Executable file
0
vendor/composer/ClassLoader.php
vendored
Normal file → Executable file
0
vendor/composer/ClassLoader.php
vendored
Normal file → Executable file
0
vendor/composer/InstalledVersions.php
vendored
Normal file → Executable file
0
vendor/composer/InstalledVersions.php
vendored
Normal file → Executable file
0
vendor/composer/LICENSE
vendored
Normal file → Executable file
0
vendor/composer/LICENSE
vendored
Normal file → Executable file
0
vendor/composer/autoload_classmap.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_classmap.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_namespaces.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_namespaces.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_psr4.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_psr4.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_real.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_real.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_static.php
vendored
Normal file → Executable file
0
vendor/composer/autoload_static.php
vendored
Normal file → Executable file
0
vendor/composer/installed.json
vendored
Normal file → Executable file
0
vendor/composer/installed.json
vendored
Normal file → Executable file
0
vendor/composer/installed.php
vendored
Normal file → Executable file
0
vendor/composer/installed.php
vendored
Normal file → Executable file
0
vendor/composer/platform_check.php
vendored
Normal file → Executable file
0
vendor/composer/platform_check.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.github/workflows/tests.yml
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.github/workflows/tests.yml
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.gitignore
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.gitignore
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.php-cs-fixer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.php-cs-fixer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.travis.yml
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/.travis.yml
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/DEVELOPER-GUIDE.md
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/DEVELOPER-GUIDE.md
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/LICENSE
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/LICENSE
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/README.md
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/README.md
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/accounts.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/accounts.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/balance.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/balance.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/bpd.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/bpd.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/browser.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/browser.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/directDebit_Sephpa.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/directDebit_Sephpa.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/directDebit_phpSepaXml.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/directDebit_phpSepaXml.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/init.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/init.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/login.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/login.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/statementOfAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/statementOfAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/statementOfHoldings.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/statementOfHoldings.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/tanModesAndMedia.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/tanModesAndMedia.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/transfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/Samples/transfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/composer.json
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/composer.json
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetBalance.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetBalance.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetDepotAufstellung.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetDepotAufstellung.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetSEPAAccounts.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetSEPAAccounts.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetSEPADirectDebitParameters.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetSEPADirectDebitParameters.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccountXML.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccountXML.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendInternationalCreditTransfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendInternationalCreditTransfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendSEPADirectDebit.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendSEPADirectDebit.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendSEPARealtimeTransfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendSEPARealtimeTransfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendSEPATransfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Action/SendSEPATransfer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/BaseAction.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/BaseAction.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Connection.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Connection.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/CurlException.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/CurlException.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/FinTs.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/FinTs.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT535/MT535.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT535/MT535.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/Dialect/PostbankMT940.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/Dialect/PostbankMT940.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/Dialect/SpardaMT940.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/Dialect/SpardaMT940.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/MT940.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/MT940.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/MT940Exception.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/MT940/MT940Exception.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/Account.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/Account.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/DataElement.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/DataElement.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/StartCode.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/StartCode.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/SvgRenderer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/SvgRenderer.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/TanRequestChallengeFlicker.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/FlickerTan/TanRequestChallengeFlicker.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/NoPsd2TanMode.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/NoPsd2TanMode.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/SEPAAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/SEPAAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfAccount/Statement.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfAccount/Statement.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfAccount/Transaction.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfAccount/Transaction.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfHoldings/Holding.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfHoldings/Holding.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfHoldings/StatementOfHoldings.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/StatementOfHoldings/StatementOfHoldings.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanMedium.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanMedium.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanMode.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanMode.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanRequest.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanRequest.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanRequestChallengeImage.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Model/TanRequestChallengeImage.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Options/Credentials.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Options/Credentials.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Options/FinTsOptions.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Options/FinTsOptions.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Options/SanitizingLogger.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Options/SanitizingLogger.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/PaginateableAction.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/PaginateableAction.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/ActionIncompleteException.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/ActionIncompleteException.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/BPD.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/BPD.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/DialogInitialization.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/DialogInitialization.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/GetTanMedia.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/GetTanMedia.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/Message.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/Message.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/MessageBuilder.php
vendored
Normal file → Executable file
0
vendor/nemiah/php-fints/lib/Fhp/Protocol/MessageBuilder.php
vendored
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue