Finanzmanager/tests/51_RegelAusBuchungTest.php
fds2610-bot 057075368c Fix #94: Regel direkt aus einer Buchung anlegen
Toggle „Regel anlegen“ in der Buchungsmaske (Fix #94); beim Speichern
entsteht daraus eine Regel (AbgleichRegel::ausBuchung()), die das
Kontoblatt danach in einem modalen Dialog zum Prüfen und Anpassen zeigt.
Abbruch (auch per Escape) verwirft die Regel wieder, Speichern übernimmt
Änderungen. Die Regelfelder sind aus regeln.php in regelfelder.php
ausgelagert, damit beide Formulare dieselbe Bauart teilen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 23:11:06 +02:00

161 lines
6.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
use Finanzmanager\Models\AbgleichRegel;
use Finanzmanager\Models\Buchung;
use Finanzmanager\Models\Kategorie;
use Finanzmanager\Models\Klasse;
use Finanzmanager\Models\Konto;
/**
* Fix #94: Regel direkt aus einer Buchung anlegen – die Buchungsmaske
* erzeugt dafür `AbgleichRegel::ausBuchung()`.
*/
class RegelAusBuchungTest extends TestCase
{
protected string $testName = 'Test 51: Regel aus einer Buchung anlegen';
private int $giro = 0;
private int $strom = 0;
private int $versicherung = 0;
private int $klasse = 0;
public function run(): void
{
$this->mandantAnlegen();
$this->stammdaten();
$this->uebernimmtEmpfaengerKategorieUndKlasse();
$this->weichtOhneEmpfaengerAufDenBuchungstextAus();
$this->ohneEmpfaengerUndBuchungstextEntstehtKeineRegel();
$this->splitbuchungBekommtVerhaeltnisgleicheAnteile();
$this->unbekannteBuchungWirdAbgewiesen();
}
private function stammdaten(): void
{
$this->giro = Konto::anlegen([
'name' => 'Regeltest Giro', 'kontoart' => 'bank', 'anfangssaldo' => 1000, 'aktiv' => 1,
]);
$this->strom = Kategorie::anlegen(['name' => 'Regeltest Strom', 'art' => 'ausgabe']);
$this->versicherung = Kategorie::anlegen(['name' => 'Regeltest Versicherung', 'art' => 'ausgabe']);
$this->klasse = Klasse::anlegen('Regeltest Privat');
}
private function uebernimmtEmpfaengerKategorieUndKlasse(): void
{
$buchungId = Buchung::anlegen([
'konto_id' => $this->giro,
'datum' => '2026-05-01',
'betrag' => -42.50,
'empfaenger' => 'Stadtwerke Strom',
'buchungstext' => 'Abschlag Mai',
'kategorie_id' => $this->strom,
'klasse_id' => $this->klasse,
]);
$regelId = AbgleichRegel::ausBuchung($buchungId);
$this->assertNotEmpty($regelId, 'Die Regel wird angelegt');
$regel = AbgleichRegel::find((int)$regelId);
$this->assertNotEmpty($regel, 'Sie lässt sich wiederfinden');
$this->assertEquals('empfaenger', $regel['feld'], 'Geprüft wird der Empfänger');
$this->assertEquals('Stadtwerke Strom', $regel['muster'], 'Das Muster ist der Empfänger');
$this->assertEquals('enthaelt', $regel['vergleich'], 'Der Vergleich ist „enthält“');
$this->assertEquals($this->strom, (int)$regel['kategorie_id'], 'Die Kategorie wird übernommen');
$this->assertEquals($this->klasse, (int)$regel['klasse_id'], 'Die Klasse wird übernommen');
$this->assertEquals(1, (int)$regel['aktiv'], 'Die Regel ist gleich aktiv');
// Und sie taucht in der Liste der bestehenden Regeln auf – ergänzt, nicht
// ersetzt.
$gefunden = false;
foreach (AbgleichRegel::alle() as $eintrag) {
$gefunden = $gefunden || (int)$eintrag['id'] === (int)$regelId;
}
$this->assert($gefunden, 'Die neue Regel steht in der Liste der bestehenden Regeln');
// Und sie trifft tatsächlich auf einen passenden Umsatz.
$this->assert(
AbgleichRegel::passt($regel, ['betrag' => -42.50, 'empfaenger' => 'Stadtwerke Strom AG']),
'Die Regel trifft einen ähnlichen Umsatz'
);
}
private function weichtOhneEmpfaengerAufDenBuchungstextAus(): void
{
$buchungId = Buchung::anlegen([
'konto_id' => $this->giro,
'datum' => '2026-05-02',
'betrag' => -15.00,
'buchungstext' => 'Parkgebühr Innenstadt',
]);
$regelId = AbgleichRegel::ausBuchung($buchungId);
$regel = AbgleichRegel::find((int)$regelId);
$this->assertEquals('verwendungszweck', $regel['feld'], 'Ohne Empfänger zählt der Buchungstext');
$this->assertEquals('Parkgebühr Innenstadt', $regel['muster'], 'Das Muster ist der Buchungstext');
}
private function ohneEmpfaengerUndBuchungstextEntstehtKeineRegel(): void
{
$buchungId = Buchung::anlegen([
'konto_id' => $this->giro,
'datum' => '2026-05-03',
'betrag' => -5.00,
]);
$this->assertNull(AbgleichRegel::ausBuchung($buchungId), 'Ohne Muster entsteht keine Regel');
}
private function splitbuchungBekommtVerhaeltnisgleicheAnteile(): void
{
$buchungId = Buchung::anlegen(
[
'konto_id' => $this->giro,
'datum' => '2026-05-04',
'betrag' => -80.00,
'empfaenger' => 'Sammelrechnung Versicherung',
],
[
['kategorie_id' => $this->versicherung, 'betrag' => -50.00, 'notiz' => 'Kfz'],
['kategorie_id' => $this->versicherung, 'betrag' => -30.00, 'notiz' => 'Haftpflicht'],
]
);
$regelId = AbgleichRegel::ausBuchung($buchungId);
$regel = AbgleichRegel::find((int)$regelId);
$zeilen = AbgleichRegel::splitzeilen($regel);
$this->assertEquals(2, count($zeilen), 'Beide Splitzeilen kommen in der Regel an');
$this->assertEquals(62.5, $zeilen[0]['prozent'], 'Anteil der ersten Zeile im selben Verhältnis wie die Buchung');
$this->assertEquals(37.5, $zeilen[1]['prozent'], 'Anteil der zweiten Zeile ebenso');
// anwenden() rechnet die Anteile auf jeden Buchungsbetrag um – hier zur
// Probe auf einen doppelt so hohen Umsatz.
$angewendet = AbgleichRegel::anwenden($regel, ['betrag' => -160.00]);
$this->assertEquals(-100.0, $angewendet['splits'][0]['betrag'], 'Erste Zeile bleibt im Verhältnis 62,5 %');
$this->assertEquals(-60.0, $angewendet['splits'][1]['betrag'], 'Zweite Zeile bleibt im Verhältnis 37,5 %');
}
private function unbekannteBuchungWirdAbgewiesen(): void
{
$this->assert(
$this->wirftFehler(fn() => AbgleichRegel::ausBuchung(PHP_INT_MAX)),
'Eine nicht vorhandene Buchung wird abgewiesen'
);
}
/** Hilfsmittel: Wirft der Aufruf eine Ausnahme? */
private function wirftFehler(callable $aufruf): bool
{
try {
$aufruf();
} catch (\Throwable) {
return true;
}
return false;
}
}