From 0765d14d3b1a14c7317ccf1eb5c1bbd7fcade60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20P=C3=B6ttker?= Date: Fri, 3 Jul 2026 12:40:59 +0200 Subject: [PATCH] =?UTF-8?q?fix(agrarmonitor):=20Karenzzeit=20vor=20"Von=20?= =?UTF-8?q?AM=20zur=C3=BCck"-Markierung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../agrarmonitor-polling.service.ts | 77 ++++++++++++++++++- .../agrarmonitor/agrarmonitor.controller.ts | 4 + .../src/paperless/paperless.service.ts | 8 ++ paperless-frontend/src/api/settings.ts | 2 + paperless-frontend/src/pages/SettingsPage.tsx | 14 ++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts b/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts index 9e0cdab..669c4ee 100644 --- a/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts +++ b/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts @@ -56,6 +56,8 @@ export class AgrarmonitorPollingService implements OnModuleInit { await this.upsertSetting('agrarmonitor_tag_hochgeladen', ''); await this.upsertSetting('agrarmonitor_link_field', ''); await this.upsertSetting('agrarmonitor_tag_manuell', ''); + await this.upsertSetting('agrarmonitor_import_wartezeit_minuten', '10'); + await this.upsertSetting('agrarmonitor_notiz_marker', 'Agrarmonitor'); } @Cron(process.env['AGRARMONITOR_POLLING_CRON'] || '0 */30 * * * *') @@ -80,14 +82,20 @@ export class AgrarmonitorPollingService implements OnModuleInit { tagHochgeladen: string; linkField: string; tagManuell: string; + importWartezeitMinuten: string; + notizMarker: string; }> { - const [fertig, verbucht, hochgeladen, linkField, manuell] = + const [fertig, verbucht, hochgeladen, linkField, manuell, wartezeit, marker] = await Promise.all([ this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_verbucht' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_manuell' }), + this.settingRepo.findOneBy({ + Tag: 'agrarmonitor_import_wartezeit_minuten', + }), + this.settingRepo.findOneBy({ Tag: 'agrarmonitor_notiz_marker' }), ]); return { tagFertig: fertig?.Wert ?? '4', @@ -95,6 +103,8 @@ export class AgrarmonitorPollingService implements OnModuleInit { tagHochgeladen: hochgeladen?.Wert ?? '', linkField: linkField?.Wert ?? '', tagManuell: manuell?.Wert ?? '', + importWartezeitMinuten: wartezeit?.Wert ?? '10', + notizMarker: marker?.Wert ?? 'Agrarmonitor', }; } @@ -104,12 +114,16 @@ export class AgrarmonitorPollingService implements OnModuleInit { tagHochgeladen: string, linkField: string, tagManuell: string, + importWartezeitMinuten: string, + notizMarker: string, ): Promise<{ tagFertig: string; tagVerbucht: string; tagHochgeladen: string; linkField: string; tagManuell: string; + importWartezeitMinuten: string; + notizMarker: string; }> { await Promise.all([ this.settingRepo.update( @@ -132,8 +146,24 @@ export class AgrarmonitorPollingService implements OnModuleInit { { Tag: 'agrarmonitor_tag_manuell' }, { Wert: tagManuell }, ), + this.settingRepo.update( + { Tag: 'agrarmonitor_import_wartezeit_minuten' }, + { Wert: importWartezeitMinuten }, + ), + this.settingRepo.update( + { Tag: 'agrarmonitor_notiz_marker' }, + { Wert: notizMarker }, + ), ]); - return { tagFertig, tagVerbucht, tagHochgeladen, linkField, tagManuell }; + return { + tagFertig, + tagVerbucht, + tagHochgeladen, + linkField, + tagManuell, + importWartezeitMinuten, + notizMarker, + }; } async runPolling(): Promise { @@ -393,17 +423,26 @@ export class AgrarmonitorPollingService implements OnModuleInit { fertigSetting, linkFieldSetting, manuellSetting, + wartezeitSetting, + markerSetting, ] = await Promise.all([ this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }), this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_manuell' }), + this.settingRepo.findOneBy({ + Tag: 'agrarmonitor_import_wartezeit_minuten', + }), + this.settingRepo.findOneBy({ Tag: 'agrarmonitor_notiz_marker' }), ]); const tagHochgeladenId = parseInt(hochgeladenSetting?.Wert ?? '', 10); const tagFertigId = parseInt(fertigSetting?.Wert ?? '4', 10); const linkFieldId = parseInt(linkFieldSetting?.Wert ?? '', 10); const tagManuellId = parseInt(manuellSetting?.Wert ?? '', 10); + const importWartezeitMinuten = + parseInt(wartezeitSetting?.Wert ?? '10', 10) || 10; + const notizMarker = (markerSetting?.Wert ?? 'Agrarmonitor').trim(); if (isNaN(tagHochgeladenId)) { this.logger.warn( @@ -512,6 +551,40 @@ export class AgrarmonitorPollingService implements OnModuleInit { continue; } + // Karenzzeit: evtl. hat Agrarmonitor die E-Mail noch nicht importiert. + // Ist die Sende-Notiz jünger als die konfigurierte Wartezeit, nicht markieren. + const marker = notizMarker.toLowerCase(); + if (marker) { + let notes: Array<{ note: string; created: string }>; + try { + notes = await this.paperlessService.getNotes(doc.id as number); + } catch (err: unknown) { + // Vorsichtig: bei Notiz-Abruf-Fehler NICHT markieren, nächster Lauf prüft erneut + this.logger.warn( + `${interneBelegnummer}: Notiz-Abruf fehlgeschlagen — nicht markiert: ${err instanceof Error ? err.message : err}`, + ); + result.skipped++; + await this.delay(500); + continue; + } + const newest = notes + .filter((n) => (n.note ?? '').toLowerCase().includes(marker)) + .map((n) => new Date(n.created).getTime()) + .filter((t) => !isNaN(t)) + .reduce((max, t) => Math.max(max, t), 0); + if ( + newest > 0 && + Date.now() - newest < importWartezeitMinuten * 60 * 1000 + ) { + this.logger.log( + `${interneBelegnummer}: Sende-Notiz jünger als ${importWartezeitMinuten} Min — warte auf Import`, + ); + result.skipped++; + await this.delay(500); + continue; + } + } + // Weder verbucht noch im Dateieingang → Tags "Manuell bearbeiten" + "Von AM zurück" setzen if (!isNaN(tagManuellId)) { const currentTags: number[] = (doc.tags as number[]) ?? []; diff --git a/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts b/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts index 5d5cfef..c98f6f8 100644 --- a/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts +++ b/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts @@ -42,6 +42,8 @@ export class AgrarmonitorController { tagHochgeladen: string; linkField: string; tagManuell: string; + importWartezeitMinuten: string; + notizMarker: string; }, ) { return this.pollingService.updatePollingConfig( @@ -50,6 +52,8 @@ export class AgrarmonitorController { body.tagHochgeladen, body.linkField, body.tagManuell ?? '', + body.importWartezeitMinuten ?? '10', + body.notizMarker ?? 'Agrarmonitor', ); } diff --git a/paperless-backend/src/paperless/paperless.service.ts b/paperless-backend/src/paperless/paperless.service.ts index 4a7b33d..bb9cfeb 100644 --- a/paperless-backend/src/paperless/paperless.service.ts +++ b/paperless-backend/src/paperless/paperless.service.ts @@ -250,6 +250,14 @@ export class PaperlessService { return response.data; } + async getNotes( + id: number, + ): Promise> { + const response = await this.client.get(`/documents/${id}/notes/`); + const data = response.data; + return Array.isArray(data) ? data : (data?.results ?? []); + } + async checksumExists(checksum: string): Promise { const response = await this.client.get('/documents/', { params: { checksum__iexact: checksum }, diff --git a/paperless-frontend/src/api/settings.ts b/paperless-frontend/src/api/settings.ts index 89a12d5..a9c8124 100644 --- a/paperless-frontend/src/api/settings.ts +++ b/paperless-frontend/src/api/settings.ts @@ -208,6 +208,8 @@ export interface AgrarmonitorPollingConfig { tagHochgeladen: string; linkField: string; tagManuell: string; + importWartezeitMinuten: string; + notizMarker: string; } export interface AgrarmonitorPollingResult { diff --git a/paperless-frontend/src/pages/SettingsPage.tsx b/paperless-frontend/src/pages/SettingsPage.tsx index 4ac127c..3162f2a 100644 --- a/paperless-frontend/src/pages/SettingsPage.tsx +++ b/paperless-frontend/src/pages/SettingsPage.tsx @@ -2600,6 +2600,20 @@ function AgrarmonitorTab() { ))} + + + + + +