feat: Kurs der Bank statt EZB-Naeherung, und Saldo uebernehmen
Bei einer Fremdwaehrungs-Zahlung nennt die Bank oft ihren echten Kurs und den Betrag in der Ausgangswaehrung. Certain warf das weg und naeherte mit dem EZB-Kurs. Das faellt genau dort auf, wo es teuer ist: Die spaeter gelieferte Gegenseite eines Uebertrags laesst sich mit einem geschaetzten Betrag nicht als dieselbe Bewegung wiedererkennen. Der Uebertrag waehlt jetzt in drei Stufen: der von der Bank genannte Gegenbetrag (exakt), sonst ihr Kurs, sonst die EZB. Nur die erste Stufe gilt als bekannt – daran haengt, ob bei einem angebundenen Zielkonto nachgefragt wird. Der Parser klopft mehrere Formen ab, weil die Feldnamen je Anbindung verschieden sind, und setzt still nichts, wenn keine passt. Keine dieser Formen ist gegen echte Daten geprueft; ein falscher Kurs saehe aus wie ein richtiger, deshalb wird lieber gar keiner gesetzt. Ausserdem ein Knopf auf der Kontenseite: den Saldo der Bank als richtig uebernehmen. Die Differenz wandert in den ANFANGSBESTAND – sie steht fuer Bewegungen, die Certain nie gesehen hat, und genau dafuer ist er da. Eine Korrekturbuchung stuende dagegen fuer immer in der Liste und liefe durch jede Auswertung. Die Buchungen bleiben unangetastet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -877,3 +877,54 @@ def test_spaeter_gelieferte_gegenseite_wird_nicht_noch_einmal_gebucht(admin, fak
|
||||
assert abruf["geholt"] == 1, "der Umsatz kam an"
|
||||
assert abruf["neu"] == 0, "und wurde als Gegenseite erkannt"
|
||||
assert admin.get(f"/api/accounts/{ziel['id']}").json()["balance_cents"] == vorher
|
||||
|
||||
|
||||
def test_kurs_der_bank_wird_uebernommen(admin, fake_bank):
|
||||
"""Die Bank rechnet mit ihrem eigenen Kurs. Ihn wegzuwerfen und mit dem
|
||||
EZB-Kurs zu naehern, macht den Betrag der Gegenseite unbrauchbar."""
|
||||
_app(admin)
|
||||
uid = f"acc-{secrets.token_hex(4)}"
|
||||
fake_bank(
|
||||
konten=[{"uid": uid, "currency": "CHF"}],
|
||||
umsaetze=[{
|
||||
"entry_reference": f"tx-{secrets.token_hex(4)}",
|
||||
"booking_date": date.today().isoformat(),
|
||||
"transaction_amount": {"amount": "93.50", "currency": "CHF"},
|
||||
"credit_debit_indicator": "DBIT",
|
||||
"creditor": {"name": "Eigenes Euro-Konto"},
|
||||
"currency_exchange": {
|
||||
"source_currency": "CHF", "target_currency": "EUR",
|
||||
"exchange_rate": "1.0695",
|
||||
"instructed_amount": {"amount": "100.00", "currency": "EUR"},
|
||||
},
|
||||
}],
|
||||
)
|
||||
state = admin.post("/api/bank/connect",
|
||||
json={"aspsp_name": f"Kurs-{secrets.token_hex(3)}",
|
||||
"country": "CH"}).json()["state"]
|
||||
admin.post(f"/api/bank/callback?state={state}&code=guter-code")
|
||||
link = [v for v in admin.get("/api/bank/links").json() if v["remote_uid"] == uid][0]
|
||||
chf = _konto(admin, name=f"Kurs CHF {secrets.token_hex(3)}", currency="CHF")
|
||||
admin.post(f"/api/bank/links/{link['id']}/account/{chf['id']}")
|
||||
admin.post(f"/api/bank/links/{link['id']}/sync")
|
||||
|
||||
eintrag = [e for e in admin.get("/api/inbox").json()
|
||||
if e["payee"] == "Eigenes Euro-Konto"][0]
|
||||
assert eintrag["counter_amount_cents"] == 10000
|
||||
assert eintrag["counter_currency"] == "EUR"
|
||||
assert float(eintrag["fx_rate"]) == 1.0695
|
||||
|
||||
|
||||
def test_ohne_angabe_wird_nichts_geraten(admin, fake_bank):
|
||||
"""Ein falscher Kurs sieht aus wie ein richtiger – lieber gar keiner."""
|
||||
verbindung, link, _ = _verbunden(admin, fake_bank,
|
||||
umsaetze=[dict(UMSATZ,
|
||||
booking_date=date.today().isoformat())])
|
||||
konto = _konto(admin, name=f"Ohne Kurs {secrets.token_hex(3)}", currency="EUR")
|
||||
admin.post(f"/api/bank/links/{link['id']}/account/{konto['id']}")
|
||||
admin.post(f"/api/bank/connections/{verbindung}/sync")
|
||||
|
||||
eintrag = [e for e in admin.get("/api/inbox").json()
|
||||
if e["payee"] == "SPEEDEE RESTAURATIONS"][0]
|
||||
assert eintrag["fx_rate"] is None
|
||||
assert eintrag["counter_amount_cents"] is None
|
||||
|
||||
Reference in New Issue
Block a user