Fix: Bank-Import-Duplikate durch Transaktion, UNIQUE-Constraint und Dateiname-Prüfung verhindern
This commit is contained in:
parent
d27911b707
commit
d26bbfc37b
5 changed files with 62 additions and 4 deletions
15
Changes.md
15
Changes.md
|
|
@ -2,6 +2,21 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-05-15 – Fix: Bank-Import-Duplikate verhindern
|
||||||
|
|
||||||
|
- Migration 051: UNIQUE-Constraint `(buchungstag, wertstellung, wert, buchungstext(200))` auf `bank_transactions`; bestehende Duplikate wurden dabei bereinigt
|
||||||
|
- `BankController::upload()`: Import läuft jetzt in einer DB-Transaktion – bei Fehler wird alles zurückgerollt statt einen unvollständigen Batch zu hinterlassen
|
||||||
|
- `BankController::upload()`: Dateiname-Prüfung vor dem Import – erneuter Upload derselben Datei wird mit Fehlermeldung abgelehnt
|
||||||
|
- `BankTransaction::filenameExists()`: neue Hilfsmethode zur Duplikat-Prüfung anhand Dateiname
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-05-15 – Fix: Netto-Neuberechnung bei Kategorie-Wechsel in Bank-Buchungsmaske
|
||||||
|
|
||||||
|
- IIFE-Wrapper für `updateAutoUst` in einen `DOMContentLoaded`-Listener verschoben, damit er erst nach der Funktionsdefinition (späteres `<script>`-Tag) gesetzt wird und nicht sofort von ihr überschrieben wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-05-14 – Feature: Bank buchen
|
## 2026-05-14 – Feature: Bank buchen
|
||||||
|
|
||||||
- Navigation: neuer Eintrag „Bank buchen" unter Belege & Buchungen
|
- Navigation: neuer Eintrag „Bank buchen" unter Belege & Buchungen
|
||||||
|
|
|
||||||
22
migrations/051_bank_transactions_unique.sql
Normal file
22
migrations/051_bank_transactions_unique.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
-- Duplikate entfernen: pro Gruppe (buchungstag, wertstellung, wert, buchungstext) die niedrigste id behalten
|
||||||
|
DELETE bt
|
||||||
|
FROM bank_transactions bt
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT MIN(id) AS keep_id, buchungstag, wertstellung, wert, buchungstext
|
||||||
|
FROM bank_transactions
|
||||||
|
GROUP BY buchungstag, wertstellung, wert, buchungstext
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
) dup ON bt.buchungstag = dup.buchungstag
|
||||||
|
AND bt.wertstellung = dup.wertstellung
|
||||||
|
AND bt.wert = dup.wert
|
||||||
|
AND bt.buchungstext = dup.buchungstext
|
||||||
|
AND bt.id > dup.keep_id;
|
||||||
|
|
||||||
|
-- Unvollständige Batches entfernen (row_count stimmt nicht mit tatsächlichen Zeilen überein)
|
||||||
|
DELETE FROM bank_import_batches
|
||||||
|
WHERE id NOT IN (SELECT DISTINCT batch_id FROM bank_transactions)
|
||||||
|
AND row_count > 0;
|
||||||
|
|
||||||
|
-- UNIQUE-Constraint gegen Doppel-Importe
|
||||||
|
ALTER TABLE bank_transactions
|
||||||
|
ADD CONSTRAINT uq_bank_tx UNIQUE (buchungstag, wertstellung, wert, buchungstext(200));
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Steuerkram\Controllers;
|
namespace Steuerkram\Controllers;
|
||||||
|
|
||||||
|
use Steuerkram\Database;
|
||||||
use Steuerkram\Models\BankImportFormat;
|
use Steuerkram\Models\BankImportFormat;
|
||||||
use Steuerkram\Models\BankTransaction;
|
use Steuerkram\Models\BankTransaction;
|
||||||
use Steuerkram\Models\Beleg;
|
use Steuerkram\Models\Beleg;
|
||||||
|
|
@ -83,10 +84,21 @@ class BankController
|
||||||
View::redirect('/bank/upload', 'Keine Buchungszeilen in der Datei gefunden.', 'error');
|
View::redirect('/bank/upload', 'Keine Buchungszeilen in der Datei gefunden.', 'error');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (BankTransaction::filenameExists($origName)) {
|
||||||
|
View::redirect('/bank/upload', 'Diese Datei wurde bereits importiert (' . $origName . ').', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
Database::beginTransaction();
|
||||||
|
try {
|
||||||
$batchId = BankTransaction::createBatch($formatId, $origName, count($rows));
|
$batchId = BankTransaction::createBatch($formatId, $origName, count($rows));
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
BankTransaction::insertRow($batchId, $row);
|
BankTransaction::insertRow($batchId, $row);
|
||||||
}
|
}
|
||||||
|
Database::commit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Database::rollBack();
|
||||||
|
View::redirect('/bank/upload', 'Import fehlgeschlagen: ' . $e->getMessage(), 'error');
|
||||||
|
}
|
||||||
|
|
||||||
View::redirect('/bank', count($rows) . ' Buchungen importiert.');
|
View::redirect('/bank', count($rows) . ' Buchungen importiert.');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,15 @@ class BankTransaction
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function filenameExists(string $filename): bool
|
||||||
|
{
|
||||||
|
$row = Database::fetchOne(
|
||||||
|
'SELECT id FROM bank_import_batches WHERE filename = ? LIMIT 1',
|
||||||
|
[$filename]
|
||||||
|
);
|
||||||
|
return $row !== null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function createBatch(int $formatId, string $filename, int $rowCount): int
|
public static function createBatch(int $formatId, string $filename, int $rowCount): int
|
||||||
{
|
{
|
||||||
Database::execute(
|
Database::execute(
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1-260514
|
1-260515
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue