Merge pull request 'Fix n.n.: Umbuchung wird auch von der Gegenkontoseite erkannt' (#81) from fds2610-bot/Finanzmanager:main into main

Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/81
This commit is contained in:
fds2610 2026-09-27 15:00:39 +02:00
commit 963a242fc5
2 changed files with 67 additions and 4 deletions

View file

@ -200,6 +200,12 @@ class Bankumsatz
* Konto, gleicher Betrag, Datum nah beieinander, noch mit keinem anderen * Konto, gleicher Betrag, Datum nah beieinander, noch mit keinem anderen
* Umsatz verbunden. * Umsatz verbunden.
* *
* Eine Umbuchung ist dabei auch dann „dasselbe Konto“, wenn sie auf ihrem
* Ursprungskonto steht und dieses Konto nur ihr Gegenkonto ist – sonst fände
* der Kontoauszug der Empfängerseite seine eigene Umbuchung nie, weil die
* Buchung formal einem anderen Konto gehört. Auf der Gegenkonto-Seite zählt
* der Betrag mit umgekehrtem Vorzeichen, wie im Kontoblatt auch.
*
* Die Toleranz in Tagen steht in den Einstellungen (`banking_abgleich_tage`): * Die Toleranz in Tagen steht in den Einstellungen (`banking_abgleich_tage`):
* Zwischen dem Tag, an dem man eine Zahlung erfasst, und dem Tag, an dem die * Zwischen dem Tag, an dem man eine Zahlung erfasst, und dem Tag, an dem die
* Bank sie bucht, liegen erfahrungsgemäß ein paar Tage. * Bank sie bucht, liegen erfahrungsgemäß ein paar Tage.
@ -215,12 +221,15 @@ class Bankumsatz
} }
$tage = max(0, Einstellung::getInt('banking_abgleich_tage', 5)); $tage = max(0, Einstellung::getInt('banking_abgleich_tage', 5));
$betrag = round((float)$umsatz['betrag'], 2);
$zeile = Database::fetchOne( $zeile = Database::fetchOne(
'SELECT b.* FROM buchungen b 'SELECT b.* FROM buchungen b
WHERE b.benutzer_id = ? WHERE b.benutzer_id = ?
AND b.konto_id = ? AND (
AND ABS(b.betrag - ?) < 0.005 (b.konto_id = ? AND ABS(b.betrag - ?) < 0.005)
OR (b.gegenkonto_id = ? AND b.gesplittet = 0 AND ABS(b.betrag + ?) < 0.005)
)
AND ABS(DATEDIFF(b.datum, ?)) <= ? AND ABS(DATEDIFF(b.datum, ?)) <= ?
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM bank_umsaetze u SELECT 1 FROM bank_umsaetze u
@ -230,7 +239,9 @@ class Bankumsatz
[ [
Mandant::id(), Mandant::id(),
$kontoId, $kontoId,
round((float)$umsatz['betrag'], 2), $betrag,
$kontoId,
$betrag,
(string)$umsatz['buchungstag'], (string)$umsatz['buchungstag'],
$tage, $tage,
(int)($umsatz['id'] ?? 0), (int)($umsatz['id'] ?? 0),

View file

@ -47,6 +47,7 @@ class BankingTest extends TestCase
$this->splitsHaltenDasVerhaeltnis(); $this->splitsHaltenDasVerhaeltnis();
$this->ungueltigesMusterWirdAbgewiesen(); $this->ungueltigesMusterWirdAbgewiesen();
$this->abgleichFindetVorhandeneBuchung(); $this->abgleichFindetVorhandeneBuchung();
$this->umbuchungWirdAuchVonDerGegenkontoseiteErkannt();
$this->uebernehmenErzeugtEineBuchung(); $this->uebernehmenErzeugtEineBuchung();
$this->regelmitSplitsErzeugtEineSplitbuchung(); $this->regelmitSplitsErzeugtEineSplitbuchung();
$this->buchungOhneKategorieWirdGefunden(); $this->buchungOhneKategorieWirdGefunden();
@ -680,6 +681,57 @@ class BankingTest extends TestCase
); );
} }
/**
* Eine Umbuchung gehört ihrem Ursprungskonto – hier dem Giro. Der
* Kontoauszug des Gegenkontos (Sparbuch) muss sie trotzdem finden, sonst
* bucht der automatische Abgleich sie dort ein zweites Mal.
*/
private function umbuchungWirdAuchVonDerGegenkontoseiteErkannt(): void
{
Einstellung::set('banking_abgleich_tage', '5', 'banking');
$umbuchungId = Buchung::anlegen([
'konto_id' => $this->giro, 'datum' => '2026-08-20', 'betrag' => -40.00,
'empfaenger' => 'Umbuchung Sparbuch', 'gegenkonto_id' => $this->sparkonto,
]);
// Die Bank bucht das Sparbuch erst einen Tag später – auf der
// Gegenkonto-Seite zählt der Betrag mit umgekehrtem Vorzeichen.
$umsatzId = Bankumsatz::anlegen([
'zugang_id' => $this->zugang,
'konto_id' => $this->sparkonto,
'extern_id' => 'test-gegenkonto-1',
'buchungstag' => '2026-08-21',
'wertstellung' => '2026-08-21',
'betrag' => 40.00,
'empfaenger' => 'Giro',
'buchungsart' => 'GUTSCHRIFT',
]);
$umsatz = Bankumsatz::find((int)$umsatzId);
$treffer = Bankumsatz::passendeBuchung($umsatz);
$this->assertNotEmpty($treffer, 'Die Umbuchung wird auch von der Gegenkontoseite gefunden');
$this->assertEquals($umbuchungId, (int)$treffer['id'], 'Und zwar genau sie');
$vorschlag = Bankumsatz::vorschlag($umsatz);
$this->assertEquals('verbinden', $vorschlag['art'], 'Der Vorschlag lautet: verbinden');
Bankumsatz::verbinden((int)$umsatzId, $umbuchungId);
$this->assertEquals(
1,
(int)Buchung::find($umbuchungId)['abgeglichen'],
'Die Umbuchung gilt jetzt als abgeglichen'
);
// Ein anderer Betrag auf der Gegenkontoseite passt nicht.
$this->assertNull(
Bankumsatz::passendeBuchung([
'konto_id' => $this->sparkonto, 'buchungstag' => '2026-08-21', 'betrag' => 12.34, 'id' => 0,
]),
'Ein anderer Betrag ist keine Umbuchung von drüben'
);
}
private function uebernehmenErzeugtEineBuchung(): void private function uebernehmenErzeugtEineBuchung(): void
{ {
$offen = Bankumsatz::liste(['status' => 'offen']); $offen = Bankumsatz::liste(['status' => 'offen']);