From 92e15d54c182a956ebaef11929bcb8cdae891184 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Mon, 10 Aug 2026 17:10:59 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20=CE=A3-Kennzeichen=20lie=C3=9F=20sich=20?= =?UTF-8?q?beim=20Konto=20nicht=20abschalten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein nicht angehaktes Ankreuzfeld steht nicht im POST. KontenController:: formulardaten() reichte "in_summe" gar nicht durch, also griff im Model der Standardwert "zählt zur Summe" – die Anwendung meldete "Konto gespeichert", geändert hatte sich nichts. formulardaten() nimmt das POST-Array jetzt als Parameter und ist damit ohne HTTP prüfbar; die Ankreuzfelder werden ausdrücklich auf true/false abgebildet. Der Standard bleibt für Aufrufe ohne den Schlüssel bestehen – der QIF-Import gibt das Feld nicht an. Co-Authored-By: Claude Opus 5 --- Changes.md | 16 +++++++++ src/Controllers/KontenController.php | 51 ++++++++++++++++++---------- src/Models/Konto.php | 5 ++- tests/07_KontenTest.php | 49 ++++++++++++++++++++++++++ tests/e2e/specs/stammdaten.spec.js | 34 +++++++++++++++++++ 5 files changed, 137 insertions(+), 18 deletions(-) diff --git a/Changes.md b/Changes.md index 3d1b156..adf1fab 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,21 @@ # Änderungen +## Korrektur – Σ-Kennzeichen ließ sich nicht abschalten (10.08.2026) + +Das Kennzeichen *Zählt zur Summe der Kontostände* blieb beim Speichern eines Kontos +unverändert stehen, obwohl die Anwendung „Konto gespeichert“ meldete. + +- Ursache: `KontenController::formulardaten()` reichte `in_summe` gar nicht durch. Ein + nicht angehaktes Ankreuzfeld steht nicht im POST, also griff im Model der Standardwert + „zählt mit“ – gespeichert wurde ein Ja, das niemand angegeben hatte. +- `formulardaten()` nimmt das POST-Array jetzt als Parameter und ist damit ohne HTTP + prüfbar; die Ankreuzfelder werden ausdrücklich auf `true`/`false` abgebildet. +- Der Standard „zählt zur Summe“ bleibt für Aufrufe ohne den Schlüssel bestehen – der + QIF-Import gibt das Feld nicht an und soll es auch nicht. +- `07_KontenTest` prüft die Formularabbildung und den Weg durch das Model; E2E + `stammdaten.spec.js` hakt das Kennzeichen in der Oberfläche ab, setzt es wieder und + sieht in beiden Fällen nach, statt der Erfolgsmeldung zu glauben. + ## Phase 2 – Buchungen, Kontoblatt und Buchungsimport (10.08.2026) **Erkenntnis aus dem Vollexport** diff --git a/src/Controllers/KontenController.php b/src/Controllers/KontenController.php index b9e9b53..4ee67d9 100644 --- a/src/Controllers/KontenController.php +++ b/src/Controllers/KontenController.php @@ -44,7 +44,7 @@ class KontenController extends BaseController $this->verlangeAnmeldung(); try { - $id = Konto::anlegen($this->formulardaten()); + $id = Konto::anlegen(self::formulardaten($_POST)); } catch (\Throwable $e) { View::redirect('/konten/neu', $e->getMessage(), 'fehler'); return; @@ -83,7 +83,7 @@ class KontenController extends BaseController } try { - Konto::speichern($id, $this->formulardaten()); + Konto::speichern($id, self::formulardaten($_POST)); } catch (\Throwable $e) { View::redirect('/konten/' . $id . '/bearbeiten', $e->getMessage(), 'fehler'); return; @@ -107,23 +107,40 @@ class KontenController extends BaseController View::redirect('/konten', 'Konto „' . $konto['name'] . '“ gelöscht.'); } - /** @return array */ - private function formulardaten(): array + /** + * Bildet das Kontoformular auf die Modellfelder ab. + * + * Öffentlich und statisch, damit die Abbildung ohne HTTP prüfbar ist: Ein + * nicht angehaktes Ankreuzfeld steht überhaupt nicht im POST, es muss hier + * ausdrücklich zu `false` werden. Fehlt ein solches Feld in dieser Liste, + * lässt es sich in der Oberfläche nie wieder abschalten – die Meldung sagt + * „gespeichert“, der alte Wert bleibt stehen. + * + * @param array $post + * @return array + */ + public static function formulardaten(array $post): array { + $feld = static function (string $name, string $standard = '') use ($post): string { + $wert = $post[$name] ?? $standard; + return is_string($wert) ? trim($wert) : $standard; + }; + return [ - 'name' => $this->post('name'), - 'kontoart' => $this->post('kontoart', 'bank'), - 'kontengruppe_id' => $this->post('kontengruppe_id'), - 'bankname' => $this->post('bankname'), - 'kontonummer' => $this->post('kontonummer'), - 'iban' => $this->post('iban'), - 'bic' => $this->post('bic'), - 'beschreibung' => $this->post('beschreibung'), - 'anfangssaldo' => self::zahl($this->post('anfangssaldo', '0')), - 'kreditlimit' => $this->post('kreditlimit') === '' ? null : self::zahl($this->post('kreditlimit')), - 'waehrung' => $this->post('waehrung', 'EUR'), - 'aktiv' => isset($_POST['aktiv']), - 'sortierung' => (int)$this->post('sortierung', '0'), + 'name' => $feld('name'), + 'kontoart' => $feld('kontoart', 'bank'), + 'kontengruppe_id' => $feld('kontengruppe_id'), + 'bankname' => $feld('bankname'), + 'kontonummer' => $feld('kontonummer'), + 'iban' => $feld('iban'), + 'bic' => $feld('bic'), + 'beschreibung' => $feld('beschreibung'), + 'anfangssaldo' => self::zahl($feld('anfangssaldo', '0')), + 'kreditlimit' => $feld('kreditlimit') === '' ? null : self::zahl($feld('kreditlimit')), + 'waehrung' => $feld('waehrung', 'EUR'), + 'aktiv' => isset($post['aktiv']), + 'in_summe' => isset($post['in_summe']), + 'sortierung' => (int)$feld('sortierung', '0'), ]; } diff --git a/src/Models/Konto.php b/src/Models/Konto.php index cb640fb..6c79282 100644 --- a/src/Models/Konto.php +++ b/src/Models/Konto.php @@ -277,7 +277,10 @@ class Konto 'kreditlimit' => $limit, 'waehrung' => strtoupper(trim((string)($daten['waehrung'] ?? 'EUR'))) ?: 'EUR', 'aktiv' => !empty($daten['aktiv']) ? 1 : 0, - // Standard ist "zählt zur Summe"; nur ein ausdrückliches Nein schaltet es ab. + // Standard ist "zählt zur Summe"; nur ein ausdrückliches Nein schaltet + // es ab. Der QIF-Import lässt das Feld deshalb weg. Wer aus einem + // Formular kommt, muss den Schlüssel dagegen immer mitgeben – siehe + // KontenController::formulardaten(). 'in_summe' => array_key_exists('in_summe', $daten) && empty($daten['in_summe']) ? 0 : 1, 'sortierung' => (int)($daten['sortierung'] ?? 0), ]; diff --git a/tests/07_KontenTest.php b/tests/07_KontenTest.php index 9e77014..22c5618 100644 --- a/tests/07_KontenTest.php +++ b/tests/07_KontenTest.php @@ -18,6 +18,7 @@ class KontenTest extends TestCase $this->kontoarten(); $this->uebersichtGruppiertUndSummiert(); $this->betragsEingabe(); + $this->ankreuzfelderAusDemFormular(); } private function anlegenUndFinden(): void @@ -155,4 +156,52 @@ class KontenTest extends TestCase $this->assertEquals(0.0, KontenController::zahl(''), 'Leere Eingabe ergibt null'); $this->assertEquals(99.9, KontenController::zahl(' 99,90 € '), 'Währungszeichen und Leerzeichen stören nicht'); } + + /** + * Ein nicht angehaktes Ankreuzfeld schickt der Browser gar nicht mit. Die + * Formularabbildung muss daraus ein ausdrückliches Nein machen, sonst lässt + * sich das Kennzeichen nie wieder abschalten – „Konto gespeichert“ steht + * dann da, geändert hat sich nichts. + */ + private function ankreuzfelderAusDemFormular(): void + { + $angehakt = KontenController::formulardaten([ + 'name' => 'Girokonto', 'kontoart' => 'bank', 'aktiv' => '1', 'in_summe' => '1', + ]); + $this->assert($angehakt['aktiv'] === true, 'Angehaktes „aktiv“ kommt als Ja an'); + $this->assert($angehakt['in_summe'] === true, 'Angehaktes Σ-Kennzeichen kommt als Ja an'); + + $leer = KontenController::formulardaten(['name' => 'Girokonto', 'kontoart' => 'bank']); + $this->assert($leer['aktiv'] === false, 'Fehlendes „aktiv“ heißt inaktiv'); + $this->assert($leer['in_summe'] === false, 'Fehlendes Σ-Kennzeichen heißt: zählt nicht zur Summe'); + + // Und derselbe Weg durch das Model hindurch: Abhaken muss ankommen. + $id = Konto::anlegen(KontenController::formulardaten([ + 'name' => 'Auslagen Eltern', 'kontoart' => 'sonstiges', + 'anfangssaldo' => '1.500,00', 'aktiv' => '1', 'in_summe' => '1', + ])); + $this->assertEquals(1, (int)Konto::find($id)['in_summe'], 'Neues Konto zählt zunächst mit'); + $this->assertEquals(1500.0, (float)Konto::find($id)['anfangssaldo'], 'Anfangssaldo aus dem Formular'); + + Konto::speichern($id, KontenController::formulardaten([ + 'name' => 'Auslagen Eltern', 'kontoart' => 'sonstiges', + 'anfangssaldo' => '1.500,00', 'aktiv' => '1', + ])); + $this->assertEquals(0, (int)Konto::find($id)['in_summe'], 'Σ-Kennzeichen lässt sich entfernen'); + + $uebersicht = Konto::uebersicht(); + $gefunden = false; + foreach ($uebersicht['gruppen'] as $gruppe) { + foreach ($gruppe['konten'] as $konto) { + if ((int)$konto['id'] === $id) { + $gefunden = true; + } + } + } + $this->assert($gefunden, 'Das Konto bleibt in der Übersicht sichtbar'); + + // Der QIF-Import gibt das Feld nicht an – dort gilt weiter der Standard. + $importiert = Konto::anlegen(['name' => 'Aus dem Export', 'kontoart' => 'bank', 'aktiv' => 1]); + $this->assertEquals(1, (int)Konto::find($importiert)['in_summe'], 'Ohne Angabe zählt ein Konto mit'); + } } diff --git a/tests/e2e/specs/stammdaten.spec.js b/tests/e2e/specs/stammdaten.spec.js index d1af814..55f98c5 100644 --- a/tests/e2e/specs/stammdaten.spec.js +++ b/tests/e2e/specs/stammdaten.spec.js @@ -55,6 +55,40 @@ test.describe('Stammdaten', () => { await expect(page.locator('.summenzeile')).toContainText('Summe Kontostände') }) + // Ein nicht angehaktes Ankreuzfeld schickt der Browser gar nicht mit. Bleibt + // es serverseitig unbeachtet, meldet die Anwendung „Konto gespeichert“ und + // das Kennzeichen steht unverändert da. + test('Σ-Kennzeichen lässt sich abschalten und wieder setzen', async ({ page }) => { + const name = `E2E Auslagen ${MARKE}` + + await page.goto('/konten/neu') + await page.fill('#name', name) + await page.selectOption('#kontoart', 'sonstiges') + await page.fill('#anfangssaldo', '500,00') + await page.click('.formular button[type="submit"]') + await expect(page.locator('.meldung-erfolg')).toBeVisible() + + const kennzeichen = page.locator('input[name="in_summe"]') + await expect(kennzeichen).toBeChecked() + + await kennzeichen.uncheck() + await page.locator('.formular button[type="submit"]').first().click() + await expect(page.locator('.meldung-erfolg')).toBeVisible() + + // In der Übersicht bleibt das Konto sichtbar, aber als „ohne Σ“ markiert. + const zeile = page.locator('tr', { hasText: name }) + await expect(zeile).toContainText('ohne Σ') + + // Und im Formular ist das Häkchen tatsächlich weg – nicht nur die Meldung da. + await zeile.locator('a').first().click() + await page.click('a.tab[href$="/bearbeiten"]') + await expect(page.locator('input[name="in_summe"]')).not.toBeChecked() + + await page.locator('input[name="in_summe"]').check() + await page.locator('.formular button[type="submit"]').first().click() + await expect(page.locator('tr', { hasText: name })).not.toContainText('ohne Σ') + }) + test('Kategorie mit Unterkategorie anlegen', async ({ page }) => { const oben = `E2E Wohnen ${MARKE}` const unten = `Strom ${MARKE}`