Merge pull request 'Fix n.n.: Buchungsbericht – Bargeld-Abgleich, Gruppen-/Gesamtzeile, Filterbereich, Cockpit-Tooltip' (#110) from fds2610-bot/Finanzmanager:main into main

Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/110
This commit is contained in:
fds2610 2026-10-01 11:18:43 +02:00
commit ed6e707c55
15 changed files with 224 additions and 26 deletions

View file

@ -1,5 +1,30 @@
# Änderungen # Änderungen
## Buchungsbericht: Gruppen- und Gesamtzeile (01.10.2026)
- Die Formatierung von Kategoriezeile und Gesamtzeile ist getauscht. Die Gesamtzeile zeigt nur noch „Gesamt“; die Anzahl der Buchungen steht im Tooltip („Anzahl n“).
## Buchungsbericht: Bargeld-Tool-Abgleich (01.10.2026)
- Ist ein Bargeld-Tool eingerichtet, zeigt der Buchungsbericht die Spalte „Bar“ (dasselbe Symbol wie im Kontoblatt) und bietet den Filter „Bargeld-Tool“: alle · gefunden · nicht gefunden · Abruf gescheitert · nicht abgeglichen.
- Der Filter wird mit dem Bericht gespeichert (`Auswertung::BARGELD_FILTER`) und wirkt nur im Buchungsbericht.
- Test: `14_AuswertungTest.php`.
## Berichte: Filterbereich (01.10.2026)
- Der Filterbereich erhält den hellblauen Bereichsrand samt Schatten.
- „Zeilen-Summe“ und „Spalten-Aufteilung“ stehen in der ersten Zeile hinter „Bericht anzeigen als“; das Suchfeld ist dafür breiter.
## Cockpit: Tooltip der Kachel „Offene Vorgänge“ (01.10.2026)
- Beim Überfahren der Überschrift nennt der Tooltip die Prüfungen, die gerade nichts melden und deshalb nicht in der Liste stehen (`Cockpit::VORGANGSPRUEFUNGEN`).
- Test: `15_CockpitTest.php`.
## Cockpit: Offene Vorgänge für nicht gefundene Bargeld-Buchungen (01.10.2026)
- Neuer offener Vorgang „im Bargeld-Tool nicht gefunden“ (Status `fehlt`) neben der fehlgeschlagenen Prüfung; die Liste unter `/verwaltung/bargeld/fehler?status=fehlt`.
- Test: `15_CockpitTest.php`.
## Rahmen, Schatten und Tabs im Design (01.10.2026) ## Rahmen, Schatten und Tabs im Design (01.10.2026)
- Tabellen (`table.liste`), Kacheln und breite Formulare (`.formular.breit`, u. a. Einstellungen) erhalten in beiden Themen einen 1px breiten hellblauen Rand (`--bereichsrand`) und einen leichten Schatten (`--bereichsschatten`) für mehr Kontrast zwischen den Bereichen. - Tabellen (`table.liste`), Kacheln und breite Formulare (`.formular.breit`, u. a. Einstellungen) erhalten in beiden Themen einen 1px breiten hellblauen Rand (`--bereichsrand`) und einen leichten Schatten (`--bereichsschatten`) für mehr Kontrast zwischen den Bereichen.

View file

@ -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 | feste Buchungsspalten | | **Buchungsbericht** | Einzelbuchungen, gruppiert nach Zeilen-Summe; 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 |

View file

@ -274,8 +274,9 @@ grauen Kreis.
Buchungen mit dem Status `fehler` zählen im Cockpit als offener Vorgang „Bargeld-Tool-Prüfung Buchungen mit dem Status `fehler` zählen im Cockpit als offener Vorgang „Bargeld-Tool-Prüfung
fehlgeschlagen“ (siehe `docs/cockpit.md`) – ein Klick führt auf fehlgeschlagen“ (siehe `docs/cockpit.md`) – ein Klick führt auf
`/verwaltung/bargeld/fehler`, die kontoübergreifende Liste dieser Buchungen `/verwaltung/bargeld/fehler`, die kontoübergreifende Liste dieser Buchungen
(`Buchung::mitBargeldFehler()`). Der Status `fehlt` erscheint dort bewusst nicht: Er ist ein (`Buchung::mitBargeldFehler()`). Buchungen mit dem Status `fehlt` zählen als eigener offener
Ergebnis, kein offenes Problem – eine erneute Zuordnung fragt einfach wieder ohne Erfolg. Vorgang „im Bargeld-Tool nicht gefunden“ und führen auf dieselbe Seite mit `?status=fehlt`
(`Buchung::mitBargeldFehler('fehlt')`).
**Ein Fehlschlag ist ein Ergebnis, kein Abbruch.** Weder ein abgeschaltetes Bargeld-Tool **Ein Fehlschlag ist ein Ergebnis, kein Abbruch.** Weder ein abgeschaltetes Bargeld-Tool
noch ein Netzfehler darf verhindern, dass die Kategorie gespeichert wird; der Abruf bricht noch ein Netzfehler darf verhindern, dass die Kategorie gespeichert wird; der Abruf bricht

View file

@ -172,7 +172,8 @@ zusammen und es entstünde keine Fläche – deshalb zwei Halbringe derselben Fa
## Offene Vorgänge ## Offene Vorgänge
Was auf Bearbeitung wartet, mit Zahl und Weg dorthin: Was auf Bearbeitung wartet, mit Zahl und Weg dorthin. Der Tooltip der Überschrift nennt die
Prüfungen, die gerade nichts melden und daher nicht in der Liste stehen:
* **ohne Kategorie** – Buchungen ohne Kategorie, die auch keine Umbuchung und nicht * **ohne Kategorie** – Buchungen ohne Kategorie, die auch keine Umbuchung und nicht
gesplittet sind. Umbuchungen brauchen keine Kategorie und zählen deshalb nicht mit. gesplittet sind. Umbuchungen brauchen keine Kategorie und zählen deshalb nicht mit.
@ -182,6 +183,8 @@ Was auf Bearbeitung wartet, mit Zahl und Weg dorthin:
verwechseln mit „im Bargeld-Tool nicht gefunden“. Führt auf verwechseln mit „im Bargeld-Tool nicht gefunden“. Führt auf
`/verwaltung/bargeld/fehler`, die Liste dieser Buchungen kontoübergreifend `/verwaltung/bargeld/fehler`, die Liste dieser Buchungen kontoübergreifend
(`Buchung::mitBargeldFehler()`), denn die Prüfung hängt an der Kategorie, nicht am Konto. (`Buchung::mitBargeldFehler()`), denn die Prüfung hängt an der Kategorie, nicht am Konto.
* **Im Bargeld-Tool nicht gefunden** – Buchungen mit `bargeld_status = 'fehlt'`. Führt auf
`/verwaltung/bargeld/fehler?status=fehlt` (`Buchung::mitBargeldFehler('fehlt')`).
* **Monatsabschluss steht aus** – der Vorschlag aus `Monatsabschluss::naechsterOffener()`, * **Monatsabschluss steht aus** – der Vorschlag aus `Monatsabschluss::naechsterOffener()`,
aber nur, wenn dieser Monat wirklich noch offen ist und Buchungen trägt. aber nur, wenn dieser Monat wirklich noch offen ist und Buchungen trägt.

View file

@ -826,6 +826,28 @@ table.liste tr.gruppensumme td {
background: color-mix(in srgb, var(--flaeche-gedaempft) 55%, var(--flaeche)); background: color-mix(in srgb, var(--flaeche-gedaempft) 55%, var(--flaeche));
} }
/* Buchungsbericht: Kategoriezeile fett wie sonst die Summe, Gesamtzeile gedämpft wie sonst der Kopf. */
table.buchungsgruppen tr.gruppenzeile th {
background: color-mix(in srgb, var(--flaeche-gedaempft) 55%, var(--flaeche));
font-size: inherit;
letter-spacing: normal;
text-transform: none;
font-weight: 600;
color: inherit;
}
table.buchungsgruppen tr.gruppensumme td {
background: transparent;
border-top: 1px solid var(--rand);
font-size: .85rem;
letter-spacing: .03em;
text-transform: uppercase;
color: var(--text-gedaempft);
font-weight: 400;
}
table.buchungsgruppen tr.gruppensumme td span { cursor: help; }
table.liste tfoot tr.summenzeile td { table.liste tfoot tr.summenzeile td {
font-weight: 700; font-weight: 700;
border-top: 2px solid var(--rand-kraeftig); border-top: 2px solid var(--rand-kraeftig);
@ -1852,6 +1874,13 @@ body.schlicht {
gap: .6rem; gap: .6rem;
} }
.berichtsfilter {
border: var(--bereichsrand);
box-shadow: var(--bereichsschatten);
}
.berichtsfilter input[type="search"] { min-width: 24rem; }
.filterzeile { .filterzeile {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View file

@ -286,9 +286,14 @@ class VerwaltungController extends BaseController
{ {
$this->verlangeAnmeldung(); $this->verlangeAnmeldung();
$fehlt = ($_GET['status'] ?? '') === 'fehlt';
$this->render('verwaltung/bargeld_fehler.php', [ $this->render('verwaltung/bargeld_fehler.php', [
'titel' => 'Bargeld-Tool: fehlgeschlagene Prüfungen', 'titel' => $fehlt
'buchungen' => Buchung::mitBargeldFehler(), ? 'Bargeld-Tool: nicht gefundene Buchungen'
: 'Bargeld-Tool: fehlgeschlagene Prüfungen',
'fehlt' => $fehlt,
'buchungen' => Buchung::mitBargeldFehler($fehlt ? 'fehlt' : 'fehler'),
]); ]);
} }
} }

View file

@ -504,23 +504,23 @@ class Buchung
/** /**
* Alle Buchungen, an denen die Bargeld-Tool-Prüfung zuletzt einen Fehler * Alle Buchungen, an denen die Bargeld-Tool-Prüfung zuletzt einen Fehler
* festgestellt hat – kontoübergreifend, denn die Prüfung hängt an der * ('fehler') oder keinen Treffer ('fehlt') ergeben hat – kontoübergreifend, denn die Prüfung hängt an der
* Kategorie der Buchung, nicht an ihrem Konto. Der Status 'fehler' steht * Kategorie der Buchung, nicht an ihrem Konto. Der Status 'fehler' steht
* für einen gescheiterten Abruf (siehe Services\BargeldAbgleich::FEHLER), * für einen gescheiterten Abruf (siehe Services\BargeldAbgleich::FEHLER),
* nicht für eine im Bargeld-Tool fehlende Buchung. * 'fehlt' für eine im Bargeld-Tool nicht gefundene Buchung.
* *
* @return list<array<string,mixed>> * @return list<array<string,mixed>>
*/ */
public static function mitBargeldFehler(): array public static function mitBargeldFehler(string $status = 'fehler'): array
{ {
return Database::fetchAll( return Database::fetchAll(
'SELECT b.id, b.datum, b.betrag, b.empfaenger, b.buchungstext, 'SELECT b.id, b.datum, b.betrag, b.empfaenger, b.buchungstext,
b.bargeld_hinweis, b.bargeld_geprueft_am, b.konto_id, k.name AS konto_name b.bargeld_hinweis, b.bargeld_geprueft_am, b.konto_id, k.name AS konto_name
FROM buchungen b 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.bargeld_status = \'fehler\' WHERE b.benutzer_id = ? AND b.bargeld_status = ?
ORDER BY b.datum DESC, b.id DESC', ORDER BY b.datum DESC, b.id DESC',
[Mandant::id()] [Mandant::id(), $status === 'fehlt' ? 'fehlt' : 'fehler']
); );
} }

View file

@ -53,6 +53,9 @@ class Auswertung
'budgetbericht' => 'Budgetbericht', 'budgetbericht' => 'Budgetbericht',
]; ];
/** Zulässige Werte des Bargeld-Filters; 'keiner' = nicht abgeglichen. */
public const BARGELD_FILTER = ['gefunden', 'fehlt', 'fehler', 'keiner'];
/** Wonach die Zeilen zusammengefasst werden. */ /** Wonach die Zeilen zusammengefasst werden. */
public const ZEILENSUMMEN = [ public const ZEILENSUMMEN = [
'kategorie' => 'Kategorie', 'kategorie' => 'Kategorie',
@ -114,6 +117,10 @@ class Auswertung
'kategorien' => self::ids($roh['kategorien'] ?? []), 'kategorien' => self::ids($roh['kategorien'] ?? []),
'klassen' => self::ids($roh['klassen'] ?? []), 'klassen' => self::ids($roh['klassen'] ?? []),
'suche' => mb_substr(trim((string)($roh['suche'] ?? '')), 0, 120), 'suche' => mb_substr(trim((string)($roh['suche'] ?? '')), 0, 120),
// Nur der Buchungsbericht führt die Abgleichspalte und filtert danach.
'bargeld' => $typ === 'buchungsbericht' && in_array($roh['bargeld'] ?? '', self::BARGELD_FILTER, true)
? (string)$roh['bargeld']
: '',
]; ];
} }
@ -654,6 +661,7 @@ class Auswertung
return [ return [
'darstellung' => 'buchungen', 'darstellung' => 'buchungen',
'bargeld' => BargeldAbgleich::eingerichtet(),
'gruppen' => array_values($gruppen), 'gruppen' => array_values($gruppen),
'summe' => round($summe, 2), 'summe' => round($summe, 2),
'anzahl' => $anzahl, 'anzahl' => $anzahl,
@ -825,6 +833,8 @@ class Auswertung
'gesplittet' => $zeile['split_id'] !== null, 'gesplittet' => $zeile['split_id'] !== null,
'split_gesamt' => !empty($zeile['split_zusammengefasst']), 'split_gesamt' => !empty($zeile['split_zusammengefasst']),
'betrag' => round((float)$zeile['betrag'], 2), 'betrag' => round((float)$zeile['betrag'], 2),
'bargeld_status' => $zeile['bargeld_status'] ?? null,
'bargeld_hinweis' => $zeile['bargeld_hinweis'] ?? null,
]; ];
} }
@ -1177,13 +1187,13 @@ class Auswertung
$sql = 'SELECT * FROM ( $sql = 'SELECT * FROM (
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.buchungstext AS text, b.bargeld_status, b.bargeld_hinweis
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 s.notiz AS text, b.bargeld_status, b.bargeld_hinweis
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 ?
@ -1213,6 +1223,14 @@ class Auswertung
$params = array_merge($params, $filter['klassen']); $params = array_merge($params, $filter['klassen']);
} }
$bargeld = (string)($filter['bargeld'] ?? '');
if ($bargeld === 'keiner') {
$wo[] = 'bargeld_status IS NULL';
} elseif ($bargeld !== '') {
$wo[] = 'bargeld_status = ?';
$params[] = $bargeld;
}
if (($filter['suche'] ?? '') !== '') { if (($filter['suche'] ?? '') !== '') {
$wo[] = '(empfaenger LIKE ? OR text LIKE ?)'; $wo[] = '(empfaenger LIKE ? OR text LIKE ?)';
$muster = '%' . $filter['suche'] . '%'; $muster = '%' . $filter['suche'] . '%';

View file

@ -49,6 +49,22 @@ class Cockpit
], ],
]; ];
/**
* Alle Prüfungen der Kachel „Offene Vorgänge“, in der Reihenfolge der Liste.
* Angezeigt wird nur, was etwas findet; der Tooltip der Überschrift nennt
* deshalb auch die, die gerade nichts melden.
*/
public const VORGANGSPRUEFUNGEN = [
'ohne_kategorie' => 'Buchungen ohne Kategorie',
'nicht_abgeglichen' => 'Nicht abgeglichene Buchungen',
'bankumsaetze' => 'Bankumsätze zum Abgleich',
'bargeld_fehler' => 'Fehlgeschlagene Bargeld-Tool-Prüfungen',
'bargeld_fehlt' => 'Im Bargeld-Tool nicht gefundene Buchungen',
'termine' => 'Fällige regelmäßige Buchungen',
'erinnerungen' => 'Fällige Erinnerungen',
'monatsabschluss' => 'Offener Monatsabschluss',
];
/** /**
* Die sichtbaren Kacheln in ihrer Reihenfolge. Ohne gespeicherte Auswahl * Die sichtbaren Kacheln in ihrer Reihenfolge. Ohne gespeicherte Auswahl
* sind alle sichtbar – ein leeres Cockpit beim ersten Aufruf wäre nur * sind alle sichtbar – ein leeres Cockpit beim ersten Aufruf wäre nur
@ -288,6 +304,22 @@ class Cockpit
]; ];
} }
// Buchungen, die im Bargeld-Tool nicht gefunden wurden: der Abgleich
// lief, hat aber keinen Treffer ergeben.
$bargeldFehlt = (int)Database::fetchValue(
'SELECT COUNT(*) FROM buchungen WHERE benutzer_id = ? AND bargeld_status = ?',
[$benutzer, BargeldAbgleich::FEHLT],
0
);
if ($bargeldFehlt > 0) {
$vorgaenge[] = [
'schluessel' => 'bargeld_fehlt',
'text' => 'im Bargeld-Tool nicht gefunden',
'anzahl' => $bargeldFehlt,
'pfad' => '/verwaltung/bargeld/fehler?status=fehlt',
];
}
// Fällige regelmäßige Buchungen – die automatischen zählen mit, denn // Fällige regelmäßige Buchungen – die automatischen zählen mit, denn
// gebucht sind sie erst nach dem Fälligkeitslauf. // gebucht sind sie erst nach dem Fälligkeitslauf.
$termine = Faelligkeitslauf::vorschau($heute); $termine = Faelligkeitslauf::vorschau($heute);

View file

@ -127,9 +127,7 @@ include __DIR__ . '/../layout/kopf.php';
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</label> </label>
</div>
<div class="filterzeile">
<label>Zeilen-Summe <label>Zeilen-Summe
<select name="zeilensumme" <?= $zeilenWaehlbar ? '' : 'disabled' ?>> <select name="zeilensumme" <?= $zeilenWaehlbar ? '' : 'disabled' ?>>
<?php foreach ($zeilensummen as $wert => $bezeichnung): ?> <?php foreach ($zeilensummen as $wert => $bezeichnung): ?>
@ -151,7 +149,9 @@ include __DIR__ . '/../layout/kopf.php';
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</label> </label>
</div>
<div class="filterzeile">
<?php /* Vor jedem Ankreuzfeld ein verstecktes Feld gleichen Namens mit <?php /* Vor jedem Ankreuzfeld ein verstecktes Feld gleichen Namens mit
„0“: Ein nicht angehaktes Feld sendet gar nichts, und der Wert „0“: Ein nicht angehaktes Feld sendet gar nichts, und der Wert
fiele sonst auf seine Vorgabe zurück statt auf „aus“. Bei fiele sonst auf seine Vorgabe zurück statt auf „aus“. Bei
@ -174,6 +174,19 @@ include __DIR__ . '/../layout/kopf.php';
</label> </label>
<?php endif; ?> <?php endif; ?>
<?php if ($bericht['typ'] === 'buchungsbericht' && ($bericht['bargeld'] ?? false)): ?>
<label>Bargeld-Tool
<select name="bargeld">
<?php foreach (['' => 'alle', 'gefunden' => 'gefunden', 'fehlt' => 'nicht gefunden',
'fehler' => 'Abruf gescheitert', 'keiner' => 'nicht abgeglichen'] as $wert => $text): ?>
<option value="<?= View::h($wert) ?>" <?= $filter['bargeld'] === $wert ? 'selected' : '' ?>>
<?= View::h($text) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<?php endif; ?>
<label>Suche <label>Suche
<input type="search" name="suche" value="<?= View::h($filter['suche']) ?>" <input type="search" name="suche" value="<?= View::h($filter['suche']) ?>"
placeholder="Empfänger oder Text"> placeholder="Empfänger oder Text">

View file

@ -10,7 +10,8 @@ use Finanzmanager\View;
*/ */
?> ?>
<table class="liste bericht"> <?php $spalten = $bericht['bargeld'] ? 8 : 7; ?>
<table class="liste bericht buchungsgruppen">
<thead> <thead>
<tr> <tr>
<th>Datum</th> <th>Datum</th>
@ -19,6 +20,9 @@ use Finanzmanager\View;
<th>Buchungstext</th> <th>Buchungstext</th>
<th>Kategorie</th> <th>Kategorie</th>
<th>Klasse</th> <th>Klasse</th>
<?php if ($bericht['bargeld']): ?>
<th class="bargeldspalte" title="Abgleich mit dem Bargeld-Tool">Bar</th>
<?php endif; ?>
<th class="rechts">Betrag</th> <th class="rechts">Betrag</th>
</tr> </tr>
</thead> </thead>
@ -26,7 +30,7 @@ use Finanzmanager\View;
<?php foreach ($bericht['gruppen'] as $gruppe): ?> <?php foreach ($bericht['gruppen'] as $gruppe): ?>
<tbody> <tbody>
<tr class="gruppenzeile"> <tr class="gruppenzeile">
<th colspan="7"><?= View::h($gruppe['name']) ?></th> <th colspan="<?= $spalten ?>"><?= View::h($gruppe['name']) ?></th>
</tr> </tr>
<?php foreach ($gruppe['buchungen'] as $buchung): ?> <?php foreach ($gruppe['buchungen'] as $buchung): ?>
@ -48,6 +52,9 @@ use Finanzmanager\View;
<?php endif; ?> <?php endif; ?>
</td> </td>
<td><?= View::h($buchung['klasse']) ?></td> <td><?= View::h($buchung['klasse']) ?></td>
<?php if ($bericht['bargeld']): ?>
<td class="bargeldspalte"><?= View::bargeldMarke($buchung['bargeld_status'], $buchung['bargeld_hinweis']) ?></td>
<?php endif; ?>
<td class="rechts <?= View::betragKlasse($buchung['betrag']) ?>"> <td class="rechts <?= View::betragKlasse($buchung['betrag']) ?>">
<?= View::betrag($buchung['betrag'], false) ?> <?= View::betrag($buchung['betrag'], false) ?>
</td> </td>
@ -55,8 +62,7 @@ use Finanzmanager\View;
<?php endforeach; ?> <?php endforeach; ?>
<tr class="gruppensumme"> <tr class="gruppensumme">
<td colspan="6">Gesamt <?= View::h($gruppe['name']) ?> <td colspan="<?= $spalten - 1 ?>"><span title="Anzahl <?= count($gruppe['buchungen']) ?>">Gesamt</span></td>
(<?= count($gruppe['buchungen']) ?>)</td>
<td class="rechts <?= View::betragKlasse($gruppe['summe']) ?>"> <td class="rechts <?= View::betragKlasse($gruppe['summe']) ?>">
<?= View::betrag($gruppe['summe'], false) ?> <?= View::betrag($gruppe['summe'], false) ?>
</td> </td>
@ -66,7 +72,7 @@ use Finanzmanager\View;
<tfoot> <tfoot>
<tr class="summenzeile"> <tr class="summenzeile">
<td colspan="6">Gesamt (<?= (int)$bericht['anzahl'] ?> Buchungen)</td> <td colspan="<?= $spalten - 1 ?>">Gesamt (<?= (int)$bericht['anzahl'] ?> Buchungen)</td>
<td class="rechts <?= View::betragKlasse($bericht['summe']) ?>"> <td class="rechts <?= View::betragKlasse($bericht['summe']) ?>">
<?= View::betrag($bericht['summe'], false) ?> <?= View::betrag($bericht['summe'], false) ?>
</td> </td>

