diff --git a/Changes.md b/Changes.md index f4c882a..299ff3a 100644 --- a/Changes.md +++ b/Changes.md @@ -6,8 +6,8 @@ - **`migrations/087_bank_sources_last_balance.sql`:** Neue Spalte `last_balance` in `bank_sources`. - **`src/Models/BankSource.php`:** `updateLastFetched()` nimmt optional den beim Abruf ermittelten Kontostand entgegen und persistiert ihn; ohne neuen Wert bleibt der zuletzt bekannte Stand erhalten (kein Verlust bei Fehlern während des Abrufs). -- **`src/Services/FinTsFetchService.php`:** Summiert nach dem Abruf pro Konto den Schlusssaldo (`getEndBalance()`, Fallback `getStartBalance()`) des letzten Statements und übergibt ihn an `updateLastFetched()`. -- **`src/Services/PayPalNvpFetchService.php`:** Der bereits vorhandene `GetBalance`-Aufruf in `testCredentials()` liefert den aktuellen Kontostand (`AMT`), der nun mitgespeichert wird. +- **`src/Services/FinTsFetchService.php`:** Fragt pro Konto zusätzlich per dedizierter `GetBalance`-Aktion (HKSAL) den aktuellen gebuchten Saldo ab und summiert diesen. Fällt nur auf den Schlusssaldo des letzten Statements zurück, wenn die Saldenabfrage fehlschlägt – der ursprüngliche Ansatz (Saldo nur aus dem Statement ableiten) lieferte keinen Wert, wenn die Bank mangels neuer Buchungen kein MT940/CAMT-Statement zurückgab (häufigster Fall bei täglichen Abrufen ohne neue Umsätze). +- **`src/Services/PayPalNvpFetchService.php`:** Der bereits vorhandene `GetBalance`-Aufruf in `testCredentials()` liefert den aktuellen Kontostand (`AMT`), der nun mitgespeichert und im Abruf-Log ausgegeben wird. - **`src/Views/bank/fints.php`:** Kachel zeigt bei vorhandenem Kontostand rechts neben dem Abrufdatum den Betrag an (gleiche Höhe wie das Datum), mit dem Label „Kontostand" rechtsbündig darüber. Kachelgröße unverändert. - **`tests/57_BankSourceLastBalanceTest.php`:** Neuer Test prüft, dass `last_balance` beim Abruf gesetzt wird und bei einem Abruf ohne ermittelten Kontostand erhalten bleibt. diff --git a/src/Services/FinTsFetchService.php b/src/Services/FinTsFetchService.php index 74978c1..7ac309a 100644 --- a/src/Services/FinTsFetchService.php +++ b/src/Services/FinTsFetchService.php @@ -7,6 +7,7 @@ namespace Steuerkram\Services; use Steuerkram\Database; use Steuerkram\Models\BankSource; +use Fhp\Action\GetBalance; use Fhp\Action\GetSEPAAccounts; use Fhp\Action\GetStatementOfAccount; use Fhp\BaseAction; @@ -225,14 +226,33 @@ class FinTsFetchService continue; } + $accountBalance = null; + try { + $balAction = GetBalance::create($account); + $fints->execute($balAction); + if ($balAction->needsTan()) { + if (!$this->handleStrongAuthentication($balAction, $iban . ' (Saldo)')) { + throw new \RuntimeException('TAN für Saldenabfrage nicht bestätigt.'); + } + } + foreach ($balAction->getBalances() as $hisal) { + $accountBalance = ($accountBalance ?? 0.0) + $hisal->getGebuchterSaldo()->getAmount(); + } + } catch (\Throwable $e) { + $log(' → Saldenabfrage fehlgeschlagen: ' . $e->getMessage()); + } + + // Fallback: Saldo aus dem letzten Statement ableiten, falls die dedizierte + // Saldenabfrage (HKSAL) fehlschlägt oder von der Bank nicht unterstützt wird. $statements = $soa->getStatements(); - if (!empty($statements)) { + if ($accountBalance === null && !empty($statements)) { $lastStatement = end($statements); $accountBalance = $lastStatement->getEndBalance() ?? $lastStatement->getStartBalance(); - if ($accountBalance !== null) { - $balance = ($balance ?? 0.0) + $accountBalance; - $log(' → Kontostand: ' . number_format($accountBalance, 2, ',', '.') . ' €'); - } + } + + if ($accountBalance !== null) { + $balance = ($balance ?? 0.0) + $accountBalance; + $log(' → Kontostand: ' . number_format($accountBalance, 2, ',', '.') . ' €'); } $accountNew = 0; diff --git a/src/Services/PayPalNvpFetchService.php b/src/Services/PayPalNvpFetchService.php index 665389d..d683463 100644 --- a/src/Services/PayPalNvpFetchService.php +++ b/src/Services/PayPalNvpFetchService.php @@ -314,6 +314,9 @@ class PayPalNvpFetchService } if (isset($result['AMT']) && is_numeric($result['AMT'])) { $this->lastBalance = (float)$result['AMT']; + ($this->log)('Kontostand: ' . number_format($this->lastBalance, 2, ',', '.') . ' ' . ($result['CURRENCYCODE'] ?? 'EUR')); + } else { + ($this->log)('Kontostand: nicht in GetBalance-Antwort enthalten (Felder: ' . implode(', ', array_keys($result)) . ')'); } return true; }