fix: Revolut-Import wertet Gebühr, Status und Währung aus
Das Revolut-Preset kannte die Spalten Gebühr/Währung/Status laut Kommentar, der Parser hat sie aber nie gelesen. Folgen: Gebühren fehlten im Saldo, stornierte (REVERTED) und schwebende Zeilen wurden mitgebucht, und Zeilen fremder Währungs-Pockets landeten 1:1 im Konto – der Saldo lief dadurch zu hoch. - Gebührenspalte mindert den Betrag (immer als Kosten) - nur Zeilen mit Status COMPLETED (oder ohne Statusspalte) sind buchbar - Zeilen in anderer Währung als das Zielkonto werden nicht gebucht - Grund wird in der Prüfansicht als Badge angezeigt (storniert / Währung) - Gebühr/Währung/Status in der Spaltenzuordnung wählbar; Revolut-Preset ergänzt - Test mit echtem Revolut-Format (Gebühr, Storno, Fremdwährung), 80 grün Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -61,7 +61,8 @@ PRESETS: list[dict] = [
|
||||
"amount_mode": "signed",
|
||||
# Spalten: 0=Typ, 1=Produkt, 2=Start, 3=Abgeschlossen, 4=Beschreibung,
|
||||
# 5=Betrag, 6=Gebühr, 7=Währung, 8=Status, 9=Saldo.
|
||||
"column_map": {"date": 3, "amount": 5, "payee": 4,
|
||||
"column_map": {"date": 3, "amount": 5, "payee": 4, "fee": 6,
|
||||
"currency": 7, "state": 8,
|
||||
"purpose": None, "counterparty_iban": None},
|
||||
},
|
||||
]
|
||||
@@ -251,12 +252,20 @@ def parse_file_to_candidates(data: bytes, template: ImportTemplate) -> list[dict
|
||||
else:
|
||||
amount = parse_amount(cell("amount"), template.decimal_sep)
|
||||
|
||||
# Getrennte Gebührenspalte (z. B. Revolut) mindert den Betrag – eine Gebühr
|
||||
# ist immer ein Kostenposten, unabhängig vom Vorzeichen in der Datei.
|
||||
fee = parse_amount(cell("fee"), template.decimal_sep)
|
||||
if amount is not None and fee:
|
||||
amount -= abs(fee)
|
||||
|
||||
out.append({
|
||||
"date": d.isoformat() if d else None,
|
||||
"amount_cents": amount,
|
||||
"payee": cell("payee") or None,
|
||||
"purpose": cell("purpose") or None,
|
||||
"counterparty_iban": normalize_iban(cell("counterparty_iban")) or None,
|
||||
"state": (cell("state").strip().upper() or None),
|
||||
"row_currency": (cell("currency").strip().upper() or None),
|
||||
})
|
||||
return out
|
||||
|
||||
@@ -316,7 +325,19 @@ def detect_rows(db: Session, user: User, account: Account, candidates: list[dict
|
||||
iban = cand.get("counterparty_iban")
|
||||
d = date.fromisoformat(d_iso) if d_iso else None
|
||||
|
||||
valid = d is not None and amount is not None and amount != 0
|
||||
# Nur abgeschlossene Buchungen zählen. Revolut liefert auch stornierte
|
||||
# (REVERTED) und schwebende (PENDING) Zeilen mit – die dürfen den Saldo
|
||||
# nicht verändern.
|
||||
state = (cand.get("state") or "").upper()
|
||||
state_ok = state in ("", "COMPLETED")
|
||||
# Zeile in fremder Währung (Revolut-Export enthält alle Währungs-Pockets).
|
||||
row_cur = (cand.get("row_currency") or "").upper()
|
||||
currency_ok = not row_cur or row_cur == account.currency.upper()
|
||||
|
||||
valid = (
|
||||
d is not None and amount is not None and amount != 0
|
||||
and state_ok and currency_ok
|
||||
)
|
||||
ttype = "expense" if (amount or 0) < 0 else "income"
|
||||
category_id: int | None = None
|
||||
matched_rule_id: int | None = None
|
||||
@@ -364,6 +385,12 @@ def detect_rows(db: Session, user: User, account: Account, candidates: list[dict
|
||||
"purpose": purpose or None,
|
||||
"counterparty_iban": iban or None,
|
||||
"valid": valid,
|
||||
"skip_reason": (
|
||||
None if valid or d is None or amount is None
|
||||
else ("state" if not state_ok else "currency" if not currency_ok else None)
|
||||
),
|
||||
"state": state or None,
|
||||
"row_currency": row_cur or None,
|
||||
"type": ttype,
|
||||
"category_id": category_id,
|
||||
"matched_rule_id": matched_rule_id,
|
||||
|
||||
Reference in New Issue
Block a user