View file

@ -153,7 +153,19 @@ include __DIR__ . '/../layout/kopf.php';
<?php elseif ($kachel === 'offene_vorgaenge'): ?> <?php elseif ($kachel === 'offene_vorgaenge'): ?>
<section class="kachel"> <section class="kachel">
<header><h2>Offene Vorgänge</h2></header> <?php
$gefunden = array_column($vorgaenge ?? [], 'schluessel');
$nichtAngezeigt = [];
foreach (Cockpit::VORGANGSPRUEFUNGEN as $schluessel => $name) {
if (!in_array($schluessel, $gefunden, true)) {
$nichtAngezeigt[] = '• ' . $name;
}
}
$tooltip = $nichtAngezeigt === []
? 'Alle Prüfungen melden gerade etwas.'
: "Nicht angezeigt, weil nichts offen ist:\n" . implode("\n", $nichtAngezeigt);
?>
<header><h2 title="<?= View::h($tooltip) ?>">Offene Vorgänge</h2></header>
<?php if (($vorgaenge ?? []) === []): ?> <?php if (($vorgaenge ?? []) === []): ?>
<p class="platzhalter"> <p class="platzhalter">
Nichts offen: Alle Buchungen sind kategorisiert und abgeglichen, Nichts offen: Alle Buchungen sind kategorisiert und abgeglichen,

