Merge pull request 'Fix #219: Bank buchen standardmäßig älteste Umsätze zuerst' (#220) from fds2610-bot/Steuerkram:main into main
Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/220
This commit is contained in:
commit
6a0634eacf
6 changed files with 96 additions and 3 deletions
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-10-12 – Bank buchen: älteste Umsätze zuerst (#219)
|
||||||
|
|
||||||
|
- **`src/Models/BankTransaction.php`** (`all()`) **und `src/Views/bank/index.php`:** Die Standard-Sortierrichtung ist jetzt aufsteigend statt absteigend – ohne `dir`-Parameter erscheinen die ältesten Umsätze oben, damit datumsgetreu gebucht wird und die Buchungsnummern zeitlich fortlaufend entstehen. Im Filter ist „↑ Aufsteigend“ vorbelegt; „↓ Absteigend“ bleibt über `dir=desc` wählbar, unbekannte Werte fallen auf aufsteigend zurück.
|
||||||
|
- **`tests/104_BankSortierungAeltesteZuerstTest.php`** (neu) und **`tests/e2e/specs/bank-sortierung.spec.js`** (neu): prüfen die Standardreihenfolge, `dir=desc` und die Vorbelegung im Filter.
|
||||||
|
|
||||||
## 2026-10-06 – Header-Injection über Dateinamen im Content-Disposition-Header behoben (#217)
|
## 2026-10-06 – Header-Injection über Dateinamen im Content-Disposition-Header behoben (#217)
|
||||||
|
|
||||||
- **`src/View.php`:** neue Methode `View::contentDispositionHeader(string $disposition, string $filename): string`. `PATHINFO_BASENAME` entfernt beim Beleg-Upload nur Verzeichnisanteile, nicht aber Anführungszeichen oder Backslashes im client-seitig gewählten Dateinamen – ein präparierter Name wie `legit"; filename*=UTF-8''%2e%2e%2fevil.exe` brach beim Download aus dem `filename="..."`-Parameter aus und schleuste einen zusätzlichen, von vielen Browsern bevorzugt ausgewerteten `filename*=`-Parameter ein. Damit ließ sich der im Download-Dialog angezeigte Dateiname/die Endung gegenüber anderen Nutzern fälschen (Dateityp-Spoofing). Die neue Methode maskiert Anführungszeichen, Backslashes und Steuerzeichen im ASCII-`filename`-Parameter und setzt zusätzlich RFC-6266-konform `filename*=UTF-8''...` mit dem unveränderten, `rawurlencode()`-kodierten Namen.
|
- **`src/View.php`:** neue Methode `View::contentDispositionHeader(string $disposition, string $filename): string`. `PATHINFO_BASENAME` entfernt beim Beleg-Upload nur Verzeichnisanteile, nicht aber Anführungszeichen oder Backslashes im client-seitig gewählten Dateinamen – ein präparierter Name wie `legit"; filename*=UTF-8''%2e%2e%2fevil.exe` brach beim Download aus dem `filename="..."`-Parameter aus und schleuste einen zusätzlichen, von vielen Browsern bevorzugt ausgewerteten `filename*=`-Parameter ein. Damit ließ sich der im Download-Dialog angezeigte Dateiname/die Endung gegenüber anderen Nutzern fälschen (Dateityp-Spoofing). Die neue Methode maskiert Anführungszeichen, Backslashes und Steuerzeichen im ASCII-`filename`-Parameter und setzt zusätzlich RFC-6266-konform `filename*=UTF-8''...` mit dem unveränderten, `rawurlencode()`-kodierten Namen.
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ Anlagenspiegel für abnutzbare Wirtschaftsgüter – siehe [anlagevermögen.md](
|
||||||
- **Beleg-Zuordnung**: jede Banktransaktion kann einem oder mehreren Belegen zugeordnet werden; der zugeordnete Betrag wird separat erfasst
|
- **Beleg-Zuordnung**: jede Banktransaktion kann einem oder mehreren Belegen zugeordnet werden; der zugeordnete Betrag wird separat erfasst
|
||||||
- **Teilzuordnung**: eine Transaktion kann schrittweise mehreren Belegen anteilig zugeordnet werden
|
- **Teilzuordnung**: eine Transaktion kann schrittweise mehreren Belegen anteilig zugeordnet werden
|
||||||
- **Status-Logik**: `offen` → keine Zuordnung, `teilweise` → Restsaldo > 0,01 €, `gebucht` → vollständig zugeordnet
|
- **Status-Logik**: `offen` → keine Zuordnung, `teilweise` → Restsaldo > 0,01 €, `gebucht` → vollständig zugeordnet
|
||||||
|
- **Sortierung**: Die Liste unter `/bank` zeigt standardmäßig die ältesten Umsätze zuerst (Wertstellung aufsteigend), damit datumsgetreu gebucht wird und die Buchungsnummern zeitlich fortlaufend vergeben werden; Sortierfeld und Richtung (*Absteigend*) sind im Filter umstellbar
|
||||||
- Direkter Beleg-Upload aus der Transaktionsansicht heraus (erzeugt neuen Beleg und verknüpft ihn)
|
- Direkter Beleg-Upload aus der Transaktionsansicht heraus (erzeugt neuen Beleg und verknüpft ihn)
|
||||||
- **Verschlüsselung**: Der `APP_KEY` in `.env` ist Pflicht für Direktverbindungen – im Backup mitsichern, Verlust = Verlust aller gespeicherten Credentials
|
- **Verschlüsselung**: Der `APP_KEY` in `.env` ist Pflicht für Direktverbindungen – im Backup mitsichern, Verlust = Verlust aller gespeicherten Credentials
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class BankTransaction
|
||||||
'status' => 'bt.status',
|
'status' => 'bt.status',
|
||||||
];
|
];
|
||||||
$sortCol = $sortMap[$filter['sort'] ?? ''] ?? 'bt.wertstellung';
|
$sortCol = $sortMap[$filter['sort'] ?? ''] ?? 'bt.wertstellung';
|
||||||
$sortDir = ($filter['dir'] ?? 'desc') === 'asc' ? 'ASC' : 'DESC';
|
$sortDir = ($filter['dir'] ?? 'asc') === 'desc' ? 'DESC' : 'ASC';
|
||||||
|
|
||||||
$whereStr = implode(' AND ', $where);
|
$whereStr = implode(' AND ', $where);
|
||||||
$offset = ($page - 1) * $perPage;
|
$offset = ($page - 1) * $perPage;
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,8 @@ $months = ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','D
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select name="dir" style="width:110px">
|
<select name="dir" style="width:110px">
|
||||||
<option value="desc"<?= ($filter['dir'] ?? 'desc') === 'desc' ? ' selected' : '' ?>>↓ Absteigend</option>
|
<option value="desc"<?= ($filter['dir'] ?? 'asc') === 'desc' ? ' selected' : '' ?>>↓ Absteigend</option>
|
||||||
<option value="asc"<?= ($filter['dir'] ?? 'desc') === 'asc' ? ' selected' : '' ?>>↑ Aufsteigend</option>
|
<option value="asc"<?= ($filter['dir'] ?? 'asc') === 'asc' ? ' selected' : '' ?>>↑ Aufsteigend</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-secondary btn-sm">Filtern</button>
|
<button type="submit" class="btn btn-secondary btn-sm">Filtern</button>
|
||||||
|
|
|
||||||
69
tests/104_BankSortierungAeltesteZuerstTest.php
Normal file
69
tests/104_BankSortierungAeltesteZuerstTest.php
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Steuerkram\Database;
|
||||||
|
use Steuerkram\Models\BankTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test 104: „Bank buchen“ sortiert standardmäßig älteste zuerst (Issue #219)
|
||||||
|
*
|
||||||
|
* Ohne ausdrückliche Sortierrichtung liefert BankTransaction::all() die
|
||||||
|
* Umsätze aufsteigend nach Wertstellung, damit datumsgetreu gebucht wird.
|
||||||
|
* Mit dir=desc bleibt die absteigende Sortierung weiterhin wählbar.
|
||||||
|
*/
|
||||||
|
class BankSortierungAeltesteZuerstTest extends TestCase
|
||||||
|
{
|
||||||
|
protected string $testName = 'Test 104: Bank buchen sortiert standardmäßig älteste zuerst';
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
Database::execute(
|
||||||
|
'INSERT INTO bank_import_batches (source_id, filename, row_count) VALUES (NULL, ?, 3)',
|
||||||
|
['test-104.csv']
|
||||||
|
);
|
||||||
|
$batchId = (int)Database::lastInsertId();
|
||||||
|
|
||||||
|
$marker = 'T104-' . bin2hex(random_bytes(4));
|
||||||
|
foreach (['2026-03-15', '2026-01-05', '2026-02-10'] as $datum) {
|
||||||
|
Database::execute(
|
||||||
|
'INSERT INTO bank_transactions (batch_id, buchungstag, wertstellung, buchungstext, wert)
|
||||||
|
VALUES (?, ?, ?, ?, -10.00)',
|
||||||
|
[$batchId, $datum, $datum, "$marker $datum"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$daten = static fn(array $result): array => array_column($result['rows'], 'wertstellung');
|
||||||
|
|
||||||
|
$standard = BankTransaction::all(['search' => $marker]);
|
||||||
|
$this->assertEquals(
|
||||||
|
['2026-01-05', '2026-02-10', '2026-03-15'],
|
||||||
|
$daten($standard),
|
||||||
|
'Ohne dir-Parameter: älteste Wertstellung zuerst'
|
||||||
|
);
|
||||||
|
|
||||||
|
$absteigend = BankTransaction::all(['search' => $marker, 'dir' => 'desc']);
|
||||||
|
$this->assertEquals(
|
||||||
|
['2026-03-15', '2026-02-10', '2026-01-05'],
|
||||||
|
$daten($absteigend),
|
||||||
|
'dir=desc: neueste Wertstellung zuerst'
|
||||||
|
);
|
||||||
|
|
||||||
|
$ungueltig = BankTransaction::all(['search' => $marker, 'dir' => 'quatsch']);
|
||||||
|
$this->assertEquals(
|
||||||
|
['2026-01-05', '2026-02-10', '2026-03-15'],
|
||||||
|
$daten($ungueltig),
|
||||||
|
'Ungültiger dir-Wert fällt auf aufsteigend zurück'
|
||||||
|
);
|
||||||
|
|
||||||
|
$view = (string)file_get_contents(dirname(__DIR__) . '/src/Views/bank/index.php');
|
||||||
|
$this->assert(
|
||||||
|
str_contains($view, "(\$filter['dir'] ?? 'asc') === 'asc'"),
|
||||||
|
'Filterauswahl zeigt „Aufsteigend“ als Vorbelegung'
|
||||||
|
);
|
||||||
|
$this->assert(
|
||||||
|
!str_contains($view, "(\$filter['dir'] ?? 'desc')"),
|
||||||
|
'Filterauswahl belegt nicht mehr „Absteigend“ vor'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tests/e2e/specs/bank-sortierung.spec.js
Normal file
18
tests/e2e/specs/bank-sortierung.spec.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// @ts-check
|
||||||
|
// „Bank buchen“ sortiert standardmäßig älteste zuerst (Issue #219): Ohne
|
||||||
|
// Parameter ist im Filter „Aufsteigend“ vorbelegt, „Absteigend“ bleibt wählbar.
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test')
|
||||||
|
const { gotoOk } = require('../helpers/app')
|
||||||
|
|
||||||
|
test.describe('Bank buchen – Sortierrichtung', () => {
|
||||||
|
test('ohne Parameter ist „Aufsteigend“ vorbelegt', async ({ page }) => {
|
||||||
|
await gotoOk(page, '/bank')
|
||||||
|
await expect(page.locator('select[name="dir"]')).toHaveValue('asc')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dir=desc bleibt ausgewählt', async ({ page }) => {
|
||||||
|
await gotoOk(page, '/bank?dir=desc')
|
||||||
|
await expect(page.locator('select[name="dir"]')).toHaveValue('desc')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Reference in a new issue