Die Pruefung suchte woertlich nach "@paypal.de" oder "@paypal.com". Mails
aus einer Versand-Subdomain (e.paypal.de) oder einer Landesvariante
(paypal.at, paypal.co.uk) fielen damit als not_from_paypal durch. Geprueft
wird jetzt die Domain der Absenderadresse an ihrem Ende - "paypal.de.
beispiel.com" ist damit weiterhin kein PayPal, waere bei einer Textsuche
aber durchgerutscht.
check-imap-support.php listet zusaetzlich die letzten 15 Mails mit
Absender, Empfaengerzeilen und Betreff auf und sagt je Mail, ob der
Absender als PayPal gilt. Damit laesst sich ein not_from_paypal in einem
Lauf klaeren statt zu raten. Rein lesend: OP_READONLY, kein \Seen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher verglich die Zuordnung ausschliesslich Namen: der Parser las die
Adresse des Zahlers gar nicht aus, und die Suche kannte nur paypal_name und
display_name. Eine Zahlung von genau der Adresse, die beim Mitglied
hinterlegt ist, blieb deshalb liegen.
Die Adresse wird jetzt ausgelesen (neue Spalte paypal_payments.payer_email),
in der Warteschlange angezeigt und zuerst geprueft - sie ist im Mandanten
eindeutig und aendert sich nicht, wenn jemand bei PayPal anders heisst.
Der heikle Teil ist das Aussortieren: In einer weitergeleiteten Mail stehen
mehrere Adressen. Die falsche zu nehmen wuerde fremdes Geld dem Mitglied
hinter dieser Adresse gutschreiben - typischerweise dem Kassenwart. Deshalb
fallen paypal.*-Adressen und die eigene Eingangsadresse raus (geprueft
gegen die tatsaechliche Empfaengeradresse, Plus-Adressierung ignoriert),
und es zaehlt nur eine Adresse in unmittelbarer Naehe des Zahlernamens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die automatische Zuordnung verlangt exakte Uebereinstimmung des
Zahlernamens mit Anzeigename oder PayPal-Name des Mitglieds. Heisst jemand
bei PayPal anders, landet jede Zahlung in der Warteschlange - auch die
zehnte, obwohl der Fall laengst einmal von Hand geklaert wurde.
Nach einer manuellen Zuordnung wird der Zahlername deshalb beim Mitglied
hinterlegt. Zurueckhaltend: ein gepflegter PayPal-Name wird nie
ueberschrieben, ein Name gleich dem Anzeigenamen bringt nichts, und passt
der Name auch auf ein anderes Mitglied, wird nichts gelernt - das wuerde
die Zuordnung mehrdeutig machen und damit blockieren.
Was gelernt wurde, steht in der Erfolgsmeldung und im Audit-Log; eine
stillschweigende Aenderung am Mitglied waere sonst schwer nachvollziehbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Mail-Verarbeitung ignorierte bisher die PayPal-Schalter: eingehende
Zahlungen wurden auch dann automatisch gutgeschrieben, wenn der Betreiber
die Funktion gesperrt oder der Mandant PayPal abgeschaltet hatte. Jetzt
wird die Zahlung in dem Fall geparkt - gespeichert, aber ohne Buchung.
Wegwerfen liesse eine echte Zahlung unbemerkt verschwinden, buchen
widersprache der Abschaltung; die Zuordnungsseite bleibt fuer offene
Zahlungen ja erreichbar.
--dry-run war bisher irrefuehrend: es liess die Verarbeitung samt Buchung
laufen und uebersprang nur das Setzen des Gelesen-Flags - ausgerechnet beim
ersten Testlauf haette es also echtes Geld verbucht. Der Probelauf nutzt
jetzt paypal_preview(), das nichts schreibt und meldet, was passieren
wuerde (would_book/would_queue/would_park/duplicate).
scripts/check-paypal-inbox-flow.php deckt die Kette ohne IMAP ab:
Absenderpruefung, Token, Parser, Zuordnung, Netto-Buchung, Dedup, beide
Park-Faelle und die Schreibfreiheit der Vorschau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wird PayPal in den Mandant-Einstellungen abgeschaltet, waehrend noch
Zahlungen unzugeordnet in der Warteschlange liegen, kaeme ohne diese
Ausnahme niemand mehr an sie heran - weder ueber das Menue noch ueber die
URL.
paypal_inbox_accessible() haelt Menuepunkt und Seite deshalb offen, solange
paypal_count_unmatched() etwas findet; die Seite weist per Hinweis darauf
hin, dass sie nach dem Abarbeiten verschwindet. Eine Sperre durch den
Betreiber sticht die Ausnahme.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Abschluss des Legacy-Abbaus. Die Migration in participants/ledger_entries war
laut Dev-DB vollstaendig (keine legacy_mitarbeiter_id, kein legacy_table), die
kl_*-Tabellen enthielten nur noch Golden-Master-Testfixtures. Da kein echter
Altbestand mehr importiert werden muss (Prod entsteht aus der jetzigen Dev-DB),
kommt die Altwelt komplett raus.
Laufzeit-Code (Schritt 3a):
- ledger.php: Option legacy_mitarbeiter_ids, die Spalten aus allen SELECTs und
Rueckgaben, ledger_fetch_participant_summary_by_legacy_id sowie das Loeschen
gespiegelter Legacy-Zeilen in ledger_void_entry/ledger_void_own_self_entry
entfernt
- imports.php, paypal-inbox.php: legacy_mitarbeiter_id aus Ergebnis und
Typannotationen entfernt
- ledger-preview.php: Spalte "Legacy" entfernt
Skripte (Schritt 3b/3c):
- die acht reinen Legacy-/Golden-Master-Skripte entfernt (backfill-*,
seed-golden-master, golden-master-data, check-golden-master, check-m4-*,
check-m3-saas-basis)
- init-mysql-dev.php legt jetzt einen SaaS-Mandanten mit Owner-Login,
Teilnehmer, Journalbuchungen und Hinweis an statt kl_*-Zeilen
Schema (Schritt 4, Migration 0024):
- participants.legacy_mitarbeiter_id + uq_participants_tenant_legacy
- ledger_entries.legacy_table/legacy_id + uq_ledger_entries_tenant_legacy
- DROP der Tabellen kl_Einzahlungen, kl_Kaffeeverbrauch, kl_hinweise,
kl_config, kl_Mitarbeiter
Verifikation unveraendert gruen: http-smoke 33/0, role-matrix 55/0,
tenant-isolation 11/1 (Altfehler), m3-auth/tenant-resolution/billing gruen.
check-m3-settings-flow 7/6 ist vorbestehend (schon bei 3e24910 rot, mit
spaeteren Settings-Feldern nicht synchron) und unabhaengig von diesem Abbau.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Die Migration in participants/ledger_entries ist abgeschlossen: kein
Teilnehmer traegt noch eine legacy_mitarbeiter_id, kein Journaleintrag eine
legacy_table. Damit war der jeweils zweite Zweig ("Default-Mandant schreibt
zusaetzlich nach kl_Einzahlungen/kl_Kaffeeverbrauch/kl_Mitarbeiter") in
sechs Dateien nicht mehr erreichbar - er musste aber bei jeder Aenderung
mitgepflegt werden, zuletzt bei der Bemerkung fuer Einzahlungen.
Entfernt:
- Dual-Write beim Buchen: einzahlung.php, stricheintragen.php, index.php,
jahresauswertung.php, app/imports.php, app/paypal-inbox.php
- Dual-Write in der Mitgliederverwaltung: ledger_create_participant,
ledger_update_participant, ledger_set_participant_active,
ledger_anonymize_participant
- die verwaisten Spiegelfunktionen ledger_mirror_legacy_payment,
ledger_mirror_legacy_consumption und ledger_void_entry_by_legacy_id
Nebenbei behoben: kaffeeliste.php hat die Teilnehmer-Detailseite nur fuer
Mitglieder mit legacy_mitarbeiter_id verlinkt - die hat seit der Migration
niemand mehr, die Seite war also fuer alle unerreichbar. Verlinkt und
adressiert wird jetzt ueber participant_id; "user_id" bleibt als Alias
erhalten. Der Mandanten-Isolationstest prueft jetzt ledger_void_entry, also
den Pfad, den letzteneintraege.php tatsaechlich nutzt.
Pruefskripte unveraendert gegenueber der Baseline vor dem Umbau
(http-smoke 27/6, role-matrix 55/0); die Isolationspruefung steigt von
9 auf 11 PASS bei gleichem vorbestehendem Fehler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Einzahlungen koennen jetzt eine Bemerkung tragen und negativ sein, damit
Abzuege (Auszahlung bei Austritt, Erstattung) nachvollziehbar gebucht werden
koennen.
- ledger_entries.note wurde bisher nur beim Import befuellt und wird nun auch
bei Einzahlungen geschrieben und angezeigt
- Negative Betraege verlangen zwingend eine Bemerkung, sonst ist spaeter nicht
mehr nachvollziehbar, warum jemandem Geld abgezogen wurde
- Plausibilitaetsgrenze von 1000 EUR gegen Groessenordnungs-Tippfehler; bei
einem Fehler in einer Zeile wird gar nichts gebucht und die Eingaben bleiben
stehen
- Legacy-Tabelle kl_Einzahlungen bekommt eine Bemerkung-Spalte, sonst haette
ledger_mirror_legacy_payment die Notiz beim Re-Sync wieder mit NULL
ueberschrieben
- PayPal-Buchungen tragen jetzt Zahler, Datum und Mitteilung als Bemerkung
Ausserdem behoben: letzteneintraege.php hat Einzahlungen und Striche direkt
aus den Legacy-Tabellen gelesen, ohne nach Mandant zu filtern. Jeder
Mandanten-Admin sah damit die Buchungen aus den Legacy-Tabellen, und das
Stornieren lief ueber die nicht mandantengetrennten Legacy-IDs. Die Seite
arbeitet jetzt auf dem mandantengebundenen Journal, und ledger_void_entry
prueft Mandant und Buchungsart, bevor es storniert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mandanten leiten ihre PayPal-Benachrichtigungsmails an ein zentrales
IMAP-Postfach weiter. Die Zuordnung zum Mandanten erfolgt ueber
Plus-Adressierung (zahlungen+<token>@...), der Token wird pro Mandant
erzeugt und im Backend angezeigt.
- Parser fuer PayPal-Eingangsmails, tolerant gegenueber falsch kodierten
Waehrungssymbolen und HTML-Struktur weitergeleiteter Mails
- Gutgeschrieben wird der Nettobetrag, also was tatsaechlich ankam
(bei Waren & Dienstleistungen nach Abzug der PayPal-Gebuehr)
- Deduplizierung ueber den Transaktionscode, damit doppelt weitergeleitete
Mails nicht doppelt buchen
- Eindeutiger Namens-Match bucht automatisch, alles andere landet in einer
Warteschlange zur manuellen Zuordnung
- Absenderpruefung gegen paypal.de/.com, da weitergeleitete Mails keine
gueltige SPF/DKIM-Signatur mehr haben
- IMAP-Zugang ausschliesslich ueber Server-/Env-Einstellungen, nicht pro
Mandant konfigurierbar
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>