diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92ff42e..aa38d79 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,66 +2,6 @@
Alle wesentlichen Änderungen an diesem Projekt werden in dieser Datei dokumentiert.
-## [3.5] - 2026-03-05
-
-### Hinzugefügt
-- **PDF-Kontoauszüge per FinTS**: Elektronische Kontoauszüge direkt von der Bank abrufen
- - **HKEKP**: Direkt-Abruf (für Banken die dies unterstützen)
- - **HKKAA**: Fallback über Bank-Archiv/Postfach (VR Banken, etc.)
- - Automatische Methodenwahl: System prüft BPD und wählt beste verfügbare Methode
- - Neue Segmente für php-fints: EKP/* (HKEKP) und KAA/* (HKKAA)
- - Integration in bestehende PDF-Kontoauszüge-Seite
-- **Cronjob für automatischen PDF-Abruf**: Neue geplante Aufgabe `doAutoFetchPdf`
- - Aktivierbar über Konstante `BANKIMPORT_PDF_AUTO_ENABLED`
- - Ruft automatisch neue PDF-Kontoauszüge ab und speichert sie
-
-### Geändert
-- PDF-Kontoauszüge-Seite: Neues Layout mit zwei Spalten (FinTS-Abruf links, Upload rechts)
-- fints.class.php: Neue Methoden für PDF-Abruf mit automatischer Methodenwahl
-
-### Technisch
-- Erweiterung der php-fints Bibliothek:
- - HKEKP-Unterstützung (Segment/EKP/*): HKEKPv2, HIEKPv2, HIEKPSv2
- - HKKAA-Unterstützung (Segment/KAA/*): HKKAAv2, HIKAAv2, HIKAASv1
- - Action-Klassen: GetStatementPDF, GetStatementFromArchive
-- Neue Methoden in BankImportFinTS:
- - `getPdfStatementMethod()`: Prüft welche Methode die Bank unterstützt
- - `getStatementPDFAuto()`: Automatische Methodenwahl
- - `supportsArchiveStatements()`: Prüft HKKAA-Support
-
-## [3.1] - 2026-03-05
-
-### Hinzugefügt
-- **Skonto-Erkennung**: Multi-Invoice-Matching erkennt jetzt Skonto-Abzüge bis 3%
-- **Automatische Skonto-Verarbeitung**: Zahlungen werden proportional auf Rechnungen verteilt
-- **Skonto-Vermerk**: Rechnungen werden mit `close_code='discount_vat'` als bezahlt markiert
-- **Skonto-Dokumentation**: Notiz zeigt Skonto-Betrag pro Rechnung
-
-### Verbessert
-- **Match-Sortierung**: Matches mit näherem Betrag werden jetzt bevorzugt angezeigt
-- **Multi-Match Score**: Von 90 auf 98 erhöht, damit Sammelzahlungen vor Einzelmatches erscheinen
-- Sammelzahlungen mit Skonto werden jetzt korrekt als Gruppe erkannt (statt einzelner Rechnungen)
-
-### Beispiel
-Transaktion -523,40€ mit 3 Rechnungen (Summe 529,69€):
-- Erkennt 6,29€ Skonto-Abzug
-- Verteilt proportional: 59,37€ + 206,72€ + 257,32€
-- Markiert alle 3 als "bezahlt mit Skonto"
-
-## [3.0] - 2026-03-05
-
-### Hinzugefügt
-- **Repair-Seite**: Neue Admin-Seite zum Reparieren verwaister Transaktionen
- - Findet Transaktionen die als "Neu" markiert sind, obwohl Zahlung bereits existiert
- - Ermöglicht manuelles oder Batch-Reparieren
- - Erreichbar über Admin-Setup → Admin-Werkzeuge
-- **Admin-Werkzeuge Sektion**: Neuer Bereich im Setup für Wartungsfunktionen
-
-### Geändert
-- **Filter für bezahlte Rechnungen**: Zeigt jetzt alle bezahlten Rechnungen die noch nicht über BankImport verknüpft sind
- - Prüft nur BankImport-Verknüpfung (nicht mehr payment.fk_bank)
- - Ermöglicht nachträgliche Verknüpfung von extern bezahlten Rechnungen
-
## [2.9] - 2026-02-23
### Entfernt
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100755
index 48a50e4..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,175 +0,0 @@
-# BankImport Modul - Entwicklernotizen
-
-## Modul-Übersicht
-- **Name**: BankImport
-- **Version**: 3.5
-- **Pfad**: `/srv/http/dolibarr/custom/bankimport/`
-- **Funktion**: FinTS/HBCI Kontoauszüge importieren und mit Dolibarr-Rechnungen abgleichen
-
-## Wichtige Dateien
-
-| Datei | Funktion |
-|-------|----------|
-| `class/banktransaction.class.php` | Hauptklasse für Transaktionen, findMatches(), confirmMultiplePayment() |
-| `confirm.php` | Übersicht aller Matches zur Bestätigung |
-| `card.php` | Einzelansicht einer Transaktion mit manueller Zuordnung |
-| `repair.php` | Admin-Seite für verwaiste Transaktionen |
-| `cron/bankimport.cron.php` | Cronjob für automatischen Import |
-| `admin/cronmonitor.php` | Cron-Monitoring und Pause/Resume |
-| `pdfstatements.php` | PDF-Kontoauszüge hochladen und per FinTS abrufen |
-| `vendor/.../Segment/EKP/*` | HKEKP-Segmente für PDF-Abruf (direkt) |
-| `vendor/.../Segment/KAA/*` | HKKAA-Segmente für PDF-Abruf (Archiv) |
-| `vendor/.../Action/GetStatementPDF.php` | Action-Klasse für HKEKP |
-| `vendor/.../Action/GetStatementFromArchive.php` | Action-Klasse für HKKAA |
-
-## PDF-Kontoauszüge per FinTS
-
-### Übersicht
-Seit Version 3.5 können PDF-Kontoauszüge von der Bank abgerufen werden. Es gibt zwei Methoden:
-
-| Methode | Segment | Beschreibung | Bank-Beispiele |
-|---------|---------|--------------|----------------|
-| **HKEKP** | Elektronischer Kontoauszug PDF | Direkt-Abruf | Sparkassen, einige Volksbanken |
-| **HKKAA** | Kontoauszug aus Archiv | Abruf aus Bank-Postfach | VR Banken (z.B. VR Bank Schleswig-Holstein) |
-
-Das System wählt automatisch die beste verfügbare Methode.
-
-### Neue Dateien in php-fints
-```
-vendor/nemiah/php-fints/lib/Fhp/
-├── Action/
-│ ├── GetStatementPDF.php # HKEKP Action
-│ └── GetStatementFromArchive.php # HKKAA Action
-└── Segment/
- ├── EKP/ # HKEKP Segmente
- │ ├── HKEKPv2.php
- │ ├── HIEKPv2.php
- │ └── ...
- └── KAA/ # HKKAA Segmente
- ├── HKKAAv2.php
- ├── HIKAAv2.php
- ├── HIKAASv1.php
- └── ParameterKontoauszugArchiv.php
-```
-
-### Verwendung (empfohlen: Auto-Modus)
-```php
-$fints = new BankImportFinTS($db);
-$fints->login();
-
-// Automatische Methodenwahl
-$method = $fints->getPdfStatementMethod(); // 'HKEKP', 'HKKAA' oder false
-if ($method) {
- $result = $fints->getStatementPDFAuto(0);
- if (is_array($result) && !empty($result['pdfData'])) {
- $pdfData = $result['pdfData'];
- $info = $result['info'];
- $usedMethod = $result['method']; // 'HKEKP' oder 'HKKAA'
- }
-}
-```
-
-### Cronjob
-- Aktivieren: `BANKIMPORT_PDF_AUTO_ENABLED = 1`
-- Klasse: `BankImportCron::doAutoFetchPdf()`
-- Frequenz: Wie Transaktions-Import konfigurierbar
-
-## Multi-Invoice Matching (Sammelzahlungen)
-
-### Ablauf
-1. `findMatches()` sucht Einzelmatches und Multi-Matches
-2. `findSupplierForMultiMatch()` findet Lieferant über IBAN oder Name (Similarity > 70%)
-3. `findMultipleSupplierInvoiceMatches()` kombiniert Rechnungen deren ref_supplier im Buchungstext steht
-4. Toleranz: **3% des Betrags** (für Skonto)
-
-### Sortierung
-Matches werden sortiert nach:
-1. Betragsnähe (innerhalb 10% Threshold bevorzugt)
-2. Score (Multi-Match: 98-100, Einzelmatch: variabel)
-
-### Skonto-Verarbeitung
-In `confirmMultiplePayment()`:
-1. Wenn gezahlter Betrag < Rechnungssumme (max 5% Abweichung)
-2. Zahlungen werden proportional verteilt (`discountFactor = actualAmount / totalPayment`)
-3. Rechnungen werden mit `setPaid($user, 'discount_vat', 'Skonto (X EUR)')` geschlossen
-
-## Datenbank-Struktur
-
-### llx_bankimport_transaction
-| Feld | Bedeutung |
-|------|-----------|
-| `status` | 0=Neu, 1=Zugeordnet, 2=Abgestimmt, 9=Ignoriert |
-| `fk_facture_fourn` | Verknüpfte Lieferantenrechnung (erste bei Multi) |
-| `fk_paiementfourn` | Verknüpfte Zahlung |
-| `fk_bank` | Verknüpfter Bank-Eintrag |
-| `note_private` | Multi-invoice payment: SI..., SI..., SI... (Skonto: X EUR) |
-
-### Rechnung (close_code)
-| Code | Bedeutung |
-|------|-----------|
-| `NULL` | Normal bezahlt |
-| `discount_vat` | Mit Skonto bezahlt |
-| `badsupplier` | Zahlungsausfall |
-| `abandon` | Aufgegeben |
-
-## Bekannte Edge Cases
-
-### Problem: Nur 1 statt 3 Rechnungen bestätigt
-- **Ursache**: Multi-Match hatte niedrigeren Score als Einzelmatch
-- **Lösung**: Score auf 98 erhöht + Sortierung nach Betragsnähe
-
-### Problem: Skonto nicht erkannt
-- **Ursache**: Toleranz war 5€ fix, Skonto war 6,29€
-- **Lösung**: Toleranz auf 3% des Betrags geändert
-
-### Problem: Bezahlte Rechnungen nicht in Liste
-- **Ursache**: Filter prüfte payment.fk_bank statt BankImport-Verknüpfung
-- **Lösung**: Nur `llx_bankimport_transaction.status > 0` prüfen
-
-## Test-Datenbank
-- **Host**: 192.168.155.1 (Produktiv) / 192.168.155.11 (Test)
-- **User**: dolibarr
-- **Passwort**: 8715
-
-## Typische Debug-Queries
-
-```sql
--- Transaktion mit Verknüpfungen
-SELECT rowid, ref, amount, status, fk_facture_fourn, fk_paiementfourn, fk_bank, note_private
-FROM llx_bankimport_transaction WHERE rowid = X;
-
--- Zahlungsverteilung prüfen
-SELECT pf.fk_paiementfourn, pf.fk_facturefourn, pf.amount, f.ref
-FROM llx_paiementfourn_facturefourn pf
-JOIN llx_facture_fourn f ON f.rowid = pf.fk_facturefourn
-WHERE pf.fk_paiementfourn = X;
-
--- Rechnungen mit Skonto-Status
-SELECT rowid, ref, total_ttc, fk_statut, paye, close_code, close_note
-FROM llx_facture_fourn WHERE rowid IN (X, Y, Z);
-```
-
-## Zurücksetzen einer Transaktion (für Tests)
-
-```sql
--- 1. Zahlung-Rechnung Verknüpfung löschen
-DELETE FROM llx_paiementfourn_facturefourn WHERE fk_paiementfourn = X;
-
--- 2. Bank-URL löschen
-DELETE FROM llx_bank_url WHERE url_id = X AND type = 'payment_supplier';
-
--- 3. Zahlung löschen
-DELETE FROM llx_paiementfourn WHERE rowid = X;
-
--- 4. Bank-Eintrag löschen
-DELETE FROM llx_bank WHERE rowid = Y;
-
--- 5. Rechnung(en) auf offen setzen
-UPDATE llx_facture_fourn SET fk_statut = 1, paye = 0, close_code = NULL, close_note = NULL WHERE rowid IN (...);
-
--- 6. BankImport-Transaktion zurücksetzen
-UPDATE llx_bankimport_transaction
-SET status = 0, fk_facture_fourn = NULL, fk_paiementfourn = NULL, fk_bank = NULL,
- fk_user_match = NULL, date_match = NULL
-WHERE rowid = Z;
-```
diff --git a/bin/module_bankimport-1.6.zip b/bin/module_bankimport-1.6.zip
deleted file mode 100755
index 3f9fbef..0000000
Binary files a/bin/module_bankimport-1.6.zip and /dev/null differ
diff --git a/bin/module_bankimport-1.7.zip b/bin/module_bankimport-1.7.zip
deleted file mode 100755
index cfc0f8f..0000000
Binary files a/bin/module_bankimport-1.7.zip and /dev/null differ
diff --git a/bin/module_bankimport-1.8.zip b/bin/module_bankimport-1.8.zip
deleted file mode 100755
index 84cffca..0000000
Binary files a/bin/module_bankimport-1.8.zip and /dev/null differ
diff --git a/bin/module_bankimport-1.9.zip b/bin/module_bankimport-1.9.zip
deleted file mode 100755
index e87d50c..0000000
Binary files a/bin/module_bankimport-1.9.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.0.zip b/bin/module_bankimport-2.0.zip
deleted file mode 100755
index ac98c3a..0000000
Binary files a/bin/module_bankimport-2.0.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.1.zip b/bin/module_bankimport-2.1.zip
deleted file mode 100755
index 93493e6..0000000
Binary files a/bin/module_bankimport-2.1.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.2.zip b/bin/module_bankimport-2.2.zip
deleted file mode 100755
index a46c65d..0000000
Binary files a/bin/module_bankimport-2.2.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.3.zip b/bin/module_bankimport-2.3.zip
deleted file mode 100755
index 5baf298..0000000
Binary files a/bin/module_bankimport-2.3.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.4.zip b/bin/module_bankimport-2.4.zip
deleted file mode 100755
index 7427493..0000000
Binary files a/bin/module_bankimport-2.4.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.5.zip b/bin/module_bankimport-2.5.zip
deleted file mode 100755
index 274cb70..0000000
Binary files a/bin/module_bankimport-2.5.zip and /dev/null differ
diff --git a/bin/module_bankimport-2.6.zip b/bin/module_bankimport-2.6.zip
deleted file mode 100755
index 36ce9e3..0000000
Binary files a/bin/module_bankimport-2.6.zip and /dev/null differ
diff --git a/class/bankimportcron.class.php b/class/bankimportcron.class.php
index fcecc35..9c1dd98 100755
--- a/class/bankimportcron.class.php
+++ b/class/bankimportcron.class.php
@@ -16,7 +16,6 @@
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
dol_include_once('/bankimport/class/fints.class.php');
dol_include_once('/bankimport/class/banktransaction.class.php');
-dol_include_once('/bankimport/class/bankstatement.class.php');
/**
* Class BankImportCron
@@ -733,184 +732,4 @@ class BankImportCron
return $this->doAutoImport();
}
-
- /**
- * Execute automatic PDF statement fetch via HKEKP
- * Called by Dolibarr's scheduled task system
- *
- * @return int 0 if OK, < 0 if error
- */
- public function doAutoFetchPdf()
- {
- global $conf, $langs, $user;
-
- // Initialize timing
- $this->startTime = microtime(true);
-
- $langs->load('bankimport@bankimport');
-
- $this->cronLog("========== PDF FETCH CRON START ==========");
- $this->recordCronStatus('started', 'PDF fetch cron started');
-
- // Check if PDF fetch is enabled
- if (!getDolGlobalInt('BANKIMPORT_PDF_AUTO_ENABLED')) {
- $this->output = $langs->trans('AutoPdfFetchDisabled');
- $this->cronLog("Auto PDF fetch is disabled - exiting");
- $this->recordCronStatus('completed', 'Auto PDF fetch disabled');
- return 0;
- }
-
- // Check cron pause status (shared with main cron)
- $pausedUntil = getDolGlobalInt('BANKIMPORT_CRON_PAUSED_UNTIL');
- if ($pausedUntil > 0 && $pausedUntil > time()) {
- $pauseReason = getDolGlobalString('BANKIMPORT_CRON_PAUSE_REASON');
- $this->output = "Cron pausiert: {$pauseReason}";
- $this->cronLog("Cron is PAUSED - skipping PDF fetch", 'WARNING');
- return 0;
- }
-
- $this->cronLog("Initializing FinTS for PDF fetch");
-
- // Initialize FinTS
- $fints = new BankImportFinTS($this->db);
-
- if (!$fints->isConfigured()) {
- $this->error = $langs->trans('AutoImportNotConfigured');
- $this->cronLog("FinTS not configured", 'WARNING');
- return -1;
- }
-
- try {
- // Login to bank
- $this->cronLog("Logging in to bank");
- $loginResult = $fints->login();
-
- if ($loginResult < 0) {
- $this->error = $fints->error;
- $this->cronLog("Login failed: ".$fints->error, 'ERROR');
- return -1;
- }
-
- if ($loginResult == 0) {
- // TAN required
- $this->output = $langs->trans('TANRequired');
- $this->cronLog("TAN required for PDF fetch", 'WARNING');
- $this->recordCronStatus('completed', 'TAN required');
- return 0;
- }
-
- // Check if bank supports PDF statements
- if (!$fints->supportsPdfStatements()) {
- $this->output = $langs->trans('ErrorBankDoesNotSupportPdfStatements');
- $this->cronLog("Bank does not support PDF statements (HKEKP)", 'WARNING');
- $fints->close();
- return 0;
- }
-
- // Fetch PDF statement
- $this->cronLog("Fetching PDF statement via HKEKP");
- $pdfResult = $fints->getStatementPDF(0);
-
- if ($pdfResult === 0) {
- // TAN required
- $this->output = $langs->trans('TANRequired');
- $this->cronLog("TAN required for PDF statement", 'WARNING');
- $fints->close();
- return 0;
- }
-
- if ($pdfResult === -1) {
- $this->error = $fints->error;
- $this->cronLog("PDF fetch failed: ".$fints->error, 'ERROR');
- $fints->close();
- return -1;
- }
-
- // Check if we got any data
- if (empty($pdfResult['pdfData'])) {
- $this->output = $langs->trans('NoPdfStatementsAvailable');
- $this->cronLog("No new PDF statements available");
- $fints->close();
- return 0;
- }
-
- // Save the PDF
- $info = $pdfResult['info'];
- $pdfData = $pdfResult['pdfData'];
-
- $this->cronLog("Received PDF statement #".$info['statementNumber'].'/'.$info['statementYear']);
-
- // Check if statement already exists
- $stmt = new BankImportStatement($this->db);
- $stmt->statement_number = $info['statementNumber'];
- $stmt->statement_year = $info['statementYear'];
- $stmt->iban = $info['iban'] ?: getDolGlobalString('BANKIMPORT_IBAN');
-
- if ($stmt->exists()) {
- $this->output = $langs->trans("StatementAlreadyExists").': '.$stmt->statement_number.'/'.$stmt->statement_year;
- $this->cronLog("Statement already exists - skipping");
- $fints->close();
- return 0;
- }
-
- // Save PDF to file
- $dir = BankImportStatement::getStorageDir();
- $ibanPart = preg_replace('/[^A-Z0-9]/', '', strtoupper($stmt->iban ?: 'KONTO'));
- $filename = sprintf('Kontoauszug_%s_%d_%s.pdf',
- $ibanPart,
- $stmt->statement_year,
- str_pad($stmt->statement_number, 3, '0', STR_PAD_LEFT)
- );
-
- $filepath = $dir.'/'.$filename;
- if (file_put_contents($filepath, $pdfData) === false) {
- $this->error = $langs->trans("ErrorSavingPdfFile");
- $this->cronLog("Failed to save PDF file", 'ERROR');
- $fints->close();
- return -1;
- }
-
- // Create database record
- $stmt->filename = $filename;
- $stmt->filepath = $filepath;
- $stmt->filesize = strlen($pdfData);
- $stmt->statement_date = $info['creationDate'] ? $info['creationDate']->getTimestamp() : dol_now();
- $stmt->import_key = 'fints_cron_'.date('YmdHis');
-
- // Get system user
- $importUser = new User($this->db);
- $importUser->fetch(1);
-
- $result = $stmt->create($importUser);
-
- if ($result > 0) {
- $this->output = $langs->trans("PdfStatementFetched", $stmt->statement_number.'/'.$stmt->statement_year);
- $this->cronLog("PDF statement saved successfully: ".$stmt->statement_number.'/'.$stmt->statement_year);
-
- // Update last fetch timestamp
- dolibarr_set_const($this->db, 'BANKIMPORT_PDF_LAST_FETCH', time(), 'chaine', 0, '', $conf->entity);
- } else {
- $this->error = $stmt->error;
- $this->cronLog("Failed to create database record: ".$stmt->error, 'ERROR');
- @unlink($filepath);
- $fints->close();
- return -1;
- }
-
- $fints->close();
-
- $duration = round(microtime(true) - $this->startTime, 2);
- $this->cronLog("PDF fetch completed successfully in {$duration}s");
- $this->recordCronStatus('completed', "PDF fetched: {$stmt->statement_number}/{$stmt->statement_year}");
- $this->cronLog("========== PDF FETCH CRON END ==========");
-
- return 0;
-
- } catch (Exception $e) {
- $this->error = 'Exception: '.$e->getMessage();
- $this->cronLog("EXCEPTION: ".$e->getMessage(), 'ERROR');
- $this->recordCronStatus('error', 'Exception: '.$e->getMessage());
- return -1;
- }
- }
}
diff --git a/class/bankstatement.class.php b/class/bankstatement.class.php
index 9dbe529..7fa6d9d 100755
--- a/class/bankstatement.class.php
+++ b/class/bankstatement.class.php
@@ -299,7 +299,13 @@ class BankImportStatement extends CommonObject
}
// Sort and limit
- $sql .= $this->db->order($sortfield, $sortorder);
+ if (strpos($sortfield, 'statement_number') !== false) {
+ // statement_number numerisch sortieren (db->order() sanitiert CAST-Ausdruecke)
+ $dir = strtoupper($sortorder) === 'DESC' ? 'DESC' : 'ASC';
+ $sql .= " ORDER BY t.statement_year ".$dir.", CAST(t.statement_number AS UNSIGNED) ".$dir;
+ } else {
+ $sql .= $this->db->order($sortfield, $sortorder);
+ }
if ($limit > 0) {
$sql .= $this->db->plimit($limit, $offset);
}
@@ -747,6 +753,7 @@ class BankImportStatement extends CommonObject
$sql = "SELECT DISTINCT statement_year";
$sql .= " FROM ".MAIN_DB_PREFIX."bankimport_statement";
$sql .= " WHERE entity = ".((int) $this->entity);
+ $sql .= " AND statement_year > 0";
$sql .= " ORDER BY statement_year DESC";
$result = array();
@@ -1166,6 +1173,14 @@ class BankImportStatement extends CommonObject
$txType = trim($rest);
}
+ // Ungueltige Daten korrigieren (z.B. 30.02. bei Monatsabschluss)
+ if (!checkdate($bookMonth, $bookDay, $bookYear)) {
+ $bookDay = (int) date('t', mktime(0, 0, 0, $bookMonth, 1, $bookYear));
+ }
+ if (!checkdate($valMonth, $valDay, $valYear)) {
+ $valDay = (int) date('t', mktime(0, 0, 0, $valMonth, 1, $valYear));
+ }
+
// Build date strings
$dateBooking = sprintf('%04d-%02d-%02d', $bookYear, $bookMonth, $bookDay);
$dateValue = sprintf('%04d-%02d-%02d', $valYear, $valMonth, $valDay);
diff --git a/class/fints.class.php b/class/fints.class.php
index b6a89c8..77c5447 100755
--- a/class/fints.class.php
+++ b/class/fints.class.php
@@ -38,8 +38,8 @@ use Fhp\Options\Credentials;
use Fhp\Action\GetSEPAAccounts;
use Fhp\Action\GetStatementOfAccount;
use Fhp\Action\GetStatementOfAccountXML;
-use Fhp\Action\GetStatementPDF;
-use Fhp\Action\GetStatementFromArchive;
+use Fhp\Action\GetBankStatement;
+use Fhp\Action\GetElectronicStatement;
use Fhp\Model\StatementOfAccount\Statement;
use Fhp\Model\StatementOfAccount\Transaction;
@@ -296,16 +296,28 @@ class BankImportFinTS
*/
public function login($tanModeId = null)
{
+ dol_syslog("BankImport FinTS: login() gestartet, tanModeId=".($tanModeId ?: 'auto'), LOG_DEBUG);
+
if ($this->fints === null) {
+ dol_syslog("BankImport FinTS: Keine bestehende Verbindung, rufe initConnection() auf", LOG_DEBUG);
$result = $this->initConnection();
+ dol_syslog("BankImport FinTS: initConnection() Ergebnis=".$result, LOG_DEBUG);
if ($result < 0) {
+ dol_syslog("BankImport FinTS: initConnection() FEHLGESCHLAGEN: ".$this->error, LOG_ERR);
return -1;
}
}
try {
// Get and select TAN mode
+ dol_syslog("BankImport FinTS: Rufe getTanModes() ab...", LOG_DEBUG);
$tanModes = $this->fints->getTanModes();
+ dol_syslog("BankImport FinTS: ".count($tanModes)." TAN-Modi verfuegbar", LOG_DEBUG);
+
+ foreach ($tanModes as $mode) {
+ dol_syslog("BankImport FinTS: TAN-Modus: ID=".$mode->getId()." Name=".$mode->getName()
+ ." Decoupled=".($mode->isDecoupled() ? 'JA' : 'NEIN'), LOG_DEBUG);
+ }
if ($tanModeId !== null) {
foreach ($tanModes as $mode) {
@@ -330,24 +342,34 @@ class BankImportFinTS
if ($this->selectedTanMode === null) {
$this->error = 'No TAN mode available';
+ dol_syslog("BankImport FinTS: KEIN TAN-Modus ausgewaehlt", LOG_ERR);
return -1;
}
+ dol_syslog("BankImport FinTS: Ausgewaehlter TAN-Modus: ID=".$this->selectedTanMode->getId()
+ ." Name=".$this->selectedTanMode->getName()
+ ." Decoupled=".($this->selectedTanMode->isDecoupled() ? 'JA' : 'NEIN'), LOG_DEBUG);
+
$this->fints->selectTanMode($this->selectedTanMode);
+ dol_syslog("BankImport FinTS: TAN-Modus gesetzt, starte fints->login()...", LOG_DEBUG);
// Perform login
$login = $this->fints->login();
+ dol_syslog("BankImport FinTS: fints->login() abgeschlossen, needsTan=".($login->needsTan() ? 'JA' : 'NEIN'), LOG_DEBUG);
if ($login->needsTan()) {
$this->pendingAction = $login;
$tanRequest = $login->getTanRequest();
$this->tanChallenge = $tanRequest->getChallenge();
+ dol_syslog("BankImport FinTS: TAN benoetigt, Challenge-Laenge=".strlen($this->tanChallenge), LOG_DEBUG);
return 0; // TAN required
}
+ dol_syslog("BankImport FinTS: Login erfolgreich (kein TAN benoetigt)", LOG_DEBUG);
return 1;
} catch (Exception $e) {
$this->error = $e->getMessage();
+ dol_syslog("BankImport FinTS: Login EXCEPTION: ".$e->getMessage()."\n".$e->getTraceAsString(), LOG_ERR);
return -1;
}
}
@@ -417,7 +439,10 @@ class BankImportFinTS
*/
public function getBankParameters()
{
+ dol_syslog("BankImport FinTS: getBankParameters() aufgerufen", LOG_DEBUG);
+
if ($this->fints === null) {
+ dol_syslog("BankImport FinTS: getBankParameters() - fints===null", LOG_WARNING);
return array('error' => 'Not connected');
}
@@ -429,22 +454,30 @@ class BankImportFinTS
$bpd = $bpdProperty->getValue($this->fints);
if ($bpd === null) {
+ dol_syslog("BankImport FinTS: getBankParameters() - BPD ist null (noch kein Login?)", LOG_WARNING);
return array('error' => 'BPD not available');
}
+ dol_syslog("BankImport FinTS: BPD-Klasse: ".get_class($bpd), LOG_DEBUG);
+
// Get parameters property from BPD
$bpdReflection = new \ReflectionClass($bpd);
$paramsProperty = $bpdReflection->getProperty('parameters');
$paramsProperty->setAccessible(true);
$parameters = $paramsProperty->getValue($bpd);
+ dol_syslog("BankImport FinTS: BPD enthaelt ".count($parameters)." Segment-Typen", LOG_DEBUG);
+
$result = array();
foreach ($parameters as $type => $versions) {
$result[$type] = array_keys($versions);
}
+ dol_syslog("BankImport FinTS: BPD-Segmente: ".implode(', ', array_keys($result)), LOG_DEBUG);
+
return $result;
} catch (Exception $e) {
+ dol_syslog("BankImport FinTS: getBankParameters() Exception: ".$e->getMessage(), LOG_ERR);
return array('error' => $e->getMessage());
}
}
@@ -910,6 +943,224 @@ class BankImportFinTS
}
}
+ /**
+ * Prueft ob die Bank elektronische Kontoauszuege (PDF) unterstuetzt
+ *
+ * Prueft sowohl HKEKP (HIEPS) als auch HKEKA (HIEKAS).
+ *
+ * @return bool True wenn HIEPS oder HIEKAS in den BPD vorhanden
+ */
+ public function supportsStatementPdf()
+ {
+ if ($this->fints === null) {
+ return false;
+ }
+
+ try {
+ $params = $this->getBankParameters();
+ return isset($params['HIEPS']) || isset($params['HIEKAS']);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Elektronische Kontoauszuege als PDF von der Bank abrufen (HKEKP)
+ *
+ * @param string|null $statementNumber Optionale Auszugsnummer
+ * @param string|null $year Optionales Jahr (JJJJ)
+ * @return array|int Array mit PDF-Daten oder -1 bei Fehler, 0 wenn TAN benoetigt
+ */
+ public function fetchBankStatements($statementNumber = null, $year = null)
+ {
+ dol_syslog("BankImport HKEKP: ===== fetchBankStatements() START =====", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: Parameter: statementNumber=".($statementNumber ?: 'null').", year=".($year ?: 'null'), LOG_DEBUG);
+
+ if (!$this->isConfigured()) {
+ $this->error = 'Konfiguration unvollstaendig';
+ dol_syslog("BankImport HKEKP: ABBRUCH - nicht konfiguriert", LOG_ERR);
+ return -1;
+ }
+
+ if ($this->fints === null) {
+ $this->error = 'Nicht verbunden. Bitte zuerst login() aufrufen.';
+ dol_syslog("BankImport HKEKP: ABBRUCH - fints===null, kein Login", LOG_ERR);
+ return -1;
+ }
+
+ try {
+ // Konten abrufen
+ dol_syslog("BankImport HKEKP: Rufe GetSEPAAccounts ab...", LOG_DEBUG);
+ $getAccounts = GetSEPAAccounts::create();
+ $this->fints->execute($getAccounts);
+ dol_syslog("BankImport HKEKP: GetSEPAAccounts ausgefuehrt", LOG_DEBUG);
+
+ if ($getAccounts->needsTan()) {
+ dol_syslog("BankImport HKEKP: GetSEPAAccounts benoetigt TAN", LOG_DEBUG);
+ $this->pendingAction = $getAccounts;
+ $tanRequest = $getAccounts->getTanRequest();
+ $this->tanChallenge = $tanRequest->getChallenge();
+ return 0;
+ }
+
+ $accounts = $getAccounts->getAccounts();
+ dol_syslog("BankImport HKEKP: ".count($accounts)." SEPA-Konten gefunden", LOG_DEBUG);
+
+ foreach ($accounts as $idx => $acc) {
+ dol_syslog("BankImport HKEKP: Konto ".($idx+1).": IBAN=".$acc->getIban()
+ .", BIC=".$acc->getBic()
+ .", Ktonr=".$acc->getAccountNumber()
+ .", BLZ=".$acc->getSubAccount(), LOG_DEBUG);
+ }
+
+ // Passendes Konto per IBAN finden
+ $selectedAccount = null;
+ dol_syslog("BankImport HKEKP: Suche Konto mit IBAN=".$this->iban, LOG_DEBUG);
+ foreach ($accounts as $account) {
+ if ($account->getIban() === $this->iban) {
+ $selectedAccount = $account;
+ dol_syslog("BankImport HKEKP: IBAN-Match gefunden!", LOG_DEBUG);
+ break;
+ }
+ }
+
+ if ($selectedAccount === null) {
+ if (!empty($accounts)) {
+ $selectedAccount = $accounts[0];
+ dol_syslog("BankImport HKEKP: KEIN IBAN-Match! Verwende erstes Konto: ".$selectedAccount->getIban(), LOG_WARNING);
+ } else {
+ $this->error = 'Keine Konten gefunden';
+ dol_syslog("BankImport HKEKP: ABBRUCH - keine Konten gefunden", LOG_ERR);
+ return -1;
+ }
+ }
+
+ // BPD (Bank Parameter Data) abfragen und loggen
+ dol_syslog("BankImport HKEKP: Rufe getBankParameters() ab...", LOG_DEBUG);
+ $params = $this->getBankParameters();
+ dol_syslog("BankImport HKEKP: BPD enthaelt ".count($params)." Geschaeftsvorfaelle", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: BPD-Segmente: ".implode(', ', array_keys($params)), LOG_DEBUG);
+
+ // Detailliert loggen welche Versionen unterstuetzt werden
+ foreach ($params as $segName => $versions) {
+ if (in_array($segName, ['HIEPS', 'HIEKAS', 'HIKEP', 'HIKAS', 'HIKAZS', 'HICAZS', 'HIPINS'])) {
+ dol_syslog("BankImport: BPD Detail: ".$segName." Versionen=[".implode(',', $versions)."]", LOG_DEBUG);
+ }
+ }
+
+ // Strategie: HKEKP (PDF-spezifisch) bevorzugen, Fallback auf HKEKA (generisch)
+ $useHkekp = isset($params['HIEPS']);
+ $useHkeka = isset($params['HIEKAS']);
+
+ dol_syslog("BankImport: HIEPS (HKEKP)=".($useHkekp ? 'JA' : 'NEIN')
+ .", HIEKAS (HKEKA)=".($useHkeka ? 'JA' : 'NEIN'), LOG_DEBUG);
+
+ if (!$useHkekp && !$useHkeka) {
+ $this->error = 'Die Bank unterstuetzt keine elektronischen Kontoauszuege (weder HKEKP/HIEPS noch HKEKA/HIEKAS in BPD)';
+ dol_syslog("BankImport: Weder HIEPS noch HIEKAS in BPD! Kontoauszuege nicht unterstuetzt.", LOG_WARNING);
+ dol_syslog("BankImport: Alle verfuegbaren BPD-Parameter: ".implode(', ', array_keys($params)), LOG_WARNING);
+ return -1;
+ }
+
+ dol_syslog("BankImport: Starte Abruf fuer ".$selectedAccount->getIban()
+ .($statementNumber ? " Nr.".$statementNumber : " (alle)")
+ .($year ? " Jahr ".$year : " (alle Jahre)"), LOG_DEBUG);
+
+ $pdfStatements = [];
+
+ if ($useHkekp) {
+ // === HKEKP: PDF-spezifischer Kontoauszug ===
+ dol_syslog("BankImport HKEKP: HIEPS gefunden! Versionen: [".implode(',', $params['HIEPS'])."]", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: Erstelle GetBankStatement Action...", LOG_DEBUG);
+ $getBankStatement = GetBankStatement::create($selectedAccount, $statementNumber, $year);
+ dol_syslog("BankImport HKEKP: Fuehre GetBankStatement aus (fints->execute)...", LOG_DEBUG);
+ $this->fints->execute($getBankStatement);
+ dol_syslog("BankImport HKEKP: GetBankStatement ausgefuehrt", LOG_DEBUG);
+
+ if ($getBankStatement->needsTan()) {
+ dol_syslog("BankImport HKEKP: GetBankStatement benoetigt TAN", LOG_DEBUG);
+ $this->pendingAction = $getBankStatement;
+ $tanRequest = $getBankStatement->getTanRequest();
+ $this->tanChallenge = $tanRequest->getChallenge();
+ return 0;
+ }
+
+ $pdfStatements = $getBankStatement->getPdfStatements();
+ dol_syslog("BankImport HKEKP: ".count($pdfStatements)." PDFs empfangen", LOG_DEBUG);
+ } else {
+ // === HKEKA: Generischer Kontoauszug (mit Format=PDF) ===
+ dol_syslog("BankImport HKEKA: HIEKAS gefunden! Versionen: [".implode(',', $params['HIEKAS'])."]", LOG_DEBUG);
+ dol_syslog("BankImport HKEKA: Verwende HKEKA mit Format=3 (PDF)", LOG_DEBUG);
+ dol_syslog("BankImport HKEKA: Erstelle GetElectronicStatement Action...", LOG_DEBUG);
+
+ $getStatement = GetElectronicStatement::create(
+ $selectedAccount,
+ GetElectronicStatement::FORMAT_PDF,
+ $statementNumber,
+ $year
+ );
+
+ dol_syslog("BankImport HKEKA: Fuehre GetElectronicStatement aus...", LOG_DEBUG);
+ $this->fints->execute($getStatement);
+ dol_syslog("BankImport HKEKA: GetElectronicStatement ausgefuehrt", LOG_DEBUG);
+
+ if ($getStatement->needsTan()) {
+ dol_syslog("BankImport HKEKA: GetElectronicStatement benoetigt TAN", LOG_DEBUG);
+ $this->pendingAction = $getStatement;
+ $tanRequest = $getStatement->getTanRequest();
+ $this->tanChallenge = $tanRequest->getChallenge();
+ return 0;
+ }
+
+ // Alle Statements holen, nach PDF filtern
+ $allStatements = $getStatement->getStatements();
+ dol_syslog("BankImport HKEKA: ".count($allStatements)." Statements empfangen", LOG_DEBUG);
+
+ foreach ($allStatements as $sIdx => $stmt) {
+ $format = $stmt['format'] ?? 'unbekannt';
+ $data = $stmt['data'];
+ dol_syslog("BankImport HKEKA: Statement ".($sIdx+1).": Format=".$format
+ .", ".strlen($data)." Bytes, Anfang=".substr($data, 0, 10), LOG_DEBUG);
+ }
+
+ // PDFs extrahieren (getPdfStatements filtert nach Format=3 oder %PDF-)
+ $pdfStatements = $getStatement->getPdfStatements();
+ dol_syslog("BankImport HKEKA: Davon ".count($pdfStatements)." PDFs", LOG_DEBUG);
+
+ // Falls keine PDFs, aber andere Formate: Hinweis
+ if (empty($pdfStatements) && !empty($allStatements)) {
+ $formats = array_map(function($s) { return $s['format'] ?? '?'; }, $allStatements);
+ dol_syslog("BankImport HKEKA: Keine PDFs! Empfangene Formate: ".implode(', ', $formats), LOG_WARNING);
+ $this->error = 'Kontoauszuege empfangen, aber nicht im PDF-Format (Formate: '.implode(', ', $formats).')';
+ return -1;
+ }
+ }
+
+ foreach ($pdfStatements as $pIdx => $pdf) {
+ dol_syslog("BankImport: PDF ".($pIdx+1).": ".strlen($pdf)." Bytes, beginnt mit: ".substr($pdf, 0, 10), LOG_DEBUG);
+ }
+
+ dol_syslog("BankImport: ===== fetchBankStatements() ERFOLG - ".count($pdfStatements)." PDFs =====", LOG_DEBUG);
+
+ return array(
+ 'count' => count($pdfStatements),
+ 'pdfs' => $pdfStatements,
+ 'iban' => $selectedAccount->getIban(),
+ );
+ } catch (\Fhp\Protocol\UnexpectedResponseException $e) {
+ // Bank unterstuetzt den Geschaeftsvorfall nicht
+ $this->error = 'Kontoauszug-Abruf nicht unterstuetzt: '.$e->getMessage();
+ dol_syslog("BankImport HKEKP: UnexpectedResponseException: ".$e->getMessage(), LOG_WARNING);
+ dol_syslog("BankImport HKEKP: Stack-Trace: ".$e->getTraceAsString(), LOG_DEBUG);
+ return -1;
+ } catch (Exception $e) {
+ $this->error = 'Fehler beim Kontoauszug-Abruf: '.$e->getMessage();
+ dol_syslog("BankImport HKEKP: Exception (".get_class($e)."): ".$e->getMessage(), LOG_ERR);
+ dol_syslog("BankImport HKEKP: Stack-Trace: ".$e->getTraceAsString(), LOG_DEBUG);
+ return -1;
+ }
+ }
+
/**
* Close FinTS connection
*
@@ -1019,301 +1270,4 @@ class BankImportFinTS
{
return $this->iban;
}
-
- /**
- * Get PDF bank statement via HKEKP
- *
- * @param int $accountIndex Index of account to use (default 0)
- * @param int|null $statementNumber Optional: specific statement number
- * @param int|null $statementYear Optional: statement year
- * @return array|int Array with 'pdfData' and 'info', or 0 if TAN required, or -1 on error
- */
- public function getStatementPDF($accountIndex = 0, $statementNumber = null, $statementYear = null)
- {
- global $conf;
-
- $this->error = '';
-
- if (!$this->fints) {
- $this->error = 'Not connected';
- return -1;
- }
-
- try {
- // Get accounts if not cached
- if (empty($this->accounts)) {
- $getAccounts = GetSEPAAccounts::create();
- $this->fints->execute($getAccounts);
-
- if ($getAccounts->needsTan()) {
- $this->pendingAction = $getAccounts;
- $tanRequest = $getAccounts->getTanRequest();
- $this->tanChallenge = $tanRequest->getChallenge();
- return 0;
- }
-
- $this->accounts = $getAccounts->getAccounts();
- }
-
- if (empty($this->accounts) || !isset($this->accounts[$accountIndex])) {
- $this->error = 'No accounts available or invalid account index';
- return -1;
- }
-
- $selectedAccount = $this->accounts[$accountIndex];
-
- dol_syslog("BankImport: Fetching PDF statement via HKEKP", LOG_DEBUG);
-
- $getPdf = GetStatementPDF::create(
- $selectedAccount,
- $statementNumber,
- $statementYear
- );
-
- $this->fints->execute($getPdf);
-
- if ($getPdf->needsTan()) {
- $this->pendingAction = $getPdf;
- $tanRequest = $getPdf->getTanRequest();
- $this->tanChallenge = $tanRequest->getChallenge();
- return 0;
- }
-
- $pdfData = $getPdf->getPdfData();
- $info = $getPdf->getStatementInfo();
-
- if (empty($pdfData)) {
- dol_syslog("BankImport: No PDF data received (no new statements available)", LOG_DEBUG);
- return array(
- 'pdfData' => '',
- 'info' => array(),
- 'message' => 'No new statements available'
- );
- }
-
- dol_syslog("BankImport: Received PDF statement #".$info['statementNumber'].'/'.$info['statementYear'], LOG_DEBUG);
-
- return array(
- 'pdfData' => $pdfData,
- 'info' => $info
- );
-
- } catch (Exception $e) {
- $this->error = $e->getMessage();
- dol_syslog("BankImport: HKEKP failed: ".$this->error, LOG_ERR);
-
- // Check if bank doesn't support HKEKP
- if (stripos($this->error, 'HKEKP') !== false || stripos($this->error, 'not support') !== false) {
- $this->error = 'Bank does not support PDF statements (HKEKP)';
- }
-
- return -1;
- }
- }
-
- /**
- * Check if bank supports PDF statements (HKEKP)
- *
- * @return bool
- */
- public function supportsPdfStatements()
- {
- if (!$this->fints) {
- return false;
- }
-
- try {
- // Try to get BPD and check for HIEKPS
- $reflection = new ReflectionClass($this->fints);
- $bpdProperty = $reflection->getProperty('bpd');
- $bpdProperty->setAccessible(true);
- $bpd = $bpdProperty->getValue($this->fints);
-
- if ($bpd === null) {
- return false;
- }
-
- $hiekps = $bpd->getLatestSupportedParameters('HIEKPS');
- return $hiekps !== null;
-
- } catch (Exception $e) {
- return false;
- }
- }
-
- /**
- * Check if bank supports archive statements (HKKAA) with PDF format
- *
- * @return bool
- */
- public function supportsArchiveStatements()
- {
- if (!$this->fints) {
- return false;
- }
-
- try {
- $reflection = new ReflectionClass($this->fints);
- $bpdProperty = $reflection->getProperty('bpd');
- $bpdProperty->setAccessible(true);
- $bpd = $bpdProperty->getValue($this->fints);
-
- if ($bpd === null) {
- return false;
- }
-
- $hikaas = $bpd->getLatestSupportedParameters('HIKAAS');
- if ($hikaas === null) {
- return false;
- }
-
- // Check if PDF format is supported
- $param = $hikaas->getParameter();
- return $param->supportsPdf();
-
- } catch (Exception $e) {
- dol_syslog("BankImport: supportsArchiveStatements exception: ".$e->getMessage(), LOG_DEBUG);
- return false;
- }
- }
-
- /**
- * Check if bank supports any PDF statement method (HKEKP or HKKAA)
- *
- * @return string|false Method name ('HKEKP', 'HKKAA') or false if none supported
- */
- public function getPdfStatementMethod()
- {
- // First try HKEKP (direct PDF)
- if ($this->supportsPdfStatements()) {
- return 'HKEKP';
- }
-
- // Fallback to HKKAA (archive)
- if ($this->supportsArchiveStatements()) {
- return 'HKKAA';
- }
-
- return false;
- }
-
- /**
- * Get PDF bank statement from archive via HKKAA
- *
- * @param int $accountIndex Index of account to use (default 0)
- * @param \DateTime|null $fromDate Optional: start date
- * @param \DateTime|null $toDate Optional: end date
- * @return array|int Array with 'pdfData' and 'info', or 0 if TAN required, or -1 on error
- */
- public function getStatementFromArchive($accountIndex = 0, $fromDate = null, $toDate = null)
- {
- global $conf;
-
- $this->error = '';
-
- if (!$this->fints) {
- $this->error = 'Not connected';
- return -1;
- }
-
- try {
- // Get accounts if not cached
- if (empty($this->accounts)) {
- $getAccounts = GetSEPAAccounts::create();
- $this->fints->execute($getAccounts);
-
- if ($getAccounts->needsTan()) {
- $this->pendingAction = $getAccounts;
- $tanRequest = $getAccounts->getTanRequest();
- $this->tanChallenge = $tanRequest->getChallenge();
- return 0;
- }
-
- $this->accounts = $getAccounts->getAccounts();
- }
-
- if (empty($this->accounts) || !isset($this->accounts[$accountIndex])) {
- $this->error = 'No accounts available or invalid account index';
- return -1;
- }
-
- $selectedAccount = $this->accounts[$accountIndex];
-
- dol_syslog("BankImport: Fetching PDF statement from archive via HKKAA", LOG_DEBUG);
-
- $getArchive = GetStatementFromArchive::create(
- $selectedAccount,
- GetStatementFromArchive::FORMAT_PDF,
- $fromDate,
- $toDate
- );
-
- $this->fints->execute($getArchive);
-
- if ($getArchive->needsTan()) {
- $this->pendingAction = $getArchive;
- $tanRequest = $getArchive->getTanRequest();
- $this->tanChallenge = $tanRequest->getChallenge();
- return 0;
- }
-
- $pdfData = $getArchive->getPdfData();
- $info = $getArchive->getStatementInfo();
-
- if (empty($pdfData)) {
- dol_syslog("BankImport: No PDF data received from archive", LOG_DEBUG);
- return array(
- 'pdfData' => '',
- 'info' => array(),
- 'message' => 'No statements available in archive'
- );
- }
-
- dol_syslog("BankImport: Received PDF statement from archive #".($info['statementNumber'] ?? '?').'/'.($info['statementYear'] ?? '?'), LOG_DEBUG);
-
- return array(
- 'pdfData' => $pdfData,
- 'info' => $info
- );
-
- } catch (Exception $e) {
- $this->error = $e->getMessage();
- dol_syslog("BankImport: HKKAA failed: ".$this->error, LOG_ERR);
- dol_syslog("BankImport: HKKAA Exception class: ".get_class($e), LOG_DEBUG);
-
- // Keep original error message for better debugging
- return -1;
- }
- }
-
- /**
- * Get PDF statement using best available method (HKEKP or HKKAA)
- *
- * @param int $accountIndex Index of account to use (default 0)
- * @param int|null $statementNumber Optional: specific statement number (only for HKEKP)
- * @param int|null $statementYear Optional: statement year (only for HKEKP)
- * @return array|int Array with 'pdfData', 'info' and 'method', or 0 if TAN required, or -1 on error
- */
- public function getStatementPDFAuto($accountIndex = 0, $statementNumber = null, $statementYear = null)
- {
- $method = $this->getPdfStatementMethod();
-
- if ($method === false) {
- $this->error = 'Bank does not support PDF statements (neither HKEKP nor HKKAA)';
- return -1;
- }
-
- if ($method === 'HKEKP') {
- $result = $this->getStatementPDF($accountIndex, $statementNumber, $statementYear);
- } else {
- // HKKAA doesn't support statement number, use date range instead
- $result = $this->getStatementFromArchive($accountIndex);
- }
-
- if (is_array($result)) {
- $result['method'] = $method;
- }
-
- return $result;
- }
}
diff --git a/composer.json b/composer.json
index 85f0bb9..d77bfa8 100755
--- a/composer.json
+++ b/composer.json
@@ -5,7 +5,7 @@
"license": "GPL-3.0-or-later",
"require": {
"php": ">=8.0",
- "nemiah/php-fints": "^4.0"
+ "nemiah/php-fints": "^3.2"
},
"replace": {
"psr/log": "*"
diff --git a/composer.lock b/composer.lock
index ca4143e..4f0aff3 100755
--- a/composer.lock
+++ b/composer.lock
@@ -4,26 +4,26 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "32eb1d84f3157a4dee83ef5a81763257",
+ "content-hash": "cfc07b7e6c4a3dcfdcd6e754983b1a9b",
"packages": [
{
"name": "nemiah/php-fints",
- "version": "4.0.0",
+ "version": "3.7.0",
"source": {
"type": "git",
"url": "https://github.com/nemiah/phpFinTS.git",
- "reference": "b37e6df7efd39b4e757537e782241d5abb6b2bb5"
+ "reference": "08257e10229db2d4ca8c54ed7fec0f390b332519"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nemiah/phpFinTS/zipball/b37e6df7efd39b4e757537e782241d5abb6b2bb5",
- "reference": "b37e6df7efd39b4e757537e782241d5abb6b2bb5",
+ "url": "https://api.github.com/repos/nemiah/phpFinTS/zipball/08257e10229db2d4ca8c54ed7fec0f390b332519",
+ "reference": "08257e10229db2d4ca8c54ed7fec0f390b332519",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-mbstring": "*",
- "php": ">=8.3",
+ "php": ">=8.0",
"psr/log": "^1|^2|^3"
},
"require-dev": {
@@ -51,9 +51,9 @@
"homepage": "https://github.com/nemiah/phpFinTS",
"support": {
"issues": "https://github.com/nemiah/phpFinTS/issues",
- "source": "https://github.com/nemiah/phpFinTS/tree/4.0"
+ "source": "https://github.com/nemiah/phpFinTS/tree/3.7"
},
- "time": "2026-01-16T07:56:30+00:00"
+ "time": "2025-10-14T15:05:56+00:00"
}
],
"packages-dev": [],
diff --git a/core/modules/modBankImport.class.php b/core/modules/modBankImport.class.php
index 981fd06..8c4aa20 100755
--- a/core/modules/modBankImport.class.php
+++ b/core/modules/modBankImport.class.php
@@ -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 = '3.5';
+ $this->version = '3.4';
// Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
@@ -281,20 +281,6 @@ class modBankImport extends DolibarrModules
'test' => 'isModEnabled("bankimport") && getDolGlobalInt("BANKIMPORT_AUTO_ENABLED")',
'priority' => 50,
),
- 1 => array(
- 'label' => 'BankImportPdfFetch',
- 'jobtype' => 'method',
- 'class' => '/bankimport/class/bankimportcron.class.php',
- 'objectname' => 'BankImportCron',
- 'method' => 'doAutoFetchPdf',
- 'parameters' => '',
- 'comment' => 'Automatic PDF statement fetch via HKEKP',
- 'frequency' => 1,
- 'unitfrequency' => 86400, // Daily
- 'status' => 0, // Disabled by default
- 'test' => 'isModEnabled("bankimport") && getDolGlobalInt("BANKIMPORT_PDF_AUTO_ENABLED")',
- 'priority' => 51,
- ),
);
/* END MODULEBUILDER CRON */
// Example: $this->cronjobs=array(
diff --git a/langs/de_DE/bankimport.lang b/langs/de_DE/bankimport.lang
index 7e1d68d..f9de815 100755
--- a/langs/de_DE/bankimport.lang
+++ b/langs/de_DE/bankimport.lang
@@ -55,10 +55,10 @@ SecurityInfo = Sicherheitshinweis
SecurityInfoText = Die PIN wird verschlüsselt in der Datenbank gespeichert. Für zusätzliche Sicherheit sollte Ihre Dolibarr-Installation HTTPS verwenden.
#
-# Kontoauszüge Seite
+# Umsaetze Seite
#
-BankStatements = Kontoauszüge
-FetchStatements = Kontoauszüge abrufen
+BankStatements = Umsätze
+FetchStatements = Umsätze abrufen
Transactions = Buchungen
TransactionsFound = %s Buchungen gefunden
NoTransactionsFound = Keine Buchungen im ausgewählten Zeitraum gefunden
@@ -377,26 +377,22 @@ Open = Öffnen
CashDiscount = Skonto
#
-# PDF Statement Fetch (HKEKP)
+# FinTS Kontoauszug-Abruf (HKEKP)
#
+AutoFetchStatements = Kontoauszüge automatisch von der Bank abrufen
+AutoFetchStatementsDesc = Lädt die neuesten elektronischen Kontoauszüge (PDF) direkt von der Bank per FinTS herunter.
FetchFromBank = Von Bank abrufen
-FetchNewStatements = Neue Auszüge abrufen
-FetchNewStatementsDesc = Ruft alle noch nicht abgerufenen PDF-Kontoauszüge von der Bank ab
-FetchSpecificStatement = Bestimmten Auszug abrufen
-Fetch = Abrufen
-ErrorFinTSNotConfigured = FinTS-Verbindung nicht konfiguriert
-ErrorFinTSConnection = FinTS-Verbindungsfehler
-ErrorBankDoesNotSupportPdfStatements = Bank unterstützt keine PDF-Kontoauszüge (HKEKP)
-ErrorFetchingPdfStatement = Fehler beim Abrufen des PDF-Kontoauszugs
-NoPdfStatementsAvailable = Keine neuen PDF-Kontoauszüge verfügbar
-PdfStatementFetched = PDF-Kontoauszug %s erfolgreich abgerufen
-ErrorSavingPdfFile = Fehler beim Speichern der PDF-Datei
-FinTSNotConfiguredForPdf = FinTS nicht konfiguriert - PDF-Abruf nicht möglich
-AutoPdfFetchDisabled = Automatischer PDF-Abruf ist deaktiviert
-BankImportPdfFetch = PDF-Kontoauszüge abrufen
-PdfFetchCronDescription = Automatischer Abruf von PDF-Kontoauszügen via FinTS (HKEKP)
-PdfStatementsFetched = %s PDF-Kontoauszüge abgerufen
-ManualUpload = Manuell hochladen
-Source = Quelle
-SourceUpload = Hochgeladen
-SourceFinTS = FinTS-Abruf
+FinTSLibraryMissing = phpFinTS-Bibliothek nicht installiert
+WaitingForTanConfirmation = Warte auf TAN-Bestätigung in der SecureGo Plus App...
+TanCheckFailed = TAN-Prüfung fehlgeschlagen
+TanTimeout = TAN-Bestätigung: Zeitüberschreitung (2 Minuten)
+TanRequiredForStatements = TAN-Bestätigung für Kontoauszug-Abruf erforderlich
+FetchStatementsFailed = Kontoauszug-Abruf fehlgeschlagen
+NoStatementsAvailable = Keine neuen Kontoauszüge bei der Bank verfügbar
+StatementsDownloaded = %s von %s Kontoauszügen erfolgreich heruntergeladen und gespeichert
+StatementsDownloadErrors = %s Kontoauszüge konnten nicht gespeichert werden
+StatementsSkippedNoNumber = %s PDF(s) ohne Auszugsnummer übersprungen (vermutlich Saldenmitteilungen)
+StatementsNotPdfFormat = Kontoauszüge empfangen, aber nicht im PDF-Format
+StatementsUsingHKEKA = Nutze HKEKA (generischer Kontoauszug) statt HKEKP
+StatementsUsingHKEKP = Nutze HKEKP (PDF-Kontoauszug)
+NeitherHKEKPnorHKEKA = Die Bank unterstützt weder HKEKP noch HKEKA für elektronische Kontoauszüge
diff --git a/langs/en_US/bankimport.lang b/langs/en_US/bankimport.lang
index e4c5c3d..6be3788 100755
--- a/langs/en_US/bankimport.lang
+++ b/langs/en_US/bankimport.lang
@@ -55,10 +55,10 @@ SecurityInfo = Security Information
SecurityInfoText = The PIN is stored encrypted in the database. For additional security, ensure your Dolibarr installation uses HTTPS.
#
-# Statements page
+# Transactions page
#
-BankStatements = Bank Statements
-FetchStatements = Fetch Statements
+BankStatements = Transactions
+FetchStatements = Fetch Transactions
Transactions = Transactions
TransactionsFound = %s transactions found
NoTransactionsFound = No transactions found in the selected period
@@ -271,3 +271,24 @@ Open = Open
# Cash Discount / Skonto
#
CashDiscount = Cash Discount
+
+#
+# FinTS Bank Statement Retrieval (HKEKP)
+#
+AutoFetchStatements = Automatically fetch bank statements
+AutoFetchStatementsDesc = Downloads the latest electronic bank statements (PDF) directly from the bank via FinTS.
+FetchFromBank = Fetch from bank
+FinTSLibraryMissing = phpFinTS library not installed
+WaitingForTanConfirmation = Waiting for TAN confirmation in SecureGo Plus app...
+TanCheckFailed = TAN check failed
+TanTimeout = TAN confirmation: Timeout (2 minutes)
+TanRequiredForStatements = TAN confirmation required for statement retrieval
+FetchStatementsFailed = Bank statement retrieval failed
+NoStatementsAvailable = No new bank statements available at the bank
+StatementsDownloaded = %s of %s bank statements successfully downloaded and saved
+StatementsDownloadErrors = %s bank statements could not be saved
+StatementsSkippedNoNumber = %s PDF(s) without statement number skipped (probably balance notifications)
+StatementsNotPdfFormat = Bank statements received but not in PDF format
+StatementsUsingHKEKA = Using HKEKA (generic statement) instead of HKEKP
+StatementsUsingHKEKP = Using HKEKP (PDF statement)
+NeitherHKEKPnorHKEKA = The bank supports neither HKEKP nor HKEKA for electronic statements
diff --git a/pdfstatements.php b/pdfstatements.php
index b7c7c30..2c6549d 100755
--- a/pdfstatements.php
+++ b/pdfstatements.php
@@ -73,103 +73,296 @@ if (!$user->hasRight('bankimport', 'read')) {
$statement = new BankImportStatement($db);
-// Fetch PDF statement from bank via HKEKP
-if ($action == 'fetchpdf' || $action == 'fetchpdf_single') {
+// FinTS: Elektronische Kontoauszuege automatisch abrufen (HKEKP)
+if ($action == 'fetchfints') {
+ dol_syslog("BankImport HKEKP: ========== START fetchfints Action ==========", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: User=".$user->login." (ID ".$user->id."), Zeitpunkt=".date('Y-m-d H:i:s'), LOG_DEBUG);
+
if (!$user->hasRight('bankimport', 'write')) {
+ dol_syslog("BankImport HKEKP: Zugriff verweigert - User hat kein Schreibrecht", LOG_WARNING);
accessforbidden();
}
- $fetchNumber = ($action == 'fetchpdf_single') ? GETPOSTINT('fetch_number') : null;
- $fetchYear = ($action == 'fetchpdf_single') ? GETPOSTINT('fetch_year') : null;
+ $fints = new BankImportFinTS($db);
- // Check FinTS configuration
- $fintsUrl = getDolGlobalString('BANKIMPORT_FINTS_URL');
- $fintsBlz = getDolGlobalString('BANKIMPORT_FINTS_BLZ');
- $fintsUser = getDolGlobalString('BANKIMPORT_FINTS_USERNAME');
- $fintsPin = getDolGlobalString('BANKIMPORT_FINTS_PIN');
+ dol_syslog("BankImport HKEKP: FinTS-Objekt erstellt, isConfigured=".($fints->isConfigured() ? 'JA' : 'NEIN')
+ .", isLibraryAvailable=".($fints->isLibraryAvailable() ? 'JA' : 'NEIN'), LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: Konfiguration - URL=".getDolGlobalString('BANKIMPORT_FINTS_URL')
+ .", BLZ=".getDolGlobalString('BANKIMPORT_FINTS_BLZ')
+ .", IBAN=".getDolGlobalString('BANKIMPORT_FINTS_IBAN')
+ .", User=".getDolGlobalString('BANKIMPORT_FINTS_USERNAME'), LOG_DEBUG);
- if (empty($fintsUrl) || empty($fintsBlz) || empty($fintsUser) || empty($fintsPin)) {
- setEventMessages($langs->trans("ErrorFinTSNotConfigured"), null, 'errors');
+ if (!$fints->isConfigured()) {
+ dol_syslog("BankImport HKEKP: ABBRUCH - FinTS nicht konfiguriert", LOG_WARNING);
+ setEventMessages($langs->trans("FinTSNotConfigured"), null, 'errors');
+ } elseif (!$fints->isLibraryAvailable()) {
+ dol_syslog("BankImport HKEKP: ABBRUCH - phpFinTS Library fehlt", LOG_WARNING);
+ setEventMessages($langs->trans("FinTSLibraryMissing"), null, 'errors');
} else {
- $fints = new BankImportFinTS($db);
+ // Login
+ dol_syslog("BankImport HKEKP: Starte login()...", LOG_DEBUG);
$loginResult = $fints->login();
+ dol_syslog("BankImport HKEKP: login() Ergebnis=".$loginResult." (1=OK, 0=TAN, -1=Fehler)", LOG_DEBUG);
- if ($loginResult < 0) {
- setEventMessages($langs->trans("ErrorFinTSConnection").': '.$fints->error, null, 'errors');
- } elseif ($loginResult === 0) {
- // TAN required for login
- setEventMessages($langs->trans("TANRequired").($fints->tanChallenge ? ': '.$fints->tanChallenge : ''), null, 'warnings');
- $fints->close();
- } else {
- // Check if bank supports any PDF statement method (HKEKP or HKKAA)
- $pdfMethod = $fints->getPdfStatementMethod();
- if ($pdfMethod === false) {
- setEventMessages($langs->trans("ErrorBankDoesNotSupportPdfStatements"), null, 'errors');
- $fints->close();
+ if ($loginResult == -1) {
+ dol_syslog("BankImport HKEKP: Login FEHLGESCHLAGEN - ".$fints->error, LOG_ERR);
+ setEventMessages('FinTS Login fehlgeschlagen: '.$fints->error, null, 'errors');
+ }
+
+ if ($loginResult == 0) {
+ // TAN benoetigt - Decoupled-Polling
+ $tanConfirmed = false;
+ $maxWait = 120; // Max 2 Minuten warten
+ $waited = 0;
+
+ if ($fints->selectedTanMode && $fints->selectedTanMode->isDecoupled()) {
+ dol_syslog("BankImport HKEKP: TAN-Modus: ".$fints->selectedTanMode->getName()
+ ." (ID ".$fints->selectedTanMode->getId().", Decoupled=JA)", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: Starte Decoupled-TAN-Polling (max ".$maxWait."s, Intervall 3s)...", LOG_DEBUG);
+ setEventMessages($langs->trans("WaitingForTanConfirmation"), null, 'mesgs');
+
+ while ($waited < $maxWait) {
+ sleep(3);
+ $waited += 3;
+ $tanStatus = $fints->checkDecoupledTan();
+ dol_syslog("BankImport HKEKP: TAN-Poll nach ".$waited."s - Status=".$tanStatus." (1=OK, 0=Wartend, -1=Fehler)", LOG_DEBUG);
+
+ if ($tanStatus == 1) {
+ $tanConfirmed = true;
+ dol_syslog("BankImport HKEKP: TAN BESTAETIGT nach ".$waited."s", LOG_DEBUG);
+ break;
+ } elseif ($tanStatus < 0) {
+ dol_syslog("BankImport HKEKP: TAN-Pruefung FEHLGESCHLAGEN: ".$fints->error, LOG_ERR);
+ setEventMessages($langs->trans("TanCheckFailed").': '.$fints->error, null, 'errors');
+ break;
+ }
+ }
+
+ if (!$tanConfirmed && $waited >= $maxWait) {
+ dol_syslog("BankImport HKEKP: TAN-TIMEOUT nach ".$waited."s", LOG_WARNING);
+ setEventMessages($langs->trans("TanTimeout"), null, 'errors');
+ }
} else {
- dol_syslog("BankImport: Using PDF method: ".$pdfMethod, LOG_DEBUG);
+ $tanModeName = $fints->selectedTanMode ? $fints->selectedTanMode->getName() : 'UNBEKANNT';
+ $tanModeDecoupled = $fints->selectedTanMode ? ($fints->selectedTanMode->isDecoupled() ? 'JA' : 'NEIN') : '?';
+ dol_syslog("BankImport HKEKP: TAN-Modus: ".$tanModeName." (Decoupled=".$tanModeDecoupled.") - Manuell benoetigt!", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: TAN-Challenge: ".$fints->tanChallenge, LOG_DEBUG);
+ setEventMessages($langs->trans("TanRequired").': '.$fints->tanChallenge, null, 'warnings');
+ }
- // Fetch PDF using auto method (tries HKEKP first, falls back to HKKAA)
- $pdfResult = $fints->getStatementPDFAuto(0, $fetchNumber, $fetchYear);
+ if (!$tanConfirmed) {
+ dol_syslog("BankImport HKEKP: TAN nicht bestaetigt, schliesse Verbindung", LOG_DEBUG);
+ $fints->close();
+ $action = '';
+ } else {
+ $loginResult = 1; // Weiter mit Abruf
+ }
+ }
- if ($pdfResult === 0) {
- // TAN required - save to session and show TAN form
- $_SESSION['bankimport_pending_action'] = serialize($fints);
- setEventMessages($langs->trans("TANRequired").': '.$fints->tanChallenge, null, 'warnings');
- } elseif ($pdfResult === -1) {
- setEventMessages($langs->trans("ErrorFetchingPdfStatement").': '.$fints->error, null, 'errors');
- } elseif (empty($pdfResult['pdfData'])) {
- setEventMessages($langs->trans("NoPdfStatementsAvailable"), null, 'warnings');
+ if ($loginResult == 1) {
+ // Kontoauszuege abrufen
+ $fetchYear = GETPOST('fetch_year', 'alpha') ?: null;
+ dol_syslog("BankImport HKEKP: Login erfolgreich, starte fetchBankStatements(year="
+ .($fetchYear ?: 'ALLE').")", LOG_DEBUG);
+
+ $result = $fints->fetchBankStatements(null, $fetchYear);
+ dol_syslog("BankImport HKEKP: fetchBankStatements() Ergebnis-Typ=".gettype($result)
+ .(is_array($result) ? " count=".$result['count'] : " val=".$result), LOG_DEBUG);
+
+ if ($result === 0) {
+ dol_syslog("BankImport HKEKP: fetchBankStatements benoetigt TAN", LOG_WARNING);
+ setEventMessages($langs->trans("TanRequiredForStatements"), null, 'warnings');
+ } elseif ($result === -1) {
+ dol_syslog("BankImport HKEKP: fetchBankStatements FEHLGESCHLAGEN: ".$fints->error, LOG_ERR);
+ setEventMessages($langs->trans("FetchStatementsFailed").': '.$fints->error, null, 'errors');
+ } elseif (is_array($result)) {
+ $pdfCount = $result['count'];
+ $savedCount = 0;
+ $errorCountFints = 0;
+
+ dol_syslog("BankImport HKEKP: ".$pdfCount." PDFs empfangen, IBAN=".$result['iban'], LOG_DEBUG);
+
+ if ($pdfCount == 0) {
+ dol_syslog("BankImport HKEKP: Keine Auszuege verfuegbar", LOG_DEBUG);
+ setEventMessages($langs->trans("NoStatementsAvailable"), null, 'warnings');
} else {
- // Save PDF
- $info = $pdfResult['info'];
- $pdfData = $pdfResult['pdfData'];
+ // PDFs speichern ueber die bestehende bankstatement-Logik
+ $dir = BankImportStatement::getStorageDir();
+ dol_syslog("BankImport HKEKP: Speicher-Verzeichnis: ".$dir, LOG_DEBUG);
- // Check if statement already exists
- $stmt = new BankImportStatement($db);
- $stmt->statement_number = $info['statementNumber'];
- $stmt->statement_year = $info['statementYear'];
- $stmt->iban = $info['iban'] ?: getDolGlobalString('BANKIMPORT_IBAN');
-
- if ($stmt->exists()) {
- setEventMessages($langs->trans("StatementAlreadyExists").': '.$stmt->statement_number.'/'.$stmt->statement_year, null, 'warnings');
- } else {
- // Save PDF to file
- $dir = BankImportStatement::getStorageDir();
- $ibanPart = preg_replace('/[^A-Z0-9]/', '', strtoupper($stmt->iban ?: 'KONTO'));
- $filename = sprintf('Kontoauszug_%s_%d_%s.pdf',
- $ibanPart,
- $stmt->statement_year,
- str_pad($stmt->statement_number, 3, '0', STR_PAD_LEFT)
- );
-
- $filepath = $dir.'/'.$filename;
- if (file_put_contents($filepath, $pdfData) !== false) {
- $stmt->filename = $filename;
- $stmt->filepath = $filepath;
- $stmt->filesize = strlen($pdfData);
- $stmt->statement_date = $info['creationDate'] ? $info['creationDate']->getTimestamp() : dol_now();
- $stmt->import_key = 'fints_'.date('YmdHis');
-
- $result = $stmt->create($user);
- if ($result > 0) {
- setEventMessages($langs->trans("PdfStatementFetched", $stmt->statement_number.'/'.$stmt->statement_year), null, 'mesgs');
- $year = $stmt->statement_year;
- } else {
- setEventMessages($stmt->error, null, 'errors');
- @unlink($filepath);
- }
+ // Identische PDFs deduplizieren (Bank sendet teilweise Duplikate)
+ $seenHashes = [];
+ $uniquePdfs = [];
+ foreach ($result['pdfs'] as $pdfData) {
+ $hash = md5($pdfData);
+ if (!isset($seenHashes[$hash])) {
+ $seenHashes[$hash] = true;
+ $uniquePdfs[] = $pdfData;
} else {
- setEventMessages($langs->trans("ErrorSavingPdfFile"), null, 'errors');
+ dol_syslog("BankImport HKEKP: Duplikat-PDF uebersprungen (Hash=".substr($hash, 0, 8)."...)", LOG_DEBUG);
}
}
+ $result['pdfs'] = $uniquePdfs;
+ $pdfCount = count($uniquePdfs);
+ $skippedCount = 0;
+
+ foreach ($result['pdfs'] as $idx => $pdfData) {
+ dol_syslog("BankImport HKEKP: --- PDF ".($idx+1)."/".$pdfCount." ---", LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: PDF-Groesse=".strlen($pdfData)." Bytes, Erste 20 Bytes=".bin2hex(substr($pdfData, 0, 20)), LOG_DEBUG);
+ dol_syslog("BankImport HKEKP: PDF startet mit: ".substr($pdfData, 0, 10), LOG_DEBUG);
+
+ // PDF in Temp-Datei schreiben fuer Metadaten-Extraktion
+ $tmpFile = tempnam(sys_get_temp_dir(), 'fints_stmt_');
+ file_put_contents($tmpFile, $pdfData);
+ dol_syslog("BankImport HKEKP: Temp-Datei: ".$tmpFile." (".filesize($tmpFile)." Bytes)", LOG_DEBUG);
+
+ // Metadaten aus PDF parsen
+ dol_syslog("BankImport HKEKP: Starte parsePdfMetadata()...", LOG_DEBUG);
+ $parsed = BankImportStatement::parsePdfMetadata($tmpFile);
+
+ if ($parsed) {
+ dol_syslog("BankImport HKEKP: Metadaten erkannt: IBAN=".$parsed['iban']
+ .", Nr=".$parsed['statement_number'].", Jahr=".$parsed['statement_year']
+ .", Datum=".$parsed['statement_date']
+ .", Von=".$parsed['date_from'].", Bis=".$parsed['date_to']
+ .", Saldo_Start=".$parsed['opening_balance'].", Saldo_Ende=".$parsed['closing_balance'], LOG_DEBUG);
+ } else {
+ dol_syslog("BankImport HKEKP: Metadaten NICHT erkannt - verwende Fallback", LOG_WARNING);
+ }
+
+ $stmt = new BankImportStatement($db);
+ if ($parsed && !empty($parsed['statement_number'])) {
+ // Vollstaendige Metadaten mit Auszugsnummer
+ $stmt->iban = $parsed['iban'] ?: $result['iban'];
+ $stmt->statement_number = $parsed['statement_number'];
+ $stmt->statement_year = $parsed['statement_year'];
+ $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'];
+ } elseif ($parsed && !empty($parsed['iban'])) {
+ // IBAN erkannt aber keine Auszugsnummer (z.B. Saldenmitteilung)
+ // Ueberspringe solche PDFs - ohne Nummer nicht sinnvoll speicherbar
+ dol_syslog("BankImport HKEKP: PDF uebersprungen - IBAN erkannt aber keine Auszugsnummer (wahrscheinlich Saldenmitteilung)", LOG_WARNING);
+ $skippedCount++;
+ @unlink($tmpFile);
+ continue;
+ } else {
+ // Keinerlei Metadaten - Fallback mit Index
+ $stmt->iban = $result['iban'];
+ $stmt->statement_number = (string) ($idx + 1);
+ $stmt->statement_year = (int) date('Y');
+ }
+
+ $stmt->import_key = 'fints_'.date('YmdHis').'_'.$user->id;
+ dol_syslog("BankImport HKEKP: import_key=".$stmt->import_key, LOG_DEBUG);
+
+ // Duplikat-Pruefung
+ if ($stmt->statement_number && $stmt->exists()) {
+ dol_syslog("BankImport HKEKP: DUPLIKAT - Auszug ".$stmt->statement_number."/".$stmt->statement_year." existiert bereits, ueberspringe", LOG_DEBUG);
+ @unlink($tmpFile);
+ continue;
+ }
+
+ // Dateiname generieren
+ if ($parsed) {
+ $newFilename = BankImportStatement::generateFilename($parsed);
+ } else {
+ $newFilename = sprintf('Kontoauszug_FinTS_%s_%d_%03d.pdf',
+ preg_replace('/[^A-Z0-9]/', '', strtoupper($stmt->iban)),
+ $stmt->statement_year,
+ $idx + 1
+ );
+ }
+ dol_syslog("BankImport HKEKP: Dateiname=".$newFilename, LOG_DEBUG);
+
+ $stmt->filepath = $dir.'/'.$newFilename;
+
+ // Kollisionsvermeidung
+ if (file_exists($stmt->filepath)) {
+ $newFilename = pathinfo($newFilename, PATHINFO_FILENAME).'_'.date('His').'.pdf';
+ $stmt->filepath = $dir.'/'.$newFilename;
+ dol_syslog("BankImport HKEKP: Datei existiert bereits, neuer Name: ".$newFilename, LOG_DEBUG);
+ }
+
+ $stmt->filename = $newFilename;
+
+ // PDF von Temp nach Ziel verschieben
+ if (!rename($tmpFile, $stmt->filepath)) {
+ dol_syslog("BankImport HKEKP: rename() fehlgeschlagen, verwende copy()", LOG_DEBUG);
+ copy($tmpFile, $stmt->filepath);
+ @unlink($tmpFile);
+ }
+
+ $stmt->filesize = filesize($stmt->filepath);
+ dol_syslog("BankImport HKEKP: Datei gespeichert: ".$stmt->filepath." (".$stmt->filesize." Bytes)", LOG_DEBUG);
+
+ // In DB speichern
+ dol_syslog("BankImport HKEKP: Starte DB create()...", LOG_DEBUG);
+ $dbResult = $stmt->create($user);
+ dol_syslog("BankImport HKEKP: DB create() Ergebnis=".$dbResult." (>0=ID, <0=Fehler)", LOG_DEBUG);
+
+ if ($dbResult > 0) {
+ dol_syslog("BankImport HKEKP: DB-Eintrag erstellt mit ID=".$dbResult, LOG_DEBUG);
+
+ // FinTS-Transaktionen verknuepfen
+ dol_syslog("BankImport HKEKP: Starte linkTransactions()...", LOG_DEBUG);
+ $linkResult = $stmt->linkTransactions();
+ dol_syslog("BankImport HKEKP: linkTransactions() Ergebnis=".$linkResult, LOG_DEBUG);
+
+ // PDF-Einzelbuchungen parsen
+ dol_syslog("BankImport HKEKP: Starte parsePdfTransactions()...", LOG_DEBUG);
+ $pdfLines = $stmt->parsePdfTransactions();
+ dol_syslog("BankImport HKEKP: parsePdfTransactions() ergab ".(is_array($pdfLines) ? count($pdfLines) : 0)." Buchungszeilen", LOG_DEBUG);
+ if (!empty($pdfLines)) {
+ $stmt->saveStatementLines($pdfLines);
+ dol_syslog("BankImport HKEKP: saveStatementLines() abgeschlossen", LOG_DEBUG);
+ }
+
+ // PDF in Dolibarr Bank-Verzeichnis kopieren
+ $uploadBankAccountId = getDolGlobalInt('BANKIMPORT_BANK_ACCOUNT_ID');
+ dol_syslog("BankImport HKEKP: BANKIMPORT_BANK_ACCOUNT_ID=".$uploadBankAccountId, LOG_DEBUG);
+ if ($uploadBankAccountId > 0) {
+ dol_syslog("BankImport HKEKP: Starte copyToDolibarrStatementDir()...", LOG_DEBUG);
+ $stmt->copyToDolibarrStatementDir($uploadBankAccountId);
+ dol_syslog("BankImport HKEKP: Starte reconcileBankEntries()...", LOG_DEBUG);
+ $reconcileResult = $stmt->reconcileBankEntries($user, $uploadBankAccountId);
+ dol_syslog("BankImport HKEKP: reconcileBankEntries() Ergebnis=".$reconcileResult, LOG_DEBUG);
+ }
+
+ $savedCount++;
+ } else {
+ $errorCountFints++;
+ dol_syslog("BankImport HKEKP: DB-FEHLER bei create(): ".$stmt->error, LOG_ERR);
+ if (file_exists($stmt->filepath)) {
+ @unlink($stmt->filepath);
+ }
+ }
+ }
+
+ dol_syslog("BankImport HKEKP: Zusammenfassung: ".$savedCount." gespeichert, ".$errorCountFints." Fehler, ".$skippedCount." uebersprungen von ".$pdfCount." PDFs", LOG_DEBUG);
+
+ if ($savedCount > 0) {
+ setEventMessages($langs->trans("StatementsDownloaded", $savedCount, $pdfCount), null, 'mesgs');
+ }
+ if ($errorCountFints > 0) {
+ setEventMessages($langs->trans("StatementsDownloadErrors", $errorCountFints), null, 'warnings');
+ }
+ if ($skippedCount > 0) {
+ setEventMessages($langs->trans("StatementsSkippedNoNumber", $skippedCount), null, 'warnings');
+ }
}
}
$fints->close();
+ dol_syslog("BankImport HKEKP: FinTS-Verbindung geschlossen", LOG_DEBUG);
}
+
+ dol_syslog("BankImport HKEKP: ========== ENDE fetchfints Action ==========", LOG_DEBUG);
+ header("Location: ".$_SERVER['PHP_SELF']."?year=".date('Y'));
+ exit;
}
- $action = '';
}
// Upload PDF (supports multiple files)
@@ -622,6 +815,18 @@ print ''.$langs->trans("PDFStatementsInfo").'
';
print $langs->trans("PDFStatementsInfoDesc");
print '';
+// FinTS-Abruf Button (wenn konfiguriert)
+$fintsCheck = new BankImportFinTS($db);
+if ($fintsCheck->isConfigured() && $fintsCheck->isLibraryAvailable() && $user->hasRight('bankimport', 'write')) {
+ print '
| '.img_picto('', 'download', 'class="pictofixedwidth"').$langs->trans("FetchFromBank").' | '; - print '|
| ';
- print '';
- print img_picto('', 'refresh', 'class="pictofixedwidth"').$langs->trans("FetchNewStatements");
- print '';
- print ' '.$langs->trans("FetchNewStatementsDesc").''; - print ' | ';
- print '|
| '; - print ''; - print ' | '; - print '