feat: Absender-Zuordnung kann nach Waehrung unterscheiden

Revolut verschickt fuer eine Zahlung in Euro und eine in Franken dieselbe
Mail vom selben Absender. Ohne die Waehrung als Kriterium landete alles auf
demselben Konto.

Die Erkennung liest die Waehrung ohnehin aus; sie wird jetzt VOR der
Zuordnung ausgewertet und ist ein optionales Filterkriterium. Zuordnungen
mit Waehrung werden zuerst geprueft – wer neben ein allgemeines "revolut"
ein genaues "revolut + EUR" stellt, meint das genaue, unabhaengig von der
Anlagereihenfolge.

Uebergangene Mails melden ausserdem die erkannte Waehrung mit, damit beim
Einrichten sichtbar ist, welcher Wert zu filtern waere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-22 20:48:55 +02:00
co-authored by Claude Opus 5
parent 512dac2b04
commit 899241f991
8 changed files with 141 additions and 16 deletions
+4
View File
@@ -736,6 +736,10 @@ class MailSender(Base):
# Teilstrings, gegen Absender und Betreff geprüft. Leer = jede Mail.
from_contains: Mapped[str | None] = mapped_column(String(255), nullable=True)
subject_contains: Mapped[str | None] = mapped_column(String(255), nullable=True)
# Nur Zahlungen dieser Währung. Leer = jede. Revolut schickt für EUR und CHF
# dieselbe Mail vom selben Absender – erst die Währung sagt, welches Konto
# gemeint ist.
currency: Mapped[str | None] = mapped_column(String(3), nullable=True)
account_id: Mapped[int | None] = mapped_column(
ForeignKey("accounts.id", ondelete="SET NULL"), nullable=True
)
+1
View File
@@ -584,6 +584,7 @@ class MailSenderIn(BaseModel):
name: str = Field(default="", max_length=80)
from_contains: str | None = Field(default=None, max_length=255)
subject_contains: str | None = Field(default=None, max_length=255)
currency: str | None = Field(default=None, max_length=3)
account_id: int | None = None
+23 -11
View File
@@ -139,7 +139,8 @@ ERKENNUNG_TEXTLAENGE = 2000
def _passender_absender(
absender: list[MailSender], von: str, betreff: str, text: str = ""
absender: list[MailSender], von: str, betreff: str, text: str = "",
waehrung: str | None = None,
) -> MailSender | None:
"""Erste Zuordnung, deren Erkennungstext greift.
@@ -152,11 +153,15 @@ def _passender_absender(
"""
heu = f"{von}\n{betreff}\n{text[:ERKENNUNG_TEXTLAENGE]}".lower()
betreff_klein = betreff.lower()
for a in absender:
# Zuordnungen MIT Währung zuerst prüfen: Wer „revolut + EUR → Revolut Euro"
# anlegt und daneben ein allgemeines „revolut", meint die genauere zuerst.
for a in sorted(absender, key=lambda x: (x.currency is None, x.id)):
if a.from_contains and a.from_contains.lower() not in heu:
continue
if a.subject_contains and a.subject_contains.lower() not in betreff_klein:
continue
if a.currency and (waehrung or "").upper() != a.currency.upper():
continue
return a
return None
@@ -223,15 +228,6 @@ def hole_neue(db: Session, quelle: MailSource) -> dict:
betreff, text = _text_der_mail(msg)
von = _absenderfeld(msg)
zuordnung = _passender_absender(absender, von, betreff, text)
if zuordnung is None:
uebersprungen += 1
if len(ohne_zuordnung) < 5:
ohne_zuordnung.append({
"from": str(msg["From"] or "")[:120],
"subject": betreff[:120],
})
continue
gesendet = None
if msg["Date"]:
@@ -240,7 +236,23 @@ def hole_neue(db: Session, quelle: MailSource) -> dict:
except (TypeError, ValueError):
gesendet = None
# Auswerten VOR dem Zuordnen: Bei Revolut kommt dieselbe Mail vom
# selben Absender, und erst die erkannte Währung sagt, ob die
# Zahlung aufs Euro- oder aufs Franken-Konto gehört.
e = mail_parse.parse(betreff, text, gesendet)
erkannte_waehrung = e.waehrung.wert if e.waehrung else None
zuordnung = _passender_absender(absender, von, betreff, text, erkannte_waehrung)
if zuordnung is None:
uebersprungen += 1
if len(ohne_zuordnung) < 5:
ohne_zuordnung.append({
"from": str(msg["From"] or "")[:120],
"subject": betreff[:120],
"currency": erkannte_waehrung,
})
continue
konto = konten.get(zuordnung.account_id) if zuordnung.account_id else None
tag = e.datum.wert if e.datum else (gesendet.date() if gesendet else date.today())
betrag = e.betrag.wert if e.betrag else 0