Fix n.n.: Umbuchung wird auch von der Gegenkontoseite erkannt
Eine Umbuchung gehört ihrem Ursprungskonto; der automatische Abgleich suchte eine passende Buchung bisher nur über deren konto_id. Kam der Kontoauszug der Empfängerseite mit ein paar Tagen Verzug herein, fand er die schon gebuchte Umbuchung nie und legte sie als eigenständige Buchung an – die Umbuchung stand danach doppelt im Kontoblatt und verfälschte den Saldo. Die Suche berücksichtigt jetzt auch die Gegenkonto-Seite einer Umbuchung, mit umgekehrtem Vorzeichen wie im Kontoblatt auch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
c2a6a9b899
commit
ecd01cde29
2 changed files with 67 additions and 4 deletions
|
|
@ -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.
|
||||||
|
|
@ -214,13 +220,16 @@ class Bankumsatz
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$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),
|
||||||
|
|
|
||||||
|
|
@ -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']);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue