Commit Graph
137 Commits
Author SHA1 Message Date
bjoernpoettkerandClaude Opus 5.5 fe338271bf fix(security): Symlinks im Scanner, Exportpfad und Belegnummer-URL absichern
- Scanner: Dateien von der Scan-Freigabe werden nur übernommen, wenn sie
  reguläre Dateien in echten Verzeichnissen sind. chokidar folgt keinen
  Symlinks mehr; beim Kopieren über Mount-Grenzen (/mnt/scans → /mnt/data)
  wird mit O_NOFOLLOW geöffnet und nur exklusiv geschrieben. Bisher holte
  ein Symlink wie scan.pdf -> /proc/self/environ die Zieldatei in den
  Posteingang.
- Export: Dateinamen (enthalten u.a. Barcode-Werte) werden auf ein
  Pfadsegment reduziert – „../“ verlässt den FTP-Zielordner nicht mehr.
- Belegnummer: {Jahr}/{Nummer} werden URL-kodiert in BELEGNUMMER_*_URL
  eingesetzt (Nummer stammt aus Request bzw. gedrucktem Barcode);
  belegnummer/release prüft die Typen im Body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:57 +01:00
bjoernpoettkerandClaude Opus 5.5 c28475f71f fix(security): JWT-Audience gegen OIDC_CLIENT_ID prüfen
Bisher prüfte das Backend nur Issuer und Signatur. Ein Access-Token, das
derselbe Authentik-Provider für eine andere Anwendung ausgestellt hat,
wurde ebenfalls akzeptiert.

- JwtStrategy verlangt aud == OIDC_CLIENT_ID (jwt-optionen.ts, getestet
  mit echten RS256-Tokens). Ohne OIDC_CLIENT_ID bleibt die Prüfung aus,
  mit Warnung beim Start.
- docker-compose.yml übergibt OIDC_CLIENT_ID jetzt ans Backend, Standard:
  dieselbe Client-ID wie das Frontend (VITE_OIDC_CLIENT_ID).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:57 +01:00
bjoernpoettkerandClaude Opus 5.5 b11294ec10 fix(security): Path-Traversal in den Mail-Vorschau-Routen
attachments/:attachmentId/pages/:page/{thumb,preview} übernahm die ID
ungeprüft in den Dateipfad – mit `..%2Finbox%2F<uuid>` ließen sich
Scanner-Vorschauen anderer Benutzer aus /mnt/data/inbox lesen.

- Routen wandeln attachmentId und page per ParseIntPipe in Zahlen um.
- EmailPageCacheService nimmt nur positive ganze Zahlen als Anhang-ID und
  Seite an, PageCacheService (Scanner) nur UUIDs als Dokument-ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:57 +01:00
bjoernpoettkerandClaude Opus 5.5 e0a6598b35 refactor: Paperless-Dokumentrouten unter /api/paperless/documents/:id
„inbox“ war doppelt belegt (/api/inbox = Scanner, /api/paperless/inbox =
Paperless-Posteingang) und passte nicht zur Manuell-Seite, die dieselben
Routen nutzt. Umbenannt (ohne Alias, nur das eigene Frontend nutzt sie):

  PUT  paperless/inbox/:id            → PUT  paperless/documents/:id
  GET  paperless/inbox/pdf/:id        → GET  paperless/documents/:id/pdf
  GET  paperless/inbox/preview/:id    → GET  paperless/documents/:id/preview
  GET  paperless/inbox/:id/erechnung  → GET  paperless/documents/:id/erechnung

Dabei:
- Dokumentsuche (GET documents) wieder für den Posteingang (VIEW_INBOX)
  statt nur Admin – die Verknüpfungssuche im Bearbeiten-Dialog nutzt sie;
  die Einschränkung aus dem vorigen Commit hatte sie gebrochen.
- Suche und GET documents/:id liefern nur noch id, Titel, ASN, Datum
  (kein OCR-Volltext, keine Statusfelder).