View file

@ -6,6 +6,7 @@ use Finanzmanager\View;
* Alle Buchungen, an denen die Bargeld-Tool-Prüfung zuletzt einen Fehler * Alle Buchungen, an denen die Bargeld-Tool-Prüfung zuletzt einen Fehler
* festgestellt hat – der Ziel der Kachel „Offene Vorgänge“ im Cockpit. * festgestellt hat – der Ziel der Kachel „Offene Vorgänge“ im Cockpit.
* *
* @var bool $fehlt true: im Bargeld-Tool nicht gefunden, sonst Abruf gescheitert
* @var list<array<string,mixed>> $buchungen * @var list<array<string,mixed>> $buchungen
*/ */
@ -18,14 +19,20 @@ include __DIR__ . '/../layout/kopf.php';
</div> </div>
<p class="feldhinweis"> <p class="feldhinweis">
<?php if ($fehlt): ?>
Zu diesen Buchungen wurde im Bargeld-Tool keine Ausgabe mit demselben Betrag
gefunden. Eine erneute Zuordnung der Kategorie in der Buchungsmaske oder im
Kontoblatt stößt die Prüfung erneut an.
<?php else: ?>
Bei diesen Buchungen ist der letzte Abruf beim Bargeld-Tool gescheitert – ein Bei diesen Buchungen ist der letzte Abruf beim Bargeld-Tool gescheitert – ein
Abrufproblem, keine Aussage darüber, ob die Buchung dort steht. Eine erneute Abrufproblem, keine Aussage darüber, ob die Buchung dort steht. Eine erneute
Zuordnung der Kategorie in der Buchungsmaske oder im Kontoblatt stößt die Zuordnung der Kategorie in der Buchungsmaske oder im Kontoblatt stößt die
Prüfung erneut an. Prüfung erneut an.
<?php endif; ?>
</p> </p>
<?php if ($buchungen === []): ?> <?php if ($buchungen === []): ?>
<p class="platzhalter">Keine offenen Fehler.</p> <p class="platzhalter"><?= $fehlt ? 'Keine nicht gefundenen Buchungen.' : 'Keine offenen Fehler.' ?></p>
<?php else: ?> <?php else: ?>
<table class="liste"> <table class="liste">
<thead> <thead>

