Neuer Schalter „Ohne Bargeld-Abgleich“ in der Buchungsmaske für Sammelbuchungen und Differenzausgleiche. Solche Buchungen werden nicht gegen das Bargeld-Tool geprüft, ein früherer Status wird gelöscht, und sie erscheinen nicht mehr unter den offenen Vorgängen im Cockpit. Kontoblatt und Buchungsbericht zeigen einen leeren grauen Kreis. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
194 lines
8.5 KiB
PHP
194 lines
8.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Finanzmanager\Database;
|
||
use Finanzmanager\Models\Buchung;
|
||
use Finanzmanager\Models\Einstellung;
|
||
use Finanzmanager\Models\Kategorie;
|
||
use Finanzmanager\Models\Konto;
|
||
use Finanzmanager\Services\Auswertung;
|
||
use Finanzmanager\Services\BargeldAbgleich;
|
||
use Finanzmanager\Services\Cockpit;
|
||
use Finanzmanager\View;
|
||
|
||
/**
|
||
* Bewusst ohne Abgleich mit dem Bargeld-Tool buchen (#136).
|
||
*
|
||
* Sammelbuchungen und Differenzausgleiche tragen eine Kategorie, die gegen
|
||
* das Bargeld-Tool geprüft wird, stehen dort aber absichtlich nicht. Geprüft
|
||
* wird, dass der Schalter gespeichert wird, den Abgleich unterbindet und die
|
||
* Buchung damit aus den offenen Vorgängen im Cockpit nimmt – auch dann, wenn
|
||
* sie vorher schon als „nicht gefunden“ angemahnt war.
|
||
*/
|
||
class OhneBargeldAbgleichTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 65: Ohne Bargeld-Abgleich';
|
||
|
||
private int $konto = 0;
|
||
private int $kategorie = 0;
|
||
|
||
public function run(): void
|
||
{
|
||
$this->mandantAnlegen();
|
||
$this->konto = Konto::anlegen([
|
||
'name' => 'Haushaltsgiro', 'kontoart' => 'bank', 'anfangssaldo' => 500, 'aktiv' => 1,
|
||
]);
|
||
$this->kategorie = Kategorie::anlegen(['name' => 'Haushalt bar', 'art' => 'ausgabe']);
|
||
Kategorie::bargeldAuswahlSetzen([$this->kategorie]);
|
||
|
||
// Eine Adresse, unter der niemand antwortet: Jeder echte Abruf endet
|
||
// als Fehler – ein Abruf, der gar nicht erst stattfindet, nicht.
|
||
Einstellung::set('bargeld_url', 'http://127.0.0.1:9', 'bargeld');
|
||
Einstellung::cacheLeeren();
|
||
|
||
$this->schalterWirdGespeichert();
|
||
$this->keinAbgleichUndKeinOffenerVorgang();
|
||
$this->nachtraeglichSetzenRaeumtDenStatusWeg();
|
||
$this->schnellzuordnungBehaeltDenSchalter();
|
||
$this->anzeigeImKontoblattUndBericht();
|
||
$this->maskeZeigtDenSchalter();
|
||
}
|
||
|
||
/** @param array<string,mixed> $zusatz */
|
||
private function buchung(string $datum, array $zusatz = []): int
|
||
{
|
||
return Buchung::anlegen($zusatz + [
|
||
'konto_id' => $this->konto,
|
||
'datum' => $datum,
|
||
'betrag' => -45.10,
|
||
'empfaenger' => 'Sammelbuchung Bargeld',
|
||
'kategorie_id' => $this->kategorie,
|
||
]);
|
||
}
|
||
|
||
/** @return array<string,int> */
|
||
private function vorgaenge(): array
|
||
{
|
||
return array_column(Cockpit::offeneVorgaenge('2026-09-30'), 'anzahl', 'schluessel');
|
||
}
|
||
|
||
private function schalterWirdGespeichert(): void
|
||
{
|
||
$id = $this->buchung('2026-09-01', ['ohne_bargeld_abgleich' => true]);
|
||
$this->assertEquals(1, (int)Buchung::find($id)['ohne_bargeld_abgleich'], 'Der Schalter wird gespeichert');
|
||
|
||
$normal = $this->buchung('2026-09-02');
|
||
$this->assertEquals(0, (int)Buchung::find($normal)['ohne_bargeld_abgleich'], 'Ohne Schalter bleibt er aus');
|
||
}
|
||
|
||
private function keinAbgleichUndKeinOffenerVorgang(): void
|
||
{
|
||
$vorher = $this->vorgaenge()['bargeld_fehler'] ?? 0;
|
||
|
||
$ohne = $this->buchung('2026-09-05', ['ohne_bargeld_abgleich' => true]);
|
||
$this->assertNull(BargeldAbgleich::pruefe($ohne), 'Eine Buchung ohne Bargeld-Abgleich wird nicht geprüft');
|
||
$this->assertNull(Buchung::find($ohne)['bargeld_status'], 'Sie trägt keinen Abgleichstatus');
|
||
$this->assertEquals($vorher, $this->vorgaenge()['bargeld_fehler'] ?? 0, 'Sie erscheint nicht unter den offenen Vorgängen');
|
||
|
||
// Gegenprobe: Dieselbe Buchung ohne Schalter wird geprüft und angemahnt.
|
||
$mit = $this->buchung('2026-09-06');
|
||
$this->assertEquals(BargeldAbgleich::FEHLER, BargeldAbgleich::pruefe($mit), 'Ohne Schalter wird abgeglichen');
|
||
$this->assertEquals($vorher + 1, $this->vorgaenge()['bargeld_fehler'] ?? 0, 'Und als offener Vorgang angemahnt');
|
||
}
|
||
|
||
private function nachtraeglichSetzenRaeumtDenStatusWeg(): void
|
||
{
|
||
$id = $this->buchung('2026-09-10');
|
||
Database::execute("UPDATE buchungen SET bargeld_status = 'fehlt', bargeld_hinweis = 'Kein Treffer.' WHERE id = ?", [$id]);
|
||
$vorher = $this->vorgaenge()['bargeld_fehlt'] ?? 0;
|
||
$this->assertGreaterThan(0, $vorher, 'Die nicht gefundene Buchung wird angemahnt');
|
||
|
||
$buchung = Buchung::find($id);
|
||
Buchung::speichern($id, ['ohne_bargeld_abgleich' => 1] + $buchung);
|
||
BargeldAbgleich::pruefe($id);
|
||
|
||
$frisch = Buchung::find($id);
|
||
$this->assertEquals(1, (int)$frisch['ohne_bargeld_abgleich'], 'Der Schalter lässt sich nachträglich setzen');
|
||
$this->assertNull($frisch['bargeld_status'], 'Der alte Abgleichstatus verschwindet');
|
||
$this->assertEquals($vorher - 1, $this->vorgaenge()['bargeld_fehlt'] ?? 0, 'Die Buchung wird nicht mehr angemahnt');
|
||
|
||
Buchung::speichern($id, ['ohne_bargeld_abgleich' => 0] + $frisch);
|
||
$this->assertEquals(0, (int)Buchung::find($id)['ohne_bargeld_abgleich'], 'Abgeschaltet gilt der Abgleich wieder');
|
||
}
|
||
|
||
private function schnellzuordnungBehaeltDenSchalter(): void
|
||
{
|
||
$id = $this->buchung('2026-09-12', ['ohne_bargeld_abgleich' => true, 'kategorie_id' => null]);
|
||
Buchung::zuordnen($id, $this->kategorie, null);
|
||
$this->assertNull(BargeldAbgleich::pruefe($id), 'Auch nach der Schnellzuordnung wird nicht abgeglichen');
|
||
$this->assertEquals(
|
||
1,
|
||
(int)Buchung::find($id)['ohne_bargeld_abgleich'],
|
||
'Die Schnellzuordnung lässt den Schalter stehen'
|
||
);
|
||
}
|
||
|
||
private function anzeigeImKontoblattUndBericht(): void
|
||
{
|
||
$marke = View::bargeldMarke(null, null, true);
|
||
$this->assert(str_contains($marke, 'Bewusst ohne Abgleich mit dem Bargeld-Tool'), 'Die Marke nennt den Grund');
|
||
$this->assert(str_contains($marke, '○'), 'Die Marke ist ein leerer Kreis');
|
||
$this->assert(str_contains(View::bargeldMarke(null, null), '●'), 'Ohne Schalter bleibt der volle Kreis');
|
||
|
||
$zeilen = Buchung::kontoblatt($this->konto, ['von' => '2026-09-01', 'bis' => '2026-09-01']);
|
||
$this->assertEquals(1, (int)($zeilen[0]['ohne_bargeld_abgleich'] ?? 0), 'Das Kontoblatt liefert den Schalter mit');
|
||
|
||
$bericht = Auswertung::bericht(Auswertung::normalisiere([
|
||
'typ' => 'buchungsbericht', 'zeitraum' => 'alles',
|
||
]), '2026-09-30');
|
||
$buchungen = array_merge(...array_column($bericht['gruppen'], 'buchungen'));
|
||
$treffer = array_values(array_filter(
|
||
$buchungen,
|
||
static fn(array $b): bool => $b['datum'] === '2026-09-01' && $b['ohne_bargeld_abgleich']
|
||
));
|
||
$this->assertEquals(1, count($treffer), 'Der Buchungsbericht liefert den Schalter mit');
|
||
}
|
||
|
||
private function maskeZeigtDenSchalter(): void
|
||
{
|
||
$mit = $this->maske(true, ['ohne_bargeld_abgleich' => 1]);
|
||
$this->assert(
|
||
(bool)preg_match('#name="ohne_bargeld_abgleich" value="1"\s*checked#', $mit),
|
||
'Mit Bargeld-Tool steht der Schalter gesetzt in der Maske'
|
||
);
|
||
$this->assert(str_contains($mit, 'Ohne Bargeld-Abgleich'), 'Er ist beschriftet');
|
||
|
||
$ohne = $this->maske(false, ['ohne_bargeld_abgleich' => 1]);
|
||
$this->assert(!str_contains($ohne, 'Ohne Bargeld-Abgleich'), 'Ohne Bargeld-Tool ist der Schalter unsichtbar');
|
||
$this->assert(
|
||
str_contains($ohne, '<input type="hidden" name="ohne_bargeld_abgleich" value="1">'),
|
||
'Ein gesetzter Wert geht trotzdem nicht verloren'
|
||
);
|
||
|
||
$leer = $this->maske(false, []);
|
||
$this->assert(!str_contains($leer, 'ohne_bargeld_abgleich'), 'Ohne Bargeld-Tool und ohne Wert steht nichts in der Maske');
|
||
}
|
||
|
||
/** @param array<string,mixed> $buchung */
|
||
private function maske(bool $bargeld, array $buchung): string
|
||
{
|
||
$daten = [
|
||
'titel' => 'Buchung bearbeiten',
|
||
'konto' => Konto::find($this->konto),
|
||
'buchung' => $buchung + [
|
||
'id' => 1, 'konto_id' => $this->konto, 'datum' => '2026-09-01', 'betrag' => -10.0,
|
||
],
|
||
'splits' => [],
|
||
'eingaben' => null,
|
||
'kategorien' => Kategorie::baumFlach(),
|
||
'klassen' => [],
|
||
'konten' => Konto::alle(),
|
||
'bargeld' => $bargeld,
|
||
'flash' => [],
|
||
'benutzer' => null,
|
||
'csrfToken' => 'x',
|
||
'aktuellerPfad' => '/konten',
|
||
];
|
||
extract($daten, EXTR_SKIP);
|
||
|
||
ob_start();
|
||
include dirname(__DIR__) . '/src/Views/buchungen/formular.php';
|
||
return (string)ob_get_clean();
|
||
}
|
||
}
|