diff --git a/paperless-backend/src/email/email-import.service.spec.ts b/paperless-backend/src/email/email-import.service.spec.ts index 1972b85..0da63cc 100644 --- a/paperless-backend/src/email/email-import.service.spec.ts +++ b/paperless-backend/src/email/email-import.service.spec.ts @@ -14,6 +14,7 @@ import { PdfService } from '../preprocessing/pdf.service'; import { EmailPageCacheService } from './email-page-cache.service'; import { ImapFolderService } from './imap-folder.service'; import { AttachmentFingerprintService } from './attachment-fingerprint.service'; +import { DokumentVersionService } from '../paperless/dokument-version.service'; async function pdfMitSeiten(anzahl: number): Promise { const doc = await PDFDocument.create(); @@ -29,11 +30,19 @@ describe('EmailImportService – Duplikatprüfung', () => { andWhere: jest.Mock; getCount: jest.Mock; }; - let attachmentRepo: { findOne: jest.Mock }; + let attachmentRepo: { findOne: jest.Mock; save: jest.Mock }; + let taskRepo: { + createQueryBuilder: jest.Mock; + create: jest.Mock; + save: jest.Mock; + }; + let emailRepo: { update: jest.Mock; findOne: jest.Mock }; + let dokumentVersion: { haengeVersionAn: jest.Mock }; let contentRepo: { findOne: jest.Mock }; let paperlessService: { checksumExists: jest.Mock; uploadDocument: jest.Mock; + validateAsnNotExists: jest.Mock; }; let fingerprints: { ensure: jest.Mock; hasImportedTwin: jest.Mock }; @@ -50,11 +59,25 @@ describe('EmailImportService – Duplikatprüfung', () => { Checksum: 'abc123', FileName: 'rechnung.pdf', }), + save: jest.fn().mockImplementation((a: unknown) => Promise.resolve(a)), + }; + taskRepo = { + createQueryBuilder: jest.fn().mockReturnValue(taskQuery), + create: jest.fn().mockImplementation((t: unknown) => t), + save: jest.fn(), + }; + emailRepo = { + update: jest.fn().mockResolvedValue({}), + findOne: jest.fn().mockResolvedValue(null), + }; + dokumentVersion = { + haengeVersionAn: jest.fn().mockResolvedValue(undefined), }; contentRepo = { findOne: jest.fn() }; paperlessService = { checksumExists: jest.fn().mockResolvedValue(false), uploadDocument: jest.fn(), + validateAsnNotExists: jest.fn(), }; fingerprints = { ensure: jest.fn().mockResolvedValue(null), @@ -65,24 +88,20 @@ describe('EmailImportService – Duplikatprüfung', () => { providers: [ EmailImportService, { provide: ConfigService, useValue: { get: jest.fn() } }, - { provide: getRepositoryToken(Email), useValue: {} }, + { provide: getRepositoryToken(Email), useValue: emailRepo }, { provide: getRepositoryToken(Attachment), useValue: attachmentRepo }, { provide: getRepositoryToken(Content), useValue: contentRepo }, { provide: getRepositoryToken(CorrespondentEmailMapping), useValue: {}, }, - { - provide: getRepositoryToken(Task), - useValue: { - createQueryBuilder: jest.fn().mockReturnValue(taskQuery), - }, - }, + { provide: getRepositoryToken(Task), useValue: taskRepo }, { provide: PaperlessService, useValue: paperlessService }, { provide: PdfService, useValue: {} }, { provide: EmailPageCacheService, useValue: {} }, { provide: ImapFolderService, useValue: {} }, { provide: AttachmentFingerprintService, useValue: fingerprints }, + { provide: DokumentVersionService, useValue: dokumentVersion }, ], }).compile(); @@ -189,6 +208,48 @@ describe('EmailImportService – Duplikatprüfung', () => { }); }); + describe('Import als Version', () => { + it('hängt den Beleg als Version an, statt ein neues Dokument anzulegen', async () => { + contentRepo.findOne.mockResolvedValue({ + Content1: await pdfMitSeiten(1), + }); + + await service.executeImport({ + emailDate: '2026-09-25', + attachments: [ + { + attachmentId: 7, + type: 'MAIN', + belegnummer: '2026-000123', + barcode: { + x: 0, + y: 0, + nummer: '000123', + datum: '2026-09-25', + jahr: '2026', + }, + versionZuDokumentId: 42, + }, + ], + }); + + expect(dokumentVersion.haengeVersionAn).toHaveBeenCalledTimes(1); + expect(dokumentVersion.haengeVersionAn).toHaveBeenCalledWith( + 42, + expect.stringMatching(/\.pdf$/), + ); + expect(paperlessService.uploadDocument).not.toHaveBeenCalled(); + expect(paperlessService.validateAsnNotExists).not.toHaveBeenCalled(); + expect(taskRepo.save).not.toHaveBeenCalled(); + expect(attachmentRepo.save).toHaveBeenCalledWith( + expect.objectContaining({ + PaperlessDocumentIds: { full: 42 }, + ImportStatus: 1, + }), + ); + }); + }); + describe('executeImport', () => { const importDaten = (splitRanges?: { start: number; end: number }[]) => ({ emailDate: '2026-09-25', diff --git a/paperless-backend/src/email/email-import.service.ts b/paperless-backend/src/email/email-import.service.ts index 2d617a2..a181bf5 100644 --- a/paperless-backend/src/email/email-import.service.ts +++ b/paperless-backend/src/email/email-import.service.ts @@ -21,6 +21,7 @@ import * as QRCode from 'qrcode'; import { EmailPageCacheService } from './email-page-cache.service'; import { ImapFolderService } from './imap-folder.service'; import { AttachmentFingerprintService } from './attachment-fingerprint.service'; +import { DokumentVersionService } from '../paperless/dokument-version.service'; import { PdfService } from '../preprocessing/pdf.service'; import * as path from 'path'; import * as os from 'os'; @@ -73,6 +74,7 @@ export class EmailImportService { private readonly pageCache: EmailPageCacheService, private readonly imapFolderService: ImapFolderService, private readonly fingerprints: AttachmentFingerprintService, + private readonly dokumentVersion: DokumentVersionService, ) {} async ensurePreviews(emailId: number): Promise { @@ -568,6 +570,8 @@ export class EmailImportService { jahr: string; }; belegnummer?: string; + /** Stammdokument in Paperless, an das der Beleg als neue Version kommt */ + versionZuDokumentId?: number | null; }[]; emailDate: string; }): Promise<{ success: boolean; results: any[] }> { @@ -634,6 +638,26 @@ export class EmailImportService { } } + /** + * Hängt jede Upload-Datei als neue Version an das Stammdokument. Kein Task, + * keine Belegnummer-Bestätigung – Metadaten des Dokuments bleiben unverändert. + */ + private async importiereAlsVersion( + documentId: number, + uploadItems: { path: string; filename: string; rangeKey: string }[], + paperlessIds: Record, + jobId?: string, + ): Promise { + for (const item of uploadItems) { + this.setJobStatus( + jobId, + `Hänge ${item.filename} als neue Version an Dokument ${documentId}...`, + ); + await this.dokumentVersion.haengeVersionAn(documentId, item.path); + paperlessIds[item.rangeKey] = documentId; + } + } + private async runImport(data: { jobId?: string; attachments: { @@ -650,6 +674,8 @@ export class EmailImportService { jahr: string; }; belegnummer?: string; + /** Stammdokument in Paperless, an das der Beleg als neue Version kommt */ + versionZuDokumentId?: number | null; }[]; emailDate: string; }): Promise<{ success: boolean; results: any[] }> { @@ -677,7 +703,7 @@ export class EmailImportService { const baseFilename = sanitizeFilename( attachmentEntity.FileName.replace(/\.pdf$/i, ''), ); - const paperlessIds: any = {}; + const paperlessIds: Record = {}; const uploadPromises = []; @@ -735,6 +761,28 @@ export class EmailImportService { }); } + // Vom Benutzer bestätigt: als neue Version an das vorhandene Dokument + if (att.versionZuDokumentId) { + await this.importiereAlsVersion( + att.versionZuDokumentId, + uploadPromises, + paperlessIds, + data.jobId, + ); + attachmentEntity.PaperlessDocumentIds = paperlessIds; + attachmentEntity.ImportStatus = 1; + if (att.belegnummer) { + attachmentEntity.InterneBelegnummer = att.belegnummer; + } + await this.attachmentRepo.save(attachmentEntity); + results.push({ + attachmentId: att.attachmentId, + paperlessIds, + alsVersion: true, + }); + continue; + } + // 0. Check if ASN already exists if (att.belegnummer) { await this.paperlessService.validateAsnNotExists(att.belegnummer); diff --git a/paperless-frontend/src/api/email-import.ts b/paperless-frontend/src/api/email-import.ts index a12ce91..652e47d 100644 --- a/paperless-frontend/src/api/email-import.ts +++ b/paperless-frontend/src/api/email-import.ts @@ -20,6 +20,8 @@ export interface AttachmentImportData { barcode?: { x: number; y: number; nummer: string; datum: string; jahr: string }; belegnummer?: string; isDuplicate?: boolean; + /** Stammdokument in Paperless, an das der Beleg als neue Version kommt */ + versionZuDokumentId?: number | null; } export const emailImportApi = {