View file

@ -2,12 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
use Finanzmanager\Database;
use Finanzmanager\Models\Bericht; use Finanzmanager\Models\Bericht;
use Finanzmanager\Models\Berichtsgruppe; use Finanzmanager\Models\Berichtsgruppe;
use Finanzmanager\Models\Buchung; use Finanzmanager\Models\Buchung;
use Finanzmanager\Models\Budget; use Finanzmanager\Models\Budget;
use Finanzmanager\Models\Kategorie; use Finanzmanager\Models\Kategorie;
use Finanzmanager\Models\Klasse; use Finanzmanager\Models\Klasse;
use Finanzmanager\Models\Einstellung;
use Finanzmanager\Models\Konto; use Finanzmanager\Models\Konto;
use Finanzmanager\Services\Auswertung; use Finanzmanager\Services\Auswertung;
use Finanzmanager\Services\BerichtExport; use Finanzmanager\Services\BerichtExport;
@ -50,6 +52,7 @@ class AuswertungTest extends TestCase
$this->summenbericht_einschraenkungen(); $this->summenbericht_einschraenkungen();
$this->buchungsbericht(); $this->buchungsbericht();
$this->buchungsberichtBargeld();
$this->vergleichsbericht(); $this->vergleichsbericht();
$this->kontostandsbericht(); $this->kontostandsbericht();
$this->budgetbericht(); $this->budgetbericht();
@ -478,6 +481,30 @@ class AuswertungTest extends TestCase
// ── Weitere Berichtstypen ──────────────────────────────────────────────── // ── Weitere Berichtstypen ────────────────────────────────────────────────
private function buchungsberichtBargeld(): void
{
$vorher = Einstellung::get('bargeld_url', '');
Einstellung::set('bargeld_url', '');
$this->assert(!$this->bericht(['typ' => 'buchungsbericht'])['bargeld'], 'Ohne Bargeld-Tool keine Abgleichspalte');
Einstellung::set('bargeld_url', 'http://bargeld.invalid');
Database::execute("UPDATE buchungen SET bargeld_status = 'fehlt' WHERE empfaenger = 'Rewe'");
$alle = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);
$this->assert($alle['bargeld'], 'Mit Bargeld-Tool erscheint die Abgleichspalte');
$fehlt = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto', 'bargeld' => 'fehlt']);
$this->assertEquals(1, $fehlt['anzahl'], 'Der Filter „nicht gefunden“ lässt nur die markierte Buchung übrig');
$keiner = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto', 'bargeld' => 'keiner']);
$this->assertEquals($alle['anzahl'] - 1, $keiner['anzahl'], 'Der Filter „nicht abgeglichen“ lässt die markierte Buchung weg');
$summe = $this->bericht(['typ' => 'summenbericht', 'bargeld' => 'fehlt']);
$this->assertEquals('', $summe['filter']['bargeld'], 'Außerhalb des Buchungsberichts wirkt der Filter nicht');
Einstellung::set('bargeld_url', (string)$vorher);
}
private function buchungsbericht(): void private function buchungsbericht(): void
{ {
$bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']); $bericht = $this->bericht(['typ' => 'buchungsbericht', 'zeilensumme' => 'konto']);

View file

@ -38,6 +38,18 @@ class CockpitTest extends TestCase
$this->offeneVorgaengeZaehlenWasWartet(); $this->offeneVorgaengeZaehlenWasWartet();
$this->bargeldFehlerErscheintAlsOffenerVorgang(); $this->bargeldFehlerErscheintAlsOffenerVorgang();
$this->legendenFuehrenZuDenBuchungen(); $this->legendenFuehrenZuDenBuchungen();
$this->vorgangspruefungenKennenJedenVorgang();
}
private function vorgangspruefungenKennenJedenVorgang(): void
{
foreach (Cockpit::offeneVorgaenge(self::HEUTE) as $vorgang) {
$this->assert(
isset(Cockpit::VORGANGSPRUEFUNGEN[$vorgang['schluessel']]),
'Der Tooltip kennt die Prüfung „' . $vorgang['schluessel'] . '“'
);
}
$this->assertEquals(8, count(Cockpit::VORGANGSPRUEFUNGEN), 'Acht Prüfungen sind aufgelistet');
} }
private function stammdaten(): void private function stammdaten(): void
@ -309,6 +321,14 @@ class CockpitTest extends TestCase
$ohne = $schluessel(Cockpit::offeneVorgaenge(self::HEUTE)); $ohne = $schluessel(Cockpit::offeneVorgaenge(self::HEUTE));
$this->assert(!isset($ohne['bargeld_fehler']), 'Ein gefundener Treffer ist kein offener Vorgang mehr'); $this->assert(!isset($ohne['bargeld_fehler']), 'Ein gefundener Treffer ist kein offener Vorgang mehr');
// Nicht gefunden: eigener Vorgang mit eigener Liste.
Database::execute("UPDATE buchungen SET bargeld_status = 'fehlt' WHERE id = ?", [$id]);
$fehlt = $schluessel(Cockpit::offeneVorgaenge(self::HEUTE));
$this->assertEquals(1, $fehlt['bargeld_fehlt'] ?? 0, 'Eine im Bargeld-Tool nicht gefundene Buchung ist ein offener Vorgang');
$this->assert(!isset($fehlt['bargeld_fehler']), 'Nicht gefunden zählt nicht als gescheiterter Abruf');
$this->assertEquals(1, count(Buchung::mitBargeldFehler('fehlt')), 'Die Liste „nicht gefunden“ enthält die Buchung');
$this->assertEquals(0, count(Buchung::mitBargeldFehler()), 'Die Fehlerliste enthält sie nicht');
Database::execute("UPDATE buchungen SET bargeld_status = 'fehler' WHERE id = ?", [$id]); Database::execute("UPDATE buchungen SET bargeld_status = 'fehler' WHERE id = ?", [$id]);
} }