Fix n.n.: Buchungsbericht zeigt Splitbuchung nach Konto/Empfänger zusammengefasst

Bei Gruppierung nach Konto oder Empfänger landen alle Zeilen einer
Splitbuchung ohnehin in derselben Gruppe, erschienen dort aber trotzdem
einzeln mit ihrem Teilbetrag statt dem Gesamtbetrag der Buchung – wer nur
eine Zeile sah, hielt sie für den vollständigen Betrag. Bei Kategorie oder
Klasse bleibt es beim bisherigen Verhalten, weil eine Splitbuchung dort auf
mehrere Gruppen verteilt ist und die Gruppensummen sonst nicht mehr zum
Summenbericht passten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-09-27 14:16:41 +02:00
parent 4bed769810
commit c2a6a9b899
5 changed files with 119 additions and 13 deletions

View file

@ -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,

View file

@ -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
'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']] ?? ''),
: ($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<array<string,mixed>> $bewegungen
* @return list<array<string,mixed>>
*/
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 ────────────────────────────────────────────────────
/**

View file

@ -58,7 +58,9 @@ include __DIR__ . '/../layout/kopf.php';
<td><?= View::h($buchung['text']) ?></td>
<td>
<?= View::h($buchung['kategorie']) ?>
<?php if ($buchung['gesplittet']): ?>
<?php if ($buchung['split_gesamt']): ?>
<span class="marke" title="Gesamtbetrag einer Splitbuchung – Aufschlüsselung im Kontoblatt">Splitbuchung</span>
<?php elseif ($buchung['gesplittet']): ?>
<span class="marke" title="Zeile einer Splitbuchung">Split</span>
<?php endif; ?>
</td>

View file

@ -41,7 +41,9 @@ use Finanzmanager\View;
<td><?= View::h($buchung['text']) ?></td>
<td>
<?= View::h($buchung['kategorie']) ?>
<?php if ($buchung['gesplittet']): ?>
<?php if ($buchung['split_gesamt']): ?>
<span class="marke" title="Gesamtbetrag einer Splitbuchung – Aufschlüsselung im Kontoblatt">Splitbuchung</span>
<?php elseif ($buchung['gesplittet']): ?>
<span class="marke" title="Zeile einer Splitbuchung">Split</span>
<?php endif; ?>
</td>
@ -73,6 +75,12 @@ use Finanzmanager\View;
</table>
<p class="hinweis klein">
<?php if (in_array($bericht['filter']['zeilensumme'], ['konto', 'empfaenger'], true)): ?>
Eine Splitbuchung erscheint mit ihrem Gesamtbetrag – die Aufschlüsselung nach
Kategorie steht im Kontoblatt.
<?php else: ?>
Eine Splitbuchung erscheint mit jeder ihrer Zeilen einzeln – so stimmt die Summe mit
dem Summenbericht überein. Der Empfänger führt in die Buchungsmaske.
dem Summenbericht überein.
<?php endif; ?>
Der Empfänger führt in die Buchungsmaske.
</p>

View file

@ -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;