Fix #137: Spalte und Filter „abgeglichen“ im Buchungsbericht
Der Buchungsbericht zeigt vor der Spalte „Bar“ des Bargeld-Tools die Spalte „✓“ (abgeglichen) und lässt sich nach abgeglichenen bzw. nicht abgeglichenen Buchungen filtern. „Buchungen nicht abgeglichen“ unter den offenen Vorgängen im Cockpit öffnet jetzt diesen Bericht mit dem Filter „nicht abgeglichen“ statt der Kontenübersicht. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a5989eb262
commit
fef2e67248
12 changed files with 208 additions and 15 deletions
|
|
@ -8,6 +8,10 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Buchungsbericht: Spalte „✓“ (abgeglichen) vor der Spalte „Bar“ des Bargeld-Tools und
|
||||||
|
Filter „Abgeglichen“ (alle / abgeglichen / nicht abgeglichen); „Buchungen nicht
|
||||||
|
abgeglichen“ unter den offenen Vorgängen im Cockpit öffnet den Buchungsbericht mit
|
||||||
|
diesem Filter statt der Kontenübersicht (#137)
|
||||||
- Buchungsmaske: Schalter „Ohne Bargeld-Abgleich“ für Sammelbuchungen und
|
- Buchungsmaske: Schalter „Ohne Bargeld-Abgleich“ für Sammelbuchungen und
|
||||||
Differenzausgleiche; solche Buchungen werden nicht mit dem Bargeld-Tool abgeglichen und
|
Differenzausgleiche; solche Buchungen werden nicht mit dem Bargeld-Tool abgeglichen und
|
||||||
erscheinen nicht mehr unter den offenen Vorgängen im Cockpit (#136)
|
erscheinen nicht mehr unter den offenen Vorgängen im Cockpit (#136)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
# Änderungen
|
# Änderungen
|
||||||
|
|
||||||
|
## Buchungsbericht: Spalte und Filter „abgeglichen“ (12.10.2026)
|
||||||
|
|
||||||
|
- Anlass (#137): Der Buchungsbericht zeigte nicht, ob eine Buchung abgeglichen ist, und ließ sich nicht danach filtern.
|
||||||
|
- Neue Spalte „✓“ (Tooltip „abgeglichen“) vor der Spalte „Bar“ des Bargeld-Tools – wie im Kontoblatt. Splitzeilen erben den Haken ihrer Buchung.
|
||||||
|
- Neuer Filter „Abgeglichen“ (alle / abgeglichen / nicht abgeglichen), Filterfeld `abgeglichen` mit den Werten `ja`/`nein`; wie der Bargeld-Filter wirkt er nur im Buchungsbericht und wird mit gespeicherten Berichten gesichert.
|
||||||
|
- Cockpit, *Offene Vorgänge*: „Buchungen nicht abgeglichen“ führt jetzt in den Buchungsbericht mit dem Filter „nicht abgeglichen“ statt in die Kontenübersicht (`Cockpit::nichtAbgeglichenBericht()`). Zeitraum von der ersten betroffenen Buchung bis heute, Umbuchungen eingeschlossen; liegen nicht abgeglichene Buchungen auf aufgelösten Konten, beschränkt sich der Bericht auf die aktiven Konten – so zeigt er genau die gezählten Buchungen.
|
||||||
|
- Tests: `tests/14_AuswertungTest.php`, `tests/15_CockpitTest.php`, `tests/e2e/specs/auswertungen.spec.js`.
|
||||||
|
|
||||||
## Buchen ohne Bargeld-Abgleich (12.10.2026)
|
## Buchen ohne Bargeld-Abgleich (12.10.2026)
|
||||||
|
|
||||||
- Anlass (#136): Sammelbuchungen und Differenzausgleiche tragen eine Kategorie, die gegen das Bargeld-Tool geprüft wird, stehen dort aber absichtlich nicht – und wurden deshalb immer wieder als offener Vorgang angemahnt.
|
- Anlass (#136): Sammelbuchungen und Differenzausgleiche tragen eine Kategorie, die gegen das Bargeld-Tool geprüft wird, stehen dort aber absichtlich nicht – und wurden deshalb immer wieder als offener Vorgang angemahnt.
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ Zwei Ausnahmen führen das Vorzeichen mit, weil sie keine Abschnitte haben: der
|
||||||
| Typ | Zeilen | Spalten |
|
| Typ | Zeilen | Spalten |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Summenbericht** | Zeilen-Summe, nach Einnahmen und Ausgaben getrennt | Zeitraum, je nach Spalten-Aufteilung |
|
| **Summenbericht** | Zeilen-Summe, nach Einnahmen und Ausgaben getrennt | Zeitraum, je nach Spalten-Aufteilung |
|
||||||
| **Buchungsbericht** | Einzelbuchungen, gruppiert nach Zeilen-Summe; bei eingerichtetem Bargeld-Tool mit Spalte „Bar“ und Filter „Bargeld-Tool“ | feste Buchungsspalten |
|
| **Buchungsbericht** | Einzelbuchungen, gruppiert nach Zeilen-Summe; Spalte „✓“ (abgeglichen) mit Filter „Abgeglichen“ (#137); bei eingerichtetem Bargeld-Tool mit Spalte „Bar“ und Filter „Bargeld-Tool“ | feste Buchungsspalten |
|
||||||
| **Vergleichsbericht** | wie Summenbericht | Zeitraum, Vorjahreszeitraum, Differenz, Veränderung |
|
| **Vergleichsbericht** | wie Summenbericht | Zeitraum, Vorjahreszeitraum, Differenz, Veränderung |
|
||||||
| **Kontostandsbericht** | Konten | Salden zu den Enden der Spaltenzeiträume |
|
| **Kontostandsbericht** | Konten | Salden zu den Enden der Spaltenzeiträume |
|
||||||
| **Budgetbericht** | Kategoriebaum | Budget, Ist, Saldo |
|
| **Budgetbericht** | Kategoriebaum | Budget, Ist, Saldo |
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,9 @@ Prüfungen, die gerade nichts melden und daher nicht in der Liste stehen:
|
||||||
gesplittet sind. Umbuchungen brauchen keine Kategorie und zählen deshalb nicht mit,
|
gesplittet sind. Umbuchungen brauchen keine Kategorie und zählen deshalb nicht mit,
|
||||||
ebenso wenig Buchungen, die bewusst ohne Kategorie gebucht sind (siehe `docs/buchungen.md`).
|
ebenso wenig Buchungen, die bewusst ohne Kategorie gebucht sind (siehe `docs/buchungen.md`).
|
||||||
* **nicht abgeglichen** – Buchungen aktiver Konten bis heute ohne Abgleich-Kennzeichen.
|
* **nicht abgeglichen** – Buchungen aktiver Konten bis heute ohne Abgleich-Kennzeichen.
|
||||||
|
Führt in den Buchungsbericht mit dem Filter „Abgeglichen: nicht abgeglichen“, von der
|
||||||
|
ersten betroffenen Buchung bis heute und mit Umbuchungen; liegen auch auf aufgelösten
|
||||||
|
Konten solche Buchungen, ist der Bericht auf die aktiven Konten eingeschränkt (#137).
|
||||||
* **Bargeld-Tool-Prüfung fehlgeschlagen** – Buchungen mit `bargeld_status = 'fehler'`, also
|
* **Bargeld-Tool-Prüfung fehlgeschlagen** – Buchungen mit `bargeld_status = 'fehler'`, also
|
||||||
einem gescheiterten Abruf beim Bargeld-Tool (siehe `docs/buchungen.md`), nicht zu
|
einem gescheiterten Abruf beim Bargeld-Tool (siehe `docs/buchungen.md`), nicht zu
|
||||||
verwechseln mit „im Bargeld-Tool nicht gefunden“. Führt in den Buchungsbericht über den
|
verwechseln mit „im Bargeld-Tool nicht gefunden“. Führt in den Buchungsbericht über den
|
||||||
|
|
|
||||||
|
|
@ -1412,17 +1412,21 @@ table.kostentabelle select { max-width: 16rem; width: 100%; }
|
||||||
table.kostentabelle th[scope="row"] { font-weight: 400; text-align: left; }
|
table.kostentabelle th[scope="row"] { font-weight: 400; text-align: left; }
|
||||||
table.kostentabelle .gruppenzeile th { padding-top: .7rem; font-weight: 600; }
|
table.kostentabelle .gruppenzeile th { padding-top: .7rem; font-weight: 600; }
|
||||||
|
|
||||||
/* Die Abgleichspalte zwischen Klasse und Betrag: so schmal wie ihr Zeichen.
|
/* Die Abgleichspalten zwischen Klasse und Betrag: so schmal wie ihr Zeichen.
|
||||||
Sie erscheint nur, wenn ein Bargeld-Tool eingerichtet ist. */
|
„✓“ (abgeglichen) steht immer davor, „Bar“ erscheint nur, wenn ein
|
||||||
|
Bargeld-Tool eingerichtet ist. */
|
||||||
table.liste th.bargeldspalte,
|
table.liste th.bargeldspalte,
|
||||||
table.liste td.bargeldspalte {
|
table.liste td.bargeldspalte,
|
||||||
|
table.liste th.abgleichspalte,
|
||||||
|
table.liste td.abgleichspalte {
|
||||||
width: 1.8rem;
|
width: 1.8rem;
|
||||||
padding-left: .2rem;
|
padding-left: .2rem;
|
||||||
padding-right: .2rem;
|
padding-right: .2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.liste th.bargeldspalte {
|
table.liste th.bargeldspalte,
|
||||||
|
table.liste th.abgleichspalte {
|
||||||
font-size: .78rem;
|
font-size: .78rem;
|
||||||
color: var(--text-gedaempft);
|
color: var(--text-gedaempft);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ class Auswertung
|
||||||
/** Zulässige Werte des Bargeld-Filters; 'keiner' = nicht abgeglichen. */
|
/** Zulässige Werte des Bargeld-Filters; 'keiner' = nicht abgeglichen. */
|
||||||
public const BARGELD_FILTER = ['gefunden', 'fehlt', 'fehler', 'keiner'];
|
public const BARGELD_FILTER = ['gefunden', 'fehlt', 'fehler', 'keiner'];
|
||||||
|
|
||||||
|
/** Zulässige Werte des Abgleich-Filters im Buchungsbericht (#137). */
|
||||||
|
public const ABGEGLICHEN_FILTER = ['ja', 'nein'];
|
||||||
|
|
||||||
/** Wonach die Zeilen zusammengefasst werden. */
|
/** Wonach die Zeilen zusammengefasst werden. */
|
||||||
public const ZEILENSUMMEN = [
|
public const ZEILENSUMMEN = [
|
||||||
'kategorie' => 'Kategorie',
|
'kategorie' => 'Kategorie',
|
||||||
|
|
@ -127,6 +130,10 @@ class Auswertung
|
||||||
'bargeld' => $typ === 'buchungsbericht' && in_array($roh['bargeld'] ?? '', self::BARGELD_FILTER, true)
|
'bargeld' => $typ === 'buchungsbericht' && in_array($roh['bargeld'] ?? '', self::BARGELD_FILTER, true)
|
||||||
? (string)$roh['bargeld']
|
? (string)$roh['bargeld']
|
||||||
: '',
|
: '',
|
||||||
|
// Ebenso nur dort: der Haken „abgeglichen“ der Buchung (#137).
|
||||||
|
'abgeglichen' => $typ === 'buchungsbericht' && in_array($roh['abgeglichen'] ?? '', self::ABGEGLICHEN_FILTER, true)
|
||||||
|
? (string)$roh['abgeglichen']
|
||||||
|
: '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -842,6 +849,7 @@ class Auswertung
|
||||||
'bargeld_status' => $zeile['bargeld_status'] ?? null,
|
'bargeld_status' => $zeile['bargeld_status'] ?? null,
|
||||||
'bargeld_hinweis' => $zeile['bargeld_hinweis'] ?? null,
|
'bargeld_hinweis' => $zeile['bargeld_hinweis'] ?? null,
|
||||||
'ohne_bargeld_abgleich' => !empty($zeile['ohne_bargeld_abgleich']),
|
'ohne_bargeld_abgleich' => !empty($zeile['ohne_bargeld_abgleich']),
|
||||||
|
'abgeglichen' => !empty($zeile['abgeglichen']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1195,14 +1203,14 @@ class Auswertung
|
||||||
SELECT b.id AS buchung_id, NULL AS split_id, b.datum, b.betrag, b.konto_id,
|
SELECT b.id AS buchung_id, NULL AS split_id, b.datum, b.betrag, b.konto_id,
|
||||||
b.kategorie_id, b.klasse_id, b.gegenkonto_id, b.empfaenger,
|
b.kategorie_id, b.klasse_id, b.gegenkonto_id, b.empfaenger,
|
||||||
b.buchungstext AS text, b.bargeld_status, b.bargeld_hinweis,
|
b.buchungstext AS text, b.bargeld_status, b.bargeld_hinweis,
|
||||||
b.ohne_bargeld_abgleich
|
b.ohne_bargeld_abgleich, b.abgeglichen
|
||||||
FROM buchungen b
|
FROM buchungen b
|
||||||
WHERE b.benutzer_id = ? AND b.gesplittet = 0 AND b.datum BETWEEN ? AND ?
|
WHERE b.benutzer_id = ? AND b.gesplittet = 0 AND b.datum BETWEEN ? AND ?
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT b.id AS buchung_id, s.id AS split_id, b.datum, s.betrag, b.konto_id,
|
SELECT b.id AS buchung_id, s.id AS split_id, b.datum, s.betrag, b.konto_id,
|
||||||
s.kategorie_id, s.klasse_id, s.gegenkonto_id, b.empfaenger,
|
s.kategorie_id, s.klasse_id, s.gegenkonto_id, b.empfaenger,
|
||||||
s.notiz AS text, b.bargeld_status, b.bargeld_hinweis,
|
s.notiz AS text, b.bargeld_status, b.bargeld_hinweis,
|
||||||
b.ohne_bargeld_abgleich
|
b.ohne_bargeld_abgleich, b.abgeglichen
|
||||||
FROM buchung_splits s
|
FROM buchung_splits s
|
||||||
JOIN buchungen b ON b.id = s.buchung_id
|
JOIN buchungen b ON b.id = s.buchung_id
|
||||||
WHERE b.benutzer_id = ? AND b.gesplittet = 1 AND b.datum BETWEEN ? AND ?
|
WHERE b.benutzer_id = ? AND b.gesplittet = 1 AND b.datum BETWEEN ? AND ?
|
||||||
|
|
@ -1258,6 +1266,11 @@ class Auswertung
|
||||||
$params[] = $bargeld;
|
$params[] = $bargeld;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$abgeglichen = (string)($filter['abgeglichen'] ?? '');
|
||||||
|
if ($abgeglichen !== '') {
|
||||||
|
$wo[] = $abgeglichen === 'ja' ? 'abgeglichen = 1' : 'abgeglichen = 0';
|
||||||
|
}
|
||||||
|
|
||||||
if (($filter['suche'] ?? '') !== '') {
|
if (($filter['suche'] ?? '') !== '') {
|
||||||
$wo[] = '(empfaenger LIKE ? OR text LIKE ?)';
|
$wo[] = '(empfaenger LIKE ? OR text LIKE ?)';
|
||||||
$muster = '%' . $filter['suche'] . '%';
|
$muster = '%' . $filter['suche'] . '%';
|
||||||
|
|
|
||||||
|
|
@ -261,19 +261,19 @@ class Cockpit
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$nichtAbgeglichen = (int)Database::fetchValue(
|
$stichtag = $heute ?? date('Y-m-d');
|
||||||
'SELECT COUNT(*) FROM buchungen b
|
$nichtAbgeglichen = Database::fetchOne(
|
||||||
|
'SELECT COUNT(*) AS anzahl, MIN(b.datum) AS erste FROM buchungen b
|
||||||
JOIN konten k ON k.id = b.konto_id
|
JOIN konten k ON k.id = b.konto_id
|
||||||
WHERE b.benutzer_id = ? AND b.abgeglichen = 0 AND k.aktiv = 1 AND b.datum <= ?',
|
WHERE b.benutzer_id = ? AND b.abgeglichen = 0 AND k.aktiv = 1 AND b.datum <= ?',
|
||||||
[$benutzer, $heute ?? date('Y-m-d')],
|
[$benutzer, $stichtag]
|
||||||
0
|
|
||||||
);
|
);
|
||||||
if ($nichtAbgeglichen > 0) {
|
if ((int)($nichtAbgeglichen['anzahl'] ?? 0) > 0) {
|
||||||
$vorgaenge[] = [
|
$vorgaenge[] = [
|
||||||
'schluessel' => 'nicht_abgeglichen',
|
'schluessel' => 'nicht_abgeglichen',
|
||||||
'text' => 'nicht abgeglichen',
|
'text' => 'nicht abgeglichen',
|
||||||
'anzahl' => $nichtAbgeglichen,
|
'anzahl' => (int)$nichtAbgeglichen['anzahl'],
|
||||||
'pfad' => '/konten',
|
'pfad' => self::nichtAbgeglichenBericht((string)$nichtAbgeglichen['erste'], $stichtag),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,4 +387,39 @@ class Cockpit
|
||||||
'angepasst' => '1',
|
'angepasst' => '1',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Buchungsbericht, gefiltert auf nicht abgeglichene Buchungen (#137) –
|
||||||
|
* mit denselben Grenzen wie der offene Vorgang: von der ersten betroffenen
|
||||||
|
* Buchung bis heute, Umbuchungen eingeschlossen. Liegen auch auf
|
||||||
|
* aufgelösten Konten nicht abgeglichene Buchungen, beschränkt sich der
|
||||||
|
* Bericht auf die aktiven Konten, denn nur die zählt der Vorgang.
|
||||||
|
*/
|
||||||
|
private static function nichtAbgeglichenBericht(string $erste, string $stichtag): string
|
||||||
|
{
|
||||||
|
$parameter = [
|
||||||
|
'typ' => 'buchungsbericht',
|
||||||
|
'zeitraum' => 'benutzerdefiniert',
|
||||||
|
'von' => $erste,
|
||||||
|
'bis' => $stichtag,
|
||||||
|
'umbuchungen' => '1',
|
||||||
|
'abgeglichen' => 'nein',
|
||||||
|
];
|
||||||
|
|
||||||
|
$inaktiv = (int)Database::fetchValue(
|
||||||
|
'SELECT COUNT(*) FROM buchungen b
|
||||||
|
JOIN konten k ON k.id = b.konto_id
|
||||||
|
WHERE b.benutzer_id = ? AND b.abgeglichen = 0 AND k.aktiv = 0 AND b.datum BETWEEN ? AND ?',
|
||||||
|
[Mandant::id(), $erste, $stichtag],
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if ($inaktiv > 0) {
|
||||||
|
$parameter['konten'] = array_map('intval', array_column(Database::fetchAll(
|
||||||
|
'SELECT id FROM konten WHERE benutzer_id = ? AND aktiv = 1 ORDER BY id',
|
||||||
|
[Mandant::id()]
|
||||||
|
), 'id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/auswertungen/bericht?' . http_build_query($parameter + ['angepasst' => '1']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,18 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
</label>
|
</label>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($bericht['typ'] === 'buchungsbericht'): ?>
|
||||||
|
<label>Abgeglichen
|
||||||
|
<select name="abgeglichen">
|
||||||
|
<?php foreach (['' => 'alle', 'ja' => 'abgeglichen', 'nein' => 'nicht abgeglichen'] as $wert => $text): ?>
|
||||||
|
<option value="<?= View::h($wert) ?>" <?= $filter['abgeglichen'] === $wert ? 'selected' : '' ?>>
|
||||||
|
<?= View::h($text) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($bericht['typ'] === 'buchungsbericht' && ($bericht['bargeld'] ?? false)): ?>
|
<?php if ($bericht['typ'] === 'buchungsbericht' && ($bericht['bargeld'] ?? false)): ?>
|
||||||
<label>Bargeld-Tool
|
<label>Bargeld-Tool
|
||||||
<select name="bargeld">
|
<select name="bargeld">
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use Finanzmanager\View;
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php $spalten = $bericht['bargeld'] ? 8 : 7; ?>
|
<?php $spalten = $bericht['bargeld'] ? 9 : 8; ?>
|
||||||
<table class="liste bericht buchungsgruppen">
|
<table class="liste bericht buchungsgruppen">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -20,6 +20,7 @@ use Finanzmanager\View;
|
||||||
<th>Buchungstext</th>
|
<th>Buchungstext</th>
|
||||||
<th>Kategorie</th>
|
<th>Kategorie</th>
|
||||||
<th>Klasse</th>
|
<th>Klasse</th>
|
||||||
|
<th class="abgleichspalte" title="abgeglichen">✓</th>
|
||||||
<?php if ($bericht['bargeld']): ?>
|
<?php if ($bericht['bargeld']): ?>
|
||||||
<th class="bargeldspalte" title="Abgleich mit dem Bargeld-Tool">Bar</th>
|
<th class="bargeldspalte" title="Abgleich mit dem Bargeld-Tool">Bar</th>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
@ -52,6 +53,7 @@ use Finanzmanager\View;
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td><?= View::h($buchung['klasse']) ?></td>
|
<td><?= View::h($buchung['klasse']) ?></td>
|
||||||
|
<td class="abgleichspalte nebentext"><?= $buchung['abgeglichen'] ? '✓' : '' ?></td>
|
||||||
<?php if ($bericht['bargeld']): ?>
|
<?php if ($bericht['bargeld']): ?>
|
||||||
<td class="bargeldspalte"><?= View::bargeldMarke($buchung['bargeld_status'], $buchung['bargeld_hinweis'], !empty($buchung['ohne_bargeld_abgleich'])) ?></td>
|
<td class="bargeldspalte"><?= View::bargeldMarke($buchung['bargeld_status'], $buchung['bargeld_hinweis'], !empty($buchung['ohne_bargeld_abgleich'])) ?></td>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
@ -89,6 +91,9 @@ use Finanzmanager\View;
|
||||||
dem Summenbericht überein.
|
dem Summenbericht überein.
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
Der Empfänger führt in die Buchungsmaske.
|
Der Empfänger führt in die Buchungsmaske.
|
||||||
|
<?php if ($bericht['filter']['abgeglichen'] === 'nein'): ?>
|
||||||
|
Den Haken „abgeglichen“ setzt die Buchungsmaske oder der Abgleich im Kontoblatt.
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (in_array($bericht['filter']['bargeld'], ['fehlt', 'fehler'], true)): ?>
|
<?php if (in_array($bericht['filter']['bargeld'], ['fehlt', 'fehler'], true)): ?>
|
||||||
<?php if ($bericht['filter']['bargeld'] === 'fehlt'): ?>
|
<?php if ($bericht['filter']['bargeld'] === 'fehlt'): ?>
|
||||||
Zu diesen Buchungen wurde im Bargeld-Tool keine Ausgabe mit demselben Betrag gefunden.
|
Zu diesen Buchungen wurde im Bargeld-Tool keine Ausgabe mit demselben Betrag gefunden.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ class AuswertungTest extends TestCase
|
||||||
|
|
||||||
$this->buchungsbericht();
|
$this->buchungsbericht();
|
||||||
$this->buchungsberichtBargeld();
|
$this->buchungsberichtBargeld();
|
||||||
|
$this->buchungsberichtAbgeglichen();
|
||||||
$this->vergleichsbericht();
|
$this->vergleichsbericht();
|
||||||
$this->kontostandsbericht();
|
$this->kontostandsbericht();
|
||||||
$this->budgetbericht();
|
$this->budgetbericht();
|
||||||
|
|
@ -505,6 +506,42 @@ class AuswertungTest extends TestCase
|
||||||
Einstellung::set('bargeld_url', (string)$vorher);
|
Einstellung::set('bargeld_url', (string)$vorher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Spalte und Filter „abgeglichen“ im Buchungsbericht (#137). */
|
||||||
|
private function buchungsberichtAbgeglichen(): void
|
||||||
|
{
|
||||||
|
Database::execute(
|
||||||
|
"UPDATE buchungen SET abgeglichen = CASE WHEN empfaenger IN ('Edeka', 'Rewe') THEN 1 ELSE 0 END
|
||||||
|
WHERE konto_id = ?",
|
||||||
|
[$this->giro]
|
||||||
|
);
|
||||||
|
|
||||||
|
$alle = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);
|
||||||
|
$markiert = [];
|
||||||
|
foreach ($alle['gruppen'][0]['buchungen'] as $buchung) {
|
||||||
|
$markiert[$buchung['empfaenger']] = $buchung['abgeglichen'];
|
||||||
|
}
|
||||||
|
$this->assert($markiert['Edeka'] && $markiert['Rewe'], 'Abgeglichene Buchungen tragen den Haken');
|
||||||
|
$this->assert(!$markiert['Kino'], 'Nicht abgeglichene Buchungen tragen keinen');
|
||||||
|
|
||||||
|
$ja = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto', 'abgeglichen' => 'ja']);
|
||||||
|
$this->assertEquals(2, $ja['anzahl'], 'Der Filter „abgeglichen“ lässt Edeka und die Splitbuchung übrig');
|
||||||
|
|
||||||
|
$nein = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto', 'abgeglichen' => 'nein']);
|
||||||
|
$this->assertEquals($alle['anzahl'] - 2, $nein['anzahl'], 'Der Filter „nicht abgeglichen“ lässt beide weg');
|
||||||
|
|
||||||
|
// Splitzeilen erben den Haken ihrer Buchung.
|
||||||
|
$nachKategorie = $this->bericht(['typ' => 'buchungsbericht', 'abgeglichen' => 'ja']);
|
||||||
|
$this->assertEquals(3, $nachKategorie['anzahl'], 'Nach Kategorie: Edeka und beide Splitzeilen');
|
||||||
|
|
||||||
|
$unbekannt = $this->bericht(['typ' => 'buchungsbericht', 'abgeglichen' => 'vielleicht']);
|
||||||
|
$this->assertEquals('', $unbekannt['filter']['abgeglichen'], 'Ein unbekannter Wert filtert nicht');
|
||||||
|
|
||||||
|
$summe = $this->bericht(['typ' => 'summenbericht', 'abgeglichen' => 'nein']);
|
||||||
|
$this->assertEquals('', $summe['filter']['abgeglichen'], 'Außerhalb des Buchungsberichts wirkt der Filter nicht');
|
||||||
|
|
||||||
|
Database::execute('UPDATE buchungen SET abgeglichen = 0 WHERE konto_id = ?', [$this->giro]);
|
||||||
|
}
|
||||||
|
|
||||||
private function buchungsbericht(): void
|
private function buchungsbericht(): void
|
||||||
{
|
{
|
||||||
$bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);
|
$bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class CockpitTest extends TestCase
|
||||||
$this->verteilungKuerztAufDieGrenze();
|
$this->verteilungKuerztAufDieGrenze();
|
||||||
$this->offeneVorgaengeZaehlenWasWartet();
|
$this->offeneVorgaengeZaehlenWasWartet();
|
||||||
$this->bargeldFehlerErscheintAlsOffenerVorgang();
|
$this->bargeldFehlerErscheintAlsOffenerVorgang();
|
||||||
|
$this->nichtAbgeglichenFuehrtInDenBuchungsbericht();
|
||||||
$this->legendenFuehrenZuDenBuchungen();
|
$this->legendenFuehrenZuDenBuchungen();
|
||||||
$this->vorgangspruefungenKennenJedenVorgang();
|
$this->vorgangspruefungenKennenJedenVorgang();
|
||||||
}
|
}
|
||||||
|
|
@ -325,6 +326,56 @@ class CockpitTest extends TestCase
|
||||||
Database::execute("UPDATE buchungen SET bargeld_status = 'fehler' WHERE id = ?", [$id]);
|
Database::execute("UPDATE buchungen SET bargeld_status = 'fehler' WHERE id = ?", [$id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* „Buchungen nicht abgeglichen“ öffnet den Buchungsbericht, gefiltert auf
|
||||||
|
* nicht abgeglichene Buchungen – und der zeigt genau die gezählten (#137).
|
||||||
|
*/
|
||||||
|
private function nichtAbgeglichenFuehrtInDenBuchungsbericht(): void
|
||||||
|
{
|
||||||
|
$pruefen = function (string $fall): void {
|
||||||
|
$vorgang = null;
|
||||||
|
foreach (Cockpit::offeneVorgaenge(self::HEUTE) as $eintrag) {
|
||||||
|
if ($eintrag['schluessel'] === 'nicht_abgeglichen') {
|
||||||
|
$vorgang = $eintrag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertNotEmpty($vorgang, $fall . ': Der Vorgang erscheint');
|
||||||
|
|
||||||
|
$this->assert(str_starts_with($vorgang['pfad'], '/auswertungen/bericht?'), $fall . ': Der Weg führt in den Buchungsbericht');
|
||||||
|
parse_str((string)parse_url($vorgang['pfad'], PHP_URL_QUERY), $filter);
|
||||||
|
$this->assertEquals('buchungsbericht', $filter['typ'] ?? '', $fall . ': Typ Buchungsbericht');
|
||||||
|
$this->assertEquals('nein', $filter['abgeglichen'] ?? '', $fall . ': Gefiltert auf nicht abgeglichen');
|
||||||
|
|
||||||
|
$bericht = Auswertung::bericht($filter, self::HEUTE);
|
||||||
|
$ids = [];
|
||||||
|
foreach ($bericht['gruppen'] as $gruppe) {
|
||||||
|
foreach ($gruppe['buchungen'] as $buchung) {
|
||||||
|
$this->assert(!$buchung['abgeglichen'], $fall . ': Nur nicht abgeglichene Buchungen');
|
||||||
|
$ids[$buchung['buchung_id']] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertEquals($vorgang['anzahl'], count($ids), $fall . ': Der Bericht zeigt genau die gezählten Buchungen');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Eine künftige Buchung zählt der Vorgang nicht – der Bericht auch nicht.
|
||||||
|
Buchung::anlegen([
|
||||||
|
'konto_id' => $this->giro, 'datum' => '2026-12-24', 'betrag' => -40,
|
||||||
|
'empfaenger' => 'Zukunft', 'kategorie_id' => $this->freizeit,
|
||||||
|
]);
|
||||||
|
$pruefen('Aktive Konten');
|
||||||
|
|
||||||
|
// Ein aufgelöstes Konto zählt nicht mit – der Bericht beschränkt sich
|
||||||
|
// dann auf die aktiven Konten.
|
||||||
|
$alt = Konto::anlegen([
|
||||||
|
'name' => 'Cockpitaltkonto', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 0,
|
||||||
|
]);
|
||||||
|
Buchung::anlegen([
|
||||||
|
'konto_id' => $alt, 'datum' => '2026-08-01', 'betrag' => -5,
|
||||||
|
'empfaenger' => 'Altlast', 'kategorie_id' => $this->freizeit,
|
||||||
|
]);
|
||||||
|
$pruefen('Mit aufgelöstem Konto');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jeder Legendeneintrag führt zu einer Buchungsliste, deren Summe genau
|
* Jeder Legendeneintrag führt zu einer Buchungsliste, deren Summe genau
|
||||||
* seinen Wert trifft – auch „Nicht kategorisiert“ und das Sammelsegment.
|
* seinen Wert trifft – auch „Nicht kategorisiert“ und das Sammelsegment.
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,27 @@ test.describe('Auswertungen', () => {
|
||||||
await expect(page.locator('thead th', { hasText: 'Saldo' })).toHaveCount(1)
|
await expect(page.locator('thead th', { hasText: 'Saldo' })).toHaveCount(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Buchungsbericht führt die Spalte „abgeglichen“ und filtert danach (#137)', async ({ page }) => {
|
||||||
|
await vorlage(page, 'buchungsbericht')
|
||||||
|
await expect(page.locator('table.bericht thead th.abgleichspalte')).toHaveCount(1)
|
||||||
|
|
||||||
|
await page.selectOption('select[name="abgeglichen"]', 'nein')
|
||||||
|
await page.click('button:has-text("Anzeigen")')
|
||||||
|
await expect(page).toHaveURL(/abgeglichen=nein/)
|
||||||
|
await expect(page.locator('select[name="abgeglichen"]')).toHaveValue('nein')
|
||||||
|
await expect(page.locator('table.bericht tbody td.abgleichspalte', { hasText: '✓' })).toHaveCount(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Cockpit: „nicht abgeglichen“ öffnet den gefilterten Buchungsbericht (#137)', async ({ page }) => {
|
||||||
|
await page.goto('/')
|
||||||
|
const eintrag = page.locator('.vorgangsliste a', { hasText: 'nicht abgeglichen' })
|
||||||
|
test.skip(await eintrag.count() === 0, 'Keine nicht abgeglichenen Buchungen auf dieser Instanz')
|
||||||
|
|
||||||
|
await eintrag.first().click()
|
||||||
|
await expect(page).toHaveURL(/typ=buchungsbericht/)
|
||||||
|
await expect(page.locator('select[name="abgeglichen"]')).toHaveValue('nein')
|
||||||
|
})
|
||||||
|
|
||||||
test('Spalten-Aufteilung macht aus einer Spalte zwölf Monate', async ({ page }) => {
|
test('Spalten-Aufteilung macht aus einer Spalte zwölf Monate', async ({ page }) => {
|
||||||
await vorlage(page, 'summenbericht')
|
await vorlage(page, 'summenbericht')
|
||||||
await expect(page.locator('table.bericht thead th')).toHaveCount(2)
|
await expect(page.locator('table.bericht thead th')).toHaveCount(2)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue