diff --git a/Changes.md b/Changes.md index 9783cff..86ae944 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,21 @@ # Änderungen +## Buchungsbericht: Splitbuchung nach Konto/Empfänger zusammengefasst (27.09.2026) + +Nach Kategorie oder Klasse gruppiert erscheint eine Splitbuchung weiterhin mit jeder +ihrer Zeilen einzeln – nur so stimmen die Gruppensummen mit dem Summenbericht überein. +Nach Konto oder Empfänger gruppiert landen aber ohnehin alle Zeilen einer Splitbuchung +in derselben Gruppe; dort erschien bisher trotzdem jede Zeile für sich, mit ihrem +Teilbetrag statt dem Gesamtbetrag – wer nur eine der beiden Zeilen sah, hielt das für +den kompletten Betrag der Buchung. + +- `Auswertung::splitsZusammenfassen()` fasst bei Gruppierung nach Konto oder Empfänger + alle Splitzeilen einer Buchung zu einer Zeile mit dem Gesamtbetrag zusammen, analog + zum Kontoblatt. Die Zeile führt keine einzelne Kategorie, sondern das Badge + „Splitbuchung“; die Aufschlüsselung bleibt dem Kontoblatt vorbehalten. +- Bei Kategorie oder Klasse bleibt es beim bisherigen Verhalten. +- Test in `tests/14_AuswertungTest.php` (`buchungsbericht`). + ## Regeln lassen sich auf Buchungen ohne Kategorie anwenden (27.09.2026) Der Knopf unter Verwaltung → Regeln hieß „Gegen offene Umsätze prüfen“ und zählte nur, diff --git a/src/Services/Auswertung.php b/src/Services/Auswertung.php index 1d0005e..02ab7c5 100644 --- a/src/Services/Auswertung.php +++ b/src/Services/Auswertung.php @@ -595,8 +595,13 @@ class Auswertung { $namen = self::namen((string)$filter['zeilensumme']); + $bewegungen = self::bewegungen($filter); + if (in_array($filter['zeilensumme'], ['konto', 'empfaenger'], true)) { + $bewegungen = self::splitsZusammenfassen($bewegungen); + } + $gruppen = []; - foreach (self::bewegungen($filter) as $zeile) { + foreach ($bewegungen as $zeile) { $schluessel = self::zeilenschluessel($zeile, (string)$filter['zeilensumme']); $gruppen[$schluessel] ??= [ @@ -700,6 +705,10 @@ class Auswertung } )); + if (in_array($dimension, ['konto', 'empfaenger'], true)) { + $bewegungen = self::splitsZusammenfassen($bewegungen); + } + $liste = self::alsBuchungsliste($bewegungen); return [ @@ -749,13 +758,16 @@ class Auswertung 'konto_id' => (int)$zeile['konto_id'], 'empfaenger' => (string)$zeile['empfaenger'], 'text' => (string)$zeile['text'], - 'kategorie' => $zeile['kategorie_id'] === null - ? ($zeile['gegenkonto_id'] === null - ? self::OHNE_KATEGORIE - : '[' . ($kontonamen[(int)$zeile['gegenkonto_id']] ?? '?') . ']') - : ($kategorien[(int)$zeile['kategorie_id']] ?? ''), + 'kategorie' => !empty($zeile['split_zusammengefasst']) + ? '' + : ($zeile['kategorie_id'] === null + ? ($zeile['gegenkonto_id'] === null + ? self::OHNE_KATEGORIE + : '[' . ($kontonamen[(int)$zeile['gegenkonto_id']] ?? '?') . ']') + : ($kategorien[(int)$zeile['kategorie_id']] ?? '')), 'klasse' => $zeile['klasse_id'] === null ? '' : ($klassen[(int)$zeile['klasse_id']] ?? ''), 'gesplittet' => $zeile['split_id'] !== null, + 'split_gesamt' => !empty($zeile['split_zusammengefasst']), 'betrag' => round((float)$zeile['betrag'], 2), ]; } @@ -768,6 +780,48 @@ class Auswertung return $liste; } + /** + * Fasst die Splitzeilen einer Buchung zu einer Zeile mit dem Gesamtbetrag + * zusammen – wie im Kontoblatt. Das ist nur bei Konto oder Empfänger + * unbedenklich: Dort landen alle Zeilen einer Splitbuchung ohnehin in + * derselben Gruppe. Bei Kategorie oder Klasse gehören sie in + * unterschiedliche Gruppen; dort blieben sie einzeln, sonst stimmten die + * Gruppensummen nicht mehr mit dem Summenbericht überein. + * + * @param list> $bewegungen + * @return list> + */ + private static function splitsZusammenfassen(array $bewegungen): array + { + $ergebnis = []; + $index = []; + + foreach ($bewegungen as $zeile) { + if ($zeile['split_id'] === null) { + $ergebnis[] = $zeile; + continue; + } + + $buchungId = (int)$zeile['buchung_id']; + if (!isset($index[$buchungId])) { + $index[$buchungId] = count($ergebnis); + $ergebnis[] = $zeile + [ + 'kategorie_id' => null, + 'klasse_id' => null, + 'gegenkonto_id' => null, + 'text' => '', + 'split_zusammengefasst' => true, + ]; + continue; + } + + $ergebnis[$index[$buchungId]]['betrag'] = + (float)$ergebnis[$index[$buchungId]]['betrag'] + (float)$zeile['betrag']; + } + + return $ergebnis; + } + // ── Vergleichsbericht ──────────────────────────────────────────────────── /** diff --git a/src/Views/auswertungen/drilldown.php b/src/Views/auswertungen/drilldown.php index 3d52e78..2212ae2 100644 --- a/src/Views/auswertungen/drilldown.php +++ b/src/Views/auswertungen/drilldown.php @@ -58,7 +58,9 @@ include __DIR__ . '/../layout/kopf.php'; - + + Splitbuchung + Split diff --git a/src/Views/auswertungen/teil-buchungen.php b/src/Views/auswertungen/teil-buchungen.php index 6cfa086..0d4b3ef 100644 --- a/src/Views/auswertungen/teil-buchungen.php +++ b/src/Views/auswertungen/teil-buchungen.php @@ -41,7 +41,9 @@ use Finanzmanager\View; - + + Splitbuchung + Split @@ -73,6 +75,12 @@ use Finanzmanager\View;

- Eine Splitbuchung erscheint mit jeder ihrer Zeilen einzeln – so stimmt die Summe mit - dem Summenbericht überein. Der Empfänger führt in die Buchungsmaske. + + Eine Splitbuchung erscheint mit ihrem Gesamtbetrag – die Aufschlüsselung nach + Kategorie steht im Kontoblatt. + + Eine Splitbuchung erscheint mit jeder ihrer Zeilen einzeln – so stimmt die Summe mit + dem Summenbericht überein. + + Der Empfänger führt in die Buchungsmaske.

diff --git a/tests/14_AuswertungTest.php b/tests/14_AuswertungTest.php index 0c790e2..d178775 100644 --- a/tests/14_AuswertungTest.php +++ b/tests/14_AuswertungTest.php @@ -411,8 +411,9 @@ class AuswertungTest extends TestCase { $bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']); - // Fünf einfache Buchungen minus Umbuchung, plus zwei Splitzeilen. - $this->assertEquals(6, $bericht['anzahl'], 'Splitzeilen zählen einzeln, die Umbuchung gar nicht'); + // Fünf einfache Buchungen minus Umbuchung macht vier, plus die + // Splitbuchung als eine zusammengefasste Zeile. + $this->assertEquals(5, $bericht['anzahl'], 'Bei Konto/Empfänger zählt eine Splitbuchung nur einmal'); $this->assertEquals(2762.5, $bericht['summe'], 'Die Summe entspricht dem Summenbericht'); $this->assertEquals(1, count($bericht['gruppen']), 'Alles auf einem Konto, also eine Gruppe'); @@ -420,9 +421,34 @@ class AuswertungTest extends TestCase $this->assertEquals('2026-07-05', $erste['datum'], 'Chronologisch sortiert'); $this->assertEquals('400 - Auswertungsleben:A-Einkaufen', $erste['kategorie'], 'Kategorie als voller Pfad'); + $split = null; + foreach ($bericht['gruppen'][0]['buchungen'] as $buchung) { + if ($buchung['empfaenger'] === 'Rewe') { + $split = $buchung; + } + } + $this->assertEquals(-80.0, $split['betrag'], + 'Die Splitbuchung zeigt ihren Gesamtbetrag, nicht nur die erste Splitzeile'); + $this->assertEquals('', $split['kategorie'], 'Keine einzelne Kategorie bei der zusammengefassten Zeile'); + $this->assert($split['split_gesamt'], 'Als zusammengefasste Splitbuchung markiert'); + + // Bei Kategorie/Klasse bleiben die Splitzeilen einzeln – sonst + // stimmten die Gruppensummen nicht mehr mit dem Summenbericht überein. + $nachKategorie = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'kategorie']); + $splitzeilen = 0; + foreach ($nachKategorie['gruppen'] as $gruppe) { + foreach ($gruppe['buchungen'] as $buchung) { + if ($buchung['empfaenger'] === 'Rewe') { + $splitzeilen++; + $this->assert(!$buchung['split_gesamt'], 'Nach Kategorie keine Zusammenfassung'); + } + } + } + $this->assertEquals(2, $splitzeilen, 'Nach Kategorie erscheint jede Splitzeile einzeln'); + // Mit Umbuchungen: eine Buchung und 200 € mehr. $mit = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto', 'umbuchungen' => true]); - $this->assertEquals(7, $mit['anzahl'], 'Mit Schalter kommt die Umbuchung dazu'); + $this->assertEquals(6, $mit['anzahl'], 'Mit Schalter kommt die Umbuchung dazu'); $this->assertEquals(2562.5, $mit['summe'], 'Die Umbuchung verschiebt die Summe'); $umbuchung = null;