- documents/:id/pdf auch für VIEW_SCANNER (Duplikatvergleich im Scanner).
- E-Rechnungs-Vorschlag prüft wie das Speichern pro Dokument (Tag 1 bzw.
  Manuell-Tag); Vorschau/PDF bewusst nicht (Suchergebnisse, Duplikate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:57 +01:00
bjoernpoettkerandClaude Opus 5.5 04fca933ee fix(auth): PM_Buchhaltung umfasst den Posteingang
Der Posteingang ist eine abgespeckte Buchhaltung. Bisher bekam
PM_Buchhaltung nur PROCESS_MANUALLY; die Seite „Manuell bearbeiten“
nutzt für Vorschau, PDF, E-Rechnung und Speichern aber Routen mit
VIEW_INBOX – reine Buchhaltungsnutzer konnten dort nichts speichern.

PM_Buchhaltung bekommt jetzt zusätzlich VIEW_INBOX (Backend und Frontend).
permissions.enum.spec.ts vergleicht künftig nicht nur die Gruppennamen,
sondern auch die Berechtigungen pro Gruppe zwischen beiden Kopien.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:43 +01:00
bjoernpoettkerandClaude Opus 5.5 cedd7c78f4 fix(security): Posteingangs-Maske nur für Dokumente im eigenen Arbeitsbereich
PUT paperless/inbox/:id prüft jetzt pro Dokument: Posteingang-Rolle nur
für Dokumente mit Tag 1 (Posteingang), Buchhaltung nur für Dokumente mit
MANUELL_BEARBEITEN_TAG, Admins immer. Bisher ließen sich Mandant (Owner),
Felder und Titel beliebiger Paperless-Dokumente ändern, weil das Backend
mit dem Service-Token arbeitet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:43 +01:00
bjoernpoettkerandClaude Opus 5.5 3f98dfd98f fix(security): Rechteprüfung deny by default, API-Keys nur auf freigegebenen Routen
- PermissionsGuard lehnt Routen ohne @RequirePermissions/@AllowAnyAuthenticated
  ab. API-Keys umgehen keine Rechteprüfung mehr, sondern gelten nur auf
  Routen mit @AllowApiKey (Paperless-Webhook, externer Upload, checksum,
  Agent-Routen des Etikettendrucks).
- Rechte für 24 bisher offene Routen ergänzt, u.a. paperless/documents
  (nur Admin), paperless/documents/:id und Kontonummern (Posteingang/
  manuell), webhook/status (Admin). routen-rechte.spec.ts prüft alle Routen.
- Etiketten-Agent: printed/error nur für abgeholte, offene Jobs (bedingtes
  Update, sonst 409) – ein gedruckter Job kann seine Nummer nicht mehr
  freigeben. Feldwerte nur aus den Eingabefeldern der Vorlage ({number}
  nicht vorgebbar) und URL-kodiert in GET-/Callback-URLs.
- PUT paperless/inbox/:id ignoriert Freigabe (CF15) und Zahlung (CF16)
  ohne die jeweilige Rolle (Funktionstrennung).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:43 +01:00
bjoernpoettkerandClaude Opus 5.5 de9cb09bcd fix(security): Ghostscript mit -dSAFER, Schutz vor PDF-Dekompressionsbomben
- Ghostscript läuft auf PDFs aus Mails und vom Scanner jetzt mit -dSAFER
  statt -dNOSAFER (kein Datei-/Prozesszugriff aus PostScript), mit
  Timeout (60 s je Seite, 10 min je Dokument, SIGKILL) und höchstens
  500 gerenderten Seiten. Temp-Dateien werden auch im Fehlerfall
  entfernt. Ungenutztes getPageCount (brauchte Dateizugriff) entfernt.
- Neuer Vorab-Check ueberschreitetDekompressionsLimit (256 MB, streamend,
  erkennt auch Deflate-Ketten und abgeschnittene Streams). Fingerprint
  (pdf.js) und E-Rechnungs-Extraktion (pdf-lib) laden Bomben nicht mehr;
  eine einzelne Mail konnte das Backend sonst per OOM dauerhaft lahmlegen.
- Verschlüsselte PDFs bekommen keinen Inhalts-Fingerprint mehr ('' =
  nicht vergleichbar), weil pdf.js sie erst intern entschlüsselt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:43 +01:00
bjoernpoettkerandClaude Opus 5.5 bb8bf3714f fix(security): API-Keys nur für Admins, Mailversand ohne Datei-/URL-Zugriff
- ApiKeysController verlangt MANAGE_SETTINGS. Bisher konnte jeder
  OIDC-Benutzer einen API-Key erzeugen, der im PermissionsGuard jede
  Rechteprüfung umgeht (Rechteausweitung zum Admin).
- MailService setzt disableFileAccess/disableUrlAccess für jede Mail.
- send-email im Posteingang prüft den Body streng auf Strings/Zahlen.
  Bisher liest nodemailer bei html: { path: '/proc/self/environ' }
  beliebige Serverdateien ein und verschickt sie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:43 +01:00
bjoernpoettkerandClaude Opus 5.5 c1afa02ee2 fix: Duplikatprüfung immer gegen die ASN, die das Dokument beim Upload bekäme
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Im Posteingang kann das ASN-Feld der Aktion von der Belegnummer abweichen.
Prüfung, Versions-Upload und normaler Upload nutzen jetzt dieselbe ASN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:27:21 +01:00
bjoernpoettkerandClaude Opus 5.5 79dadb9c93 fix: Kennzahlen mit Timeout laden, ungültige Seitenbereiche als 400 melden
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:22:44 +01:00
bjoernpoettkerandClaude Opus 5.5 32b5228f68 fix: Zieldokument prüfen, Versions-Verarbeitung abwarten, Abschnittsseiten vom Backend
- Neue Version nur, wenn die Belegnummer in Paperless eindeutig zu genau
  diesem Dokument gehört (serverseitig geprüft)
- Auf die Paperless-Task der Version warten; Ablehnung → Fehler, Tag 21 weg
- Entscheidung im Posteingang nur zusammen mit processOnlyOne
- Vergleichsdialog im Posteingang zeigt die Seiten, die tatsächlich
  hochgeladen werden

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:14:01 +01:00
bjoernpoettkerandClaude Opus 5.5 fffbf692b2 feat(frontend): Vergleichsdialog für Duplikate mit neuer Version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:04:42 +01:00
bjoernpoettkerandClaude Opus 5.5 21a447e915 feat(mail): Beleg beim Import als neue Version anhängen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:01:47 +01:00
bjoernpoettkerandClaude Opus 5.5 bdc85f5f1f feat(posteingang): Duplikat als neue Version oder verwerfen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:58:31 +01:00
bjoernpoettkerandClaude Opus 5.5 170eb72c70 feat(paperless): Belege als neue Version anhängen und mit Tag 21 markieren
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:57:02 +01:00
bjoernpoettkerandClaude Opus 5.5 8e0c609802 feat: Seiten- und Wortzahl für den Duplikatvergleich
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:55:51 +01:00
bjoernpoettkerandClaude Opus 5.5 57a460ffa2 feat(paperless): Belegnummer gegen Paperless prüfen (CF7 + ASN)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:53:50 +01:00
bjoernpoettkerandClaude Opus 5.5 bc2d1cd4b7 fix(agrarmonitor): gebuchte Belege wieder erkennen, Titel per Template
Build and Push Multi-Platform Images / build-and-push (push) Successful in 18s
- Connector auf 9bda65e: Buchungsstatus aus #bookedStatus lesen. AM zeigt
  hinter dem Status jetzt „Zahlung erfassen“, dadurch war buchungsDatum
  immer leer und das Polling hat keinen Beleg als verbucht markiert.
- Polling: Schritt-Logging pro Dokument über AGRARMONITOR_POLLING_DEBUG,
  Abschlusszeile zählt „noch nicht gebucht“ mit.
- Upload-Check: Titel über das Titel-Template des Dokumenttyps, Fallback
  auf ERG/EGU <Belegnr>. Template-Auflösung als resolveTitelTemplate()
  ausgelagert und im Processor wiederverwendet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:03:27 +01:00
bjoernpoettkerandClaude Opus 5.5 df50470e53 fix(paperless): documents.checksum für SHA-256 verbreitern
Build and Push Multi-Platform Images / build-and-push (push) Successful in 28s
Paperless-NGX ≥ 3.0 liefert original_checksum als SHA-256 (64 Zeichen).
Die lokale Spiegeltabelle documents hatte nur varchar(32), wodurch die
Nachbearbeitung mit „Data too long for column 'checksum'“ abbrach – nach
dem Setzen von Notizen, aber vor Fertig=1. Der Task wurde dadurch jede
Minute erneut verarbeitet und erzeugte doppelte Notizen.

- documents.checksum auf varchar(64) (Entity + Migration)
- Sync der Spiegeltabelle ist nicht mehr fatal: Fehler werden als
  Warnung geloggt, der Task wird trotzdem abgeschlossen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:35:45 +01:00
bjoernpoettkerandClaude Opus 5.5 4f34855c12 fix(paperless): Anpassung an Paperless-NGX 3.4 (API v10)
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
- Task-Status: getTask() normalisiert die paginierte v10-Antwort
  (status klein, result_data, related_document_ids). Bisher wurde jede
  Task als „nicht gefunden“ gelöscht, ohne Nachbearbeitung.
- Checksummen: Paperless ≥ 3.0 nutzt SHA-256 statt MD5. Duplikatprüfung
  umgestellt; Migration verbreitert Attachments.Checksum und berechnet
  Bestandswerte per SHA2() neu.
- Custom-Field-Suche über custom_field_query, der alte Filter wurde von
  Paperless ignoriert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:15:01 +01:00
bjoernpoettkerandClaude Opus 5.5 33457c19c2 chore(docker): Node.js 20 → 24 LTS
Build and Push Multi-Platform Images / build-and-push (push) Successful in 1m22s
Node 20 ist seit April 2026 End-of-Life. Backend- und Frontend-Images
laufen jetzt auf node:24-alpine; @types/node im Backend auf ^24 angehoben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:00:57 +01:00
bjoernpoettkerandClaude Opus 5.5 cdcee29c5b fix(agrarmonitor): Connector-Update – Lieferscheinnummer bei gebuchten Rechnungen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 1m4s
Connector e6ce1d2 schickt die disabled-Felder aus editForm.php mit an
update.php. Bisher schlug „Lieferscheinnummer setzen“ bei gebuchten
Rechnungen mit HTTP 422 fehl (typ, firma_id, Adressfelder fehlten).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:55:36 +02:00
bjoernpoettkerandClaude Opus 5.5 0ff78707d1 feat(agrarmonitor): Cookies in den Einstellungen zurücksetzen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 42s
Neuer Button „Cookies zurücksetzen“ neben „Status abrufen“. Er löscht
die gespeicherte Agrarmonitor-Session samt Cookie-Datei; beim nächsten
Zugriff meldet sich der Manager neu an.

Eine laufende Connector-Instanz wird über clearSession() zurückgesetzt
und verworfen, damit sie ihren alten Cookie-Jar nicht wieder in die
Datei schreibt. Während einer Lieferantennummern-Vergabe ist das
Zurücksetzen gesperrt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:53:47 +02:00
bjoernpoettkerandClaude Opus 5.5 549948913a fix(agrarmonitor): Lieferanten-Detailseite ohne AJAX-Header laden
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
Im Einzeltest fehlte auf /kunden/detail/<id> das Feld
#lieferantennummer, obwohl die Liste kurz vorher mit derselben Session
geladen wurde. Die Detailseite wird jetzt wie eine normale
Browser-Navigation ohne X-Requested-With geladen (wie im Connector
bei den Eingangsrechnungen).

Fehlt das Feld trotzdem, nennt der Fehler HTTP-Status, Ziel-URL,
Seitentitel und Feldnamen – ohne Feldwerte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:34:49 +02:00
bjoernpoettkerandClaude Opus 5.5 3d4125b29a feat(agrarmonitor): fehlende Lieferantennummern vergeben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 1m1s
Neuer Bereich „Lieferantennummern überprüfen“ in den Agrarmonitor-
Einstellungen. Er vergibt allen aktiven Lieferanten ohne
Lieferantennummer eine fortlaufende Nummer im Schema 1555xxxx, ab der
höchsten vergebenen Nummer (Inaktive zählen mit, Altlasten werden
ignoriert).

- Vorschau „Lieferant → neue Nummer“, geschrieben wird erst nach
  Bestätigung; Einzelmodus zum Testen an einem Lieferanten
- formData wird wie in der Weboberfläche aus #form_detail aufgebaut,
  nur die Lieferantennummer ändert sich
- Dublettencheck über vorhanden.php, Erfolgskontrolle durch Neuladen
  des Details, Abbruch beim ersten Fehler
- Lauf im Hintergrund mit Status-Abfrage (wegen Proxy-Timeouts),
  Listenfilter werden danach zurückgesetzt
- HTML-Parsing mit cheerio, Unit-Tests für Parser und Vergabe-Ablauf

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:51:13 +02:00
bjoernpoettkerandClaude Opus 5.5 029009182a fix(agrarmonitor): Fehlergrund beim Speichern in Agrarmonitor loggen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 50s
Connector-Update: Bei HTTP 422 von update.php steht jetzt die
Begründung von Agrarmonitor in der Warnung „Lieferscheinnummer setzen
fehlgeschlagen“.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:22:28 +02:00
bjoernpoettkerandClaude Opus 5.5 79ab3da2c2 fix(agrarmonitor): Connector mit neuen Eingangsrechnungs-Endpunkten
Build and Push Multi-Platform Images / build-and-push (push) Failing after 19s
Agrarmonitor hat eingangsrechnungen.php entfernt, dadurch schlug die
Livesearch im Upload-Check mit 404 fehl. Der Connector nutzt jetzt
editForm.php zum Laden und update.php zum Speichern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:18:40 +02:00
bjoernpoettkerandClaude Opus 5.5 d2236368be fix(agrarmonitor): aufgerufene Seite bei HTTP-Fehlern mitloggen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 43s
Fehlermeldungen im Polling enthielten nur den Status (z. B. „Request
failed with status code 404“), nicht aber die aufgerufene URL. Die neue
Hilfsfunktion fehlerText() hängt bei Axios-Fehlern Methode und
vollständige URL an, damit sich z. B. der Livesearch-404 einer
konkreten Seite zuordnen lässt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:02:59 +02:00
bjoernpoettkerandClaude Opus 5.5 acb9076944 fix(mail): „Anhänge prüfen“ erkennt Duplikate auch über den Inhalt
Build and Push Multi-Platform Images / build-and-push (push) Successful in 32s
check-attachments fragte nur die Checksumme bei Paperless ab. Neu
erzeugte PDFs mit gleichem Inhalt wie ein bereits importierter Anhang
blieben dadurch auf „Neu“. Findet Paperless die Checksumme nicht, wird
jetzt per Text-Fingerprint nach einem importierten Anhang gesucht; die
Mail wird dann als verarbeitet markiert und die Paperless-ID übernommen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 12:42:02 +02:00
bjoernpoettkerandClaude Opus 5.5 797cbb9b71 fix(mail): Duplikate auch bei neu erzeugten PDFs erkennen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 45s
Absender erzeugen PDFs beim erneuten Versand oft neu (andere
Erstellungszeit/Dokument-ID), dadurch unterscheidet sich die Checksumme
und die Duplikatprüfung griff nicht (z.B. Mail 7071 vs. 7072).

- Neuer Text-Fingerprint (SHA-256 über normalisierten PDF-Text via
  pdf-parse) in Attachments.ContentFingerprint, inkl. Migration und Index
- Wird beim Mail-Abruf berechnet; Altbestand füllt ein Hintergrund-
  Backfill beim Start auf, fehlende Werte werden bei Bedarf nachberechnet
- Duplikatprüfung und Import-Sperre berücksichtigen Anhänge mit gleichem
  Inhalt, die importiert sind oder gerade importiert werden
- Jest braucht für pdf-parse --experimental-vm-modules (test-Skripte)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 12:27:31 +02:00
bjoernpoettkerandClaude Opus 5.5 534b313054 fix(mail): Duplikate auch bei gerade laufenden Importen erkennen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Wurde dieselbe Mail zweimal weitergeleitet, erkannte der Import-Assistent
das Duplikat nicht, solange Paperless das erste Dokument noch verarbeitete –
geprüft wurde nur die Checksumme in Paperless.

- checkDuplicate() berücksichtigt zusätzlich offene Paperless-Tasks von
  Anhängen mit gleicher Checksumme (gleicher Seitenbereich)
- Assistent prüft erneut vor dem Abrufen der Belegnummern und vor dem
  Import; Duplikate werden ignoriert, reservierte Nummern freigegeben
- executeImport prüft serverseitig vor Upload und Festlegen der Nummer,
  sperrt laufende Importe gegen parallele Doppelimporte und meldet 409

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 10:29:43 +02:00
bjoernpoettkerandClaude Opus 5.5 3cea3fb239 feat(mail): E-Mails mit Leserecht als ignoriert markieren
Der Button „Als ignoriert markieren“ war für alle mit VIEW_MAIL sichtbar,
der Aufruf (PATCH :id/status) verlangte aber MANAGE_ALL und scheiterte
mit 403. Neuer Endpunkt POST /api/emails/:id/ignore mit VIEW_MAIL, der nur
Status 3 setzen kann; bereits verarbeitete E-Mails werden abgelehnt. Das
allgemeine Statusändern bleibt Admins vorbehalten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 10:29:43 +02:00
bjoernpoettkerandClaude Opus 5 f97177943d feat(mail): abgeschlossene E-Mails erneut zur Bearbeitung freigeben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Der Import-Wizard ist für verarbeitete E-Mails gesperrt. Damit ließ sich
ein fehlerhaft abgeschlossener Vorgang bisher nicht korrigieren.

Administratoren können eine E-Mail in der Detailansicht nun wieder
freigeben (POST /api/emails/:id/reimport, MANAGE_ALL). Die Anhänge aus
der Datenbank werden weiterverwendet; ein erneuter Abruf vom IMAP-Server
findet nicht statt.

Die Freigabe setzt Status 4 ("Zur Nachbearbeitung") statt Status 0:
check-attachments prüft Mails mit Status 0, findet die Anhänge per
Checksumme in Paperless – dort liegen sie ja bereits – und würde die
Freigabe beim nächsten Lauf sofort wieder auf "Verarbeitet" zurückdrehen.
Status 4 bleibt davon unberührt. Da Status ein freies int ist, ist dafür
keine Migration nötig.

Erlaubt sind nur die Status 1, 2 und 3; liegt die E-Mail ohnehin im
Arbeitsvorrat, antwortet der Endpunkt mit 400, statt den Zustand still zu
überschreiben. Wer die Freigabe ausgelöst hat, steht im Log.

Der neue Status erscheint als eigener Tag sowie in beiden Filtern der
Mailpostfach-Übersicht, damit die freigegebenen Mails auffindbar bleiben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 11:06:08 +02:00
bjoernpoettkerandClaude Opus 5 b7b65c64ae fix: Berechtigungen der Tagesübersicht, WebDAV-Ausbau, Tests instand gesetzt
Build and Push Multi-Platform Images / build-and-push (push) Successful in 55s
Die tägliche E-Mail-Zusammenfassung zeigte Nutzern Bereiche, für die sie
keine Berechtigung haben. Zwei unabhängige Ursachen:

- Das Backend prüfte weiterhin die Altgruppe PM_Belege. Die Umbenennung
  zu PM_Buchhaltung (ca1d371) war nur im Frontend angekommen, weshalb
  der Digest "Manuell bearbeiten" und "In Agrarmonitor" anbot, während
  die Oberfläche beide Bereiche sperrte.
- Der Cron-Versand wertet die Gruppen aus user_settings aus. Diese Spalte
  wurde nur beim Aufruf der Benutzereinstellungen gefüllt; entzogene
  Berechtigungen erreichten den Digest daher unter Umständen nie.

Behoben durch Angleichen des Gruppen-Mappings und den neuen
UserIdentitySyncService, der E-Mail, Benutzername und Gruppen bei jedem
authentifizierten Request aus dem Token spiegelt – ohne den Request zu
blockieren und ohne DB-Zugriff, solange sich das Token nicht ändert. Die
doppelte Identitätspflege im UserSettingsService entfällt.

WebDAV wird nicht eingesetzt und ist entfernt; Export-Ziele bieten nur
noch FTP. Damit verschwindet das ESM-Paket webdav, an dem zwei
Jest-Suites bereits beim Parsen scheiterten.

Veraltete Tests instand gesetzt: email.controller und settings.controller
mockten weniger Abhängigkeiten, als die Klassen inzwischen haben;
postprocessing.service.spec beschrieb noch das alte Regelmodell mit
Einzelfeldern statt FilterJson und ist gegen die heutige Filter-Engine
neu geschrieben (AND/OR, verschachtelte Gruppen, Fehlerprotokollierung).

Enthält außerdem den Arbeitsstand der E-Rechnungs-Mandantenzuordnung, da
sich beide Änderungen dieselben Dateien teilen (SettingsPage, package.json).

98 Tests in 12 Suites grün, Backend- und Frontend-Build sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 00:06:50 +02:00
bjoernpoettkerandClaude Opus 4.8 095cc4bb02 fix: Warteschlange blieb stehen – Altersvergleich in die DB verlagert
Build and Push Multi-Platform Images / build-and-push (push) Successful in 32s
Der Mindest-Wartezeit-Check verglich Date.now() (Node-Uhr) mit dem aus der DB
gelesenen createdAt. Ohne gesetzte timezone (mysql2-Default 'local') wird
createdAt bei abweichender DB-Zeitzone als zukünftig interpretiert → Alter
negativ → jeder Eintrag galt als "zu jung" und wurde nie verarbeitet.

Der Vergleich läuft jetzt in der DB (createdAt <= NOW(6) - INTERVAL 5s via
TypeORM Raw), unabhängig von Zeitzone/Uhr des Node-Prozesses. Bereits
wartende Einträge werden dadurch nach dem Deploy sofort abgearbeitet.
Test-Mock bildet den DB-Alters-Filter nach.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 12:07:51 +02:00
bjoernpoettkerandClaude Sonnet 4.6 7b2a79be2a fix: Dateinamen mit Pfadtrennzeichen beim Mail-Import absichern
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
Anhänge mit '/' im Dateinamen (z.B. "Rechnung - 2026/002650322.PDF")
führten zu einem Fehler, weil '/' als Verzeichnistrenner interpretiert
wurde und der temp. Pfad nicht existierte.

- sanitizeFilename() ersetzt '/' und '\' durch '-', entfernt '..' und Null-Bytes
- Wird beim Speichern in die DB (email-download) und beim Aufbau des
  temp. Pfads (email-import) angewendet (defense-in-depth)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:10:36 +02:00
bjoernpoettkerandClaude Opus 4.8 1c70473cef feat: Mindest-Wartezeit (5000 ms) zwischen Einreihen und Verarbeitung
Build and Push Multi-Platform Images / build-and-push (push) Successful in 33s
processQueue verarbeitet den ältesten Warteschlangen-Eintrag erst, wenn er
mindestens MIN_QUEUE_AGE_MS (Default 5000 ms, ENV WEBHOOK_QUEUE_MIN_AGE_MS)
in der Tabelle lag. Ist der älteste Eintrag (FIFO) noch zu jung, bricht der
Tick ab und prüft beim nächsten Intervall erneut. Unit-Test ergänzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 11:03:10 +02:00
bjoernpoettkerandClaude Fable 5 beaa1be4a5 feat(backend): Metadaten direkt beim Paperless-Upload übergeben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
E-Mail-Import und External-Upload setzen ASN, Custom Fields (CF3/CF7/CF9),
Dokumenttyp, Tags, Owner und Belegdatum jetzt bereits beim post_document,
statt erst nachträglich über den Task-Processor-Cron (30 s + PATCH).

- Neu: deriveTaskMetadata() in task-metadata.util.ts als gemeinsame
  Ableitung (Task-Felder → Paperless-Metadaten) für beide Upload-Pfade
  und den Task-Processor – identische Parsing-Regeln, kein Copy-Paste
- Task-Processor nutzt den Helper (verhaltensidentischer Refactor) und
  patcht weiterhin idempotent nach; zuständig bleibt er für alles
  ID-Abhängige (CF8-Verknüpfungen, Anlage-Titel, Owner-Entfernung, Notizen)
- FAILURE-Zweig loggt Paperless-Fehlertext und Belegnummer vor dem
  Löschen des Tasks (ASN-Kollision kann jetzt den Consume scheitern lassen)
- Tests: 18 Unit-Tests für deriveTaskMetadata, 3 Charakterisierungstests
  pinnen den PATCH-Payload von processSuccessfulTask

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 22:46:42 +02:00
bjoernpoettkerandClaude Sonnet 4.6 7076eef57b feat: add mobile-responsive layout and card views across all pages
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
- New useIsMobile hook and MobileCardList component
- AppLayout: hamburger menu + Drawer navigation on mobile
- All list pages (Inbox, Posteingang, Manuell, Mail, Freigabe, Zahlung, TaskLog)
  show card layout on mobile instead of tables
- CSS: responsive modal height, horizontal table scroll, text-size-adjust
- Backend: Agrarmonitor polling fixes, Zahlung service improvements,
  IMAP folder service extended, misc controller fixes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 09:22:58 +02:00
bjoernpoettkerandClaude Opus 4.8 0765d14d3b fix(agrarmonitor): Karenzzeit vor "Von AM zurück"-Markierung
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Belege, die per E-Mail an Agrarmonitor gesendet werden, brauchen bis zu
10 Minuten bis zum Import in den Dateieingang. Bisher wurden sie im
Upload-Check sofort als "Manuell bearbeiten" + Tag 19 markiert, wenn sie
weder verbucht noch im Dateieingang waren – auch mitten im Import-Fenster.

Jetzt wird vor dem Markieren geprüft, ob die jüngste Notiz mit dem
konfigurierbaren Marker-Text jünger als die Wartezeit ist; falls ja, wird
der Beleg übersprungen und beim nächsten Lauf erneut geprüft. Wartezeit
(agrarmonitor_import_wartezeit_minuten, Standard 10) und Marker
(agrarmonitor_notiz_marker, Standard "Agrarmonitor") sind als Settings
konfigurierbar und in der Settings-Seite editierbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:40:59 +02:00
bjoernpoettkerandClaude Opus 4.8 156b0401b3 fix(auth): JWT nicht als API-Key prüfen (Bearer-Fallback nur für pm_-Keys)
Build and Push Multi-Platform Images / build-and-push (push) Successful in 30s
Beim JWT-Ablauf fällt der JwtOrApiKeyGuard auf den ApiKeyGuard zurück. Dieser
nahm bisher jedes Authorization-Bearer-Token als API-Key-Kandidaten – also auch
das (abgelaufene) JWT – und loggte eine irreführende "Invalid API Key"-Warnung
(samt JWT-Präfix). Der Bearer-Fallback akzeptiert nun nur noch Token mit dem
API-Key-Präfix "pm_"; ein JWT wird ignoriert. Ergebnis bleibt 401 (Frontend
re-authentifiziert), aber ohne irreführenden Log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:12:53 +02:00
bjoernpoettkerandClaude Opus 4.8 9718d6888a feat: Webhook-Warteschlange in Datenbank persistieren
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
Die Warteschlange liegt nicht mehr im Speicher, sondern in der neuen Tabelle
webhook_queue (Entity WebhookQueueItem, documentId als Primärschlüssel). So
überstehen ausstehende IDs einen Neustart und werden nach dem Boot
weiterverarbeitet.

- Neue Entity + Migration (CreateWebhookQueue); in data-source.ts/Barrel
  registriert. Dev: synchronize legt die Tabelle an; Prod: migrationsRun.
- enqueue nutzt INSERT IGNORE (atomar, race-sicher) -> Dedup über den PK.
- processQueue holt FIFO (createdAt ASC), entfernt die Zeile vor der
  Verarbeitung, arbeitet sequenziell (isProcessing-Guard).
- getStatus/queueSize lesen die DB-Anzahl. Controller awaitet enqueue.
- Unit-Test mit simuliertem FIFO-Repo: Dedup, Remove-on-start, Re-Enqueue,
  No-Parallel (deterministisch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:54:16 +02:00
bjoernpoettkerandClaude Opus 4.8 66a2cccd20 feat: Webhook-Warteschlange für sequenzielle, deduplizierte Verarbeitung
Build and Push Multi-Platform Images / build-and-push (push) Successful in 37s
Der Webhook reiht eingehende Dokument-IDs nur noch in eine Warteschlange ein
und antwortet sofort (status "queued"). Ein separater Intervall-Prozess
(WebhookQueueService) arbeitet die IDs nacheinander ab – ohne Überschneidung,
falls ein Dokument mehrfach kurz hintereinander gespeichert wird:

- Jede ID kommt nur einmal in der Liste vor (Dedup).
- Beim Verarbeitungsstart wird die ID sofort entfernt; ein erneutes Feuern
  während der Verarbeitung reiht sie wieder ein (ein weiterer Lauf folgt).
- Es läuft immer nur eine Verarbeitung gleichzeitig (isProcessing-Guard).
- Prüfintervall sehr kurz (WEBHOOK_QUEUE_INTERVAL_MS, Default 1000 ms).

Status-Recording (last_webhook_call) + GET /api/webhook/status wandern in den
Queue-Service und liefern zusätzlich die aktuelle Warteschlangen-Größe.
Frontend-Tab zeigt Status "In Warteschlange" und die Queue-Größe an.
Unit-Test deckt Dedup, Remove-on-start, Re-Enqueue und No-Parallel ab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:09:23 +02:00
bjoernpoettkerandClaude Opus 4.8 b77a283ab2 feat: Webhook verarbeitet jedes Dokument ohne Tag-16-Vorprüfung
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
processDocumentById verarbeitet nun jedes vom Paperless-Webhook gemeldete
Dokument, unabhängig vom Tag "paperlessmanager" (ID 16). Die Tag-Vorprüfung
(skipped/tag-missing) entfällt. Die Konstante PAPERLESSMANAGER_TAG_ID bleibt
für den manuellen Batch-Lauf processDocuments erhalten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 16:58:34 +02:00
bjoernpoettkerandClaude Opus 4.8 32d9e84985 feat: letzten Paperless-Webhook-Aufruf in Settings festhalten
Pro Webhook-Aufruf wird ein Status in der settings-Tabelle gepflegt
(Tag "last_webhook_call", JSON mit Zeitpunkt, Dokument, action, Ergebnis).
Neuer Endpunkt GET /api/webhook/status liefert den letzten Aufruf zum
Auslesen (JWT oder API-Key). Persistent über Neustarts, anders als die
Container-Logs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:57:36 +02:00
bjoernpoettkerandClaude Opus 4.8 56596d4482 feat: Paperless-Webhook ersetzt Tag-16-Cron-Job durch Echtzeit-Verarbeitung
Build and Push Multi-Platform Images / build-and-push (push) Successful in 42s
Statt stündlichem Cron ruft Paperless-NGX nun per Webhook das Backend auf,
sobald ein Dokument bearbeitet wurde. Der Endpunkt verarbeitet ein einzelnes
Dokument (per doc_url/document_id), prüft weiterhin den Tag "paperlessmanager"
(ID 16) und ist per API-Key (X-API-Key) abgesichert, sodass nur Paperless ihn
aufrufen kann.

- paperless-processor.service.ts: @Cron entfernt; neue Methode
  processDocumentById; Tag-16 als Konstante; gemeinsamer Helper
  processAndEvaluate (Batch + Webhook)
- paperless.module.ts: PaperlessProcessorService exportiert
- webhook.controller.ts: Route auf api/webhook (erreichbar via /api-Proxy);
  @Public() -> @UseGuards(ApiKeyGuard); ID-Extraktion aus {{doc_url}}
- webhook.module.ts: PaperlessModule + AuthModule importiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:20:19 +02:00
bjoernpoettkerandClaude Sonnet 4.6 4d05a94681 feat: implement two-step Freigabe→Zahlung workflow
Adds a payment step after document approval: PM_Freigabe approves
(Field 15 = "freigegeben"), then PM_Zahlung can mark as paid (Field 16).

- Backend: VIEW_ZAHLUNG permission mapped to PM_Zahlung OIDC group
- Backend: ZahlungModule with endpoints to list documents by filter
  (ausstehend/freigegeben/alle), set Field 16, fetch options from Paperless
- Backend: setZahlung() throws ForbiddenException if Field 15 ≠ "freigegeben"
- Frontend: /zahlung route with 3-way filter, two status columns (Freigabe + Zahlung)
- Frontend: "Zahlung verbuchen" button disabled with tooltip for non-approved docs
- Frontend: Zahlung menu item with EuroOutlined icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:29:50 +02:00
bjoernpoettkerandClaude Sonnet 4.6 969f0ae0b1 feat: verarbeitete/ignorierte E-Mails beim Prüfen in IMAP-Ordner verschieben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
- Cleanup-Cron von EmailDownloadService in ImapFolderService verschoben,
  damit er auch aus EmailController aufrufbar ist (zirkuläre Abhängigkeit vermieden)
- Beim Klick auf „Anhänge prüfen" wird der IMAP-Cleanup fire-and-forget gestartet
- Beim Klick auf „Bereits verarbeitete Anhänge prüfen" werden zusätzlich alle
  E-Mails im IMAP-Posteingang, die in der DB als verarbeitet (Status 1) oder
  ignoriert (Status 3) markiert sind, in den Ordner „importiert" verschoben
- Erfolgsmeldung zeigt Anzahl verschobener E-Mails an

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:04:03 +02:00
bjoernpoettkerandClaude Opus 4.8 41eed1871e fix: Produktions-Crash durch TypeORM-synchronize beheben
Build and Push Multi-Platform Images / build-and-push (push) Successful in 44s
NODE_ENV=production deaktiviert synchronize (zerstörerischer ADD/DROP-COLUMN-
Churn auf MariaDB, der die 8126-Byte-Zeilengröße sprengte) und aktiviert
migrationsRun. Neue data-source.ts als einzige Konfigquelle (Laufzeit + CLI),
Migrations-Workflow (generate/run/revert) inkl. dotenv ergänzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:27:04 +02:00