Merge pull request 'Fix n.n.: Buchungsbericht zeigt Splitbuchung nach Konto/Empfänger zusammengefasst' (#80) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/80
This commit is contained in:
commit
234bd2e8af
5 changed files with 119 additions and 13 deletions
16
Changes.md
16
Changes.md
|
|
@ -1,5 +1,21 @@
|
||||||
# Änderungen
|
# Ä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)
|
## 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,
|
Der Knopf unter Verwaltung → Regeln hieß „Gegen offene Umsätze prüfen“ und zählte nur,
|
||||||
|
|
|
||||||
|
|
@ -595,8 +595,13 @@ class Auswertung
|
||||||
{
|
{
|
||||||
$namen = self::namen((string)$filter['zeilensumme']);
|
$namen = self::namen((string)$filter['zeilensumme']);
|
||||||
|
|
||||||
|
$bewegungen = self::bewegungen($filter);
|
||||||
|
if (in_array($filter['zeilensumme'], ['konto', 'empfaenger'], true)) {
|
||||||
|
$bewegungen = self::splitsZusammenfassen($bewegungen);
|
||||||
|
}
|
||||||
|
|
||||||
$gruppen = [];
|
$gruppen = [];
|
||||||
foreach (self::bewegungen($filter) as $zeile) {
|
foreach ($bewegungen as $zeile) {
|
||||||
$schluessel = self::zeilenschluessel($zeile, (string)$filter['zeilensumme']);
|
$schluessel = self::zeilenschluessel($zeile, (string)$filter['zeilensumme']);
|
||||||
|
|
||||||
$gruppen[$schluessel] ??= [
|
$gruppen[$schluessel] ??= [
|
||||||
|
|
@ -700,6 +705,10 @@ class Auswertung
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (in_array($dimension, ['konto', 'empfaenger'], true)) {
|
||||||
|
$bewegungen = self::splitsZusammenfassen($bewegungen);
|
||||||
|
}
|
||||||
|
|
||||||
$liste = self::alsBuchungsliste($bewegungen);
|
$liste = self::alsBuchungsliste($bewegungen);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -749,13 +758,16 @@ class Auswertung
|
||||||
'konto_id' => (int)$zeile['konto_id'],
|
'konto_id' => (int)$zeile['konto_id'],
|
||||||
'empfaenger' => (string)$zeile['empfaenger'],
|
'empfaenger' => (string)$zeile['empfaenger'],
|
||||||
'text' => (string)$zeile['text'],
|
'text' => (string)$zeile['text'],
|
||||||
'kategorie' => $zeile['kategorie_id'] === null
|
'kategorie' => !empty($zeile['split_zusammengefasst'])
|
||||||
|
? ''
|
||||||
|
: ($zeile['kategorie_id'] === null
|
||||||
? ($zeile['gegenkonto_id'] === null
|
? ($zeile['gegenkonto_id'] === null
|
||||||
? self::OHNE_KATEGORIE
|
? self::OHNE_KATEGORIE
|
||||||
: '[' . ($kontonamen[(int)$zeile['gegenkonto_id']] ?? '?') . ']')
|
: '[' . ($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']] ?? ''),
|
'klasse' => $zeile['klasse_id'] === null ? '' : ($klassen[(int)$zeile['klasse_id']] ?? ''),
|
||||||
'gesplittet' => $zeile['split_id'] !== null,
|
'gesplittet' => $zeile['split_id'] !== null,
|
||||||
|
'split_gesamt' => !empty($zeile['split_zusammengefasst']),
|
||||||
'betrag' => round((float)$zeile['betrag'], 2),
|
'betrag' => round((float)$zeile['betrag'], 2),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -768,6 +780,48 @@ class Auswertung
|
||||||
return $liste;
|
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 ────────────────────────────────────────────────────
|
// ── Vergleichsbericht ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,9 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
<td><?= View::h($buchung['text']) ?></td>
|
<td><?= View::h($buchung['text']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?= View::h($buchung['kategorie']) ?>
|
<?= 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>
|
<span class="marke" title="Zeile einer Splitbuchung">Split</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ use Finanzmanager\View;
|
||||||
<td><?= View::h($buchung['text']) ?></td>
|
<td><?= View::h($buchung['text']) ?></td>
|
||||||
<td>
|
<td>
|
||||||
<?= View::h($buchung['kategorie']) ?>
|
<?= 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>
|
<span class="marke" title="Zeile einer Splitbuchung">Split</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -73,6 +75,12 @@ use Finanzmanager\View;
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p class="hinweis klein">
|
<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
|
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>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -411,8 +411,9 @@ class AuswertungTest extends TestCase
|
||||||
{
|
{
|
||||||
$bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);
|
$bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);
|
||||||
|
|
||||||
// Fünf einfache Buchungen minus Umbuchung, plus zwei Splitzeilen.
|
// Fünf einfache Buchungen minus Umbuchung macht vier, plus die
|
||||||
$this->assertEquals(6, $bericht['anzahl'], 'Splitzeilen zählen einzeln, die Umbuchung gar nicht');
|
// 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(2762.5, $bericht['summe'], 'Die Summe entspricht dem Summenbericht');
|
||||||
$this->assertEquals(1, count($bericht['gruppen']), 'Alles auf einem Konto, also eine Gruppe');
|
$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('2026-07-05', $erste['datum'], 'Chronologisch sortiert');
|
||||||
$this->assertEquals('400 - Auswertungsleben:A-Einkaufen', $erste['kategorie'], 'Kategorie als voller Pfad');
|
$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 Umbuchungen: eine Buchung und 200 € mehr.
|
||||||
$mit = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto', 'umbuchungen' => true]);
|
$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');
|
$this->assertEquals(2562.5, $mit['summe'], 'Die Umbuchung verschiebt die Summe');
|
||||||
|
|
||||||
$umbuchung = null;
|
$umbuchung = null;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue