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>
This commit is contained in:
2026-07-14 22:46:42 +02:00
parent 57c8964384
commit beaa1be4a5
6 changed files with 517 additions and 113 deletions
@@ -10,6 +10,7 @@ import { Content } from '../database/entities/content.entity';
import { CorrespondentEmailMapping } from '../database/entities/correspondent-email-mapping.entity';
import { Task } from '../database/entities/task.entity';
import { PaperlessService } from '../paperless/paperless.service';
import { deriveTaskMetadata } from '../paperless/task-metadata.util';
import * as QRCode from 'qrcode';
import { EmailPageCacheService } from './email-page-cache.service';
import { ImapFolderService } from './imap-folder.service';
@@ -557,13 +558,29 @@ export class EmailImportService {
// Upload all generated PDFs
for (const uploadItem of uploadPromises) {
// Einmal definieren, zweimal verwenden: Upload-Metadaten und Task
// tragen garantiert dieselben Werte
const taskFields = {
InterneBelegnummer: att.belegnummer || '',
Eingangsdatum: att.barcode?.datum
? new Date(att.barcode.datum)
: createdDate,
Belegdatum: createdDate,
DocumentType: att.type === 'MAIN' ? null : 5, // 5 = Anlage
BetriebID: null, // Owner-Entfernung erledigt der Task-Processor
};
// Metadaten direkt beim Upload mitgeben; der Task-Processor patcht
// später idempotent nach (Sicherheitsnetz)
const derived = deriveTaskMetadata(taskFields);
const options: any = {
filename: uploadItem.filename,
title: att.belegnummer
? `Beleg ${att.belegnummer}`
: uploadItem.filename,
created: createdDate,
owner: null,
documentType: derived.documentType,
archiveSerialNumber: derived.archiveSerialNumber,
customFields: derived.customFields,
};
if (att.paperlessCorrespondentId)
options.correspondent = att.paperlessCorrespondentId;
@@ -577,15 +594,9 @@ export class EmailImportService {
// Create background task for enrichment (same logic as Inbox)
const backgroundTask = this.taskRepo.create({
TaskId: paperlessTaskId,
InterneBelegnummer: att.belegnummer || '',
Eingangsdatum: att.barcode?.datum
? new Date(att.barcode.datum)
: createdDate,
Belegdatum: createdDate,
...taskFields,
BarcodeJson: att.barcode ? JSON.stringify(att.barcode) : null,
BetriebID: null, // Owner
Fertig: 0,
DocumentType: att.type === 'MAIN' ? null : 5, // 5 = Anlage
SourceAttachmentID: att.attachmentId,
SourceAttachmentRange: uploadItem.rangeKey,
});