feat(mail): Beleg beim Import als neue Version anhängen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bdc85f5f1f
commit
21a447e915
3 files changed
+120
-9
No files matched your search
@@ -14,6 +14,7 @@ import { PdfService } from '../preprocessing/pdf.service';
|
|||||||
import { EmailPageCacheService } from './email-page-cache.service';
|
import { EmailPageCacheService } from './email-page-cache.service';
|
||||||
import { ImapFolderService } from './imap-folder.service';
|
import { ImapFolderService } from './imap-folder.service';
|
||||||
import { AttachmentFingerprintService } from './attachment-fingerprint.service';
|
import { AttachmentFingerprintService } from './attachment-fingerprint.service';
|
||||||
|
import { DokumentVersionService } from '../paperless/dokument-version.service';
|
||||||
|
|
||||||
async function pdfMitSeiten(anzahl: number): Promise<Buffer> {
|
async function pdfMitSeiten(anzahl: number): Promise<Buffer> {
|
||||||
const doc = await PDFDocument.create();
|
const doc = await PDFDocument.create();
|
||||||
@@ -29,11 +30,19 @@ describe('EmailImportService – Duplikatprüfung', () => {
|
|||||||
andWhere: jest.Mock;
|
andWhere: jest.Mock;
|
||||||
getCount: 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 contentRepo: { findOne: jest.Mock };
|
||||||
let paperlessService: {
|
let paperlessService: {
|
||||||
checksumExists: jest.Mock;
|
checksumExists: jest.Mock;
|
||||||
uploadDocument: jest.Mock;
|
uploadDocument: jest.Mock;
|
||||||
|
validateAsnNotExists: jest.Mock;
|
||||||
};
|
};
|
||||||
let fingerprints: { ensure: jest.Mock; hasImportedTwin: jest.Mock };
|
let fingerprints: { ensure: jest.Mock; hasImportedTwin: jest.Mock };
|
||||||
|
|
||||||
@@ -50,11 +59,25 @@ describe('EmailImportService – Duplikatprüfung', () => {
|
|||||||
Checksum: 'abc123',
|
Checksum: 'abc123',
|
||||||
FileName: 'rechnung.pdf',
|
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() };
|
contentRepo = { findOne: jest.fn() };
|
||||||
paperlessService = {
|
paperlessService = {
|
||||||
checksumExists: jest.fn().mockResolvedValue(false),
|
checksumExists: jest.fn().mockResolvedValue(false),
|
||||||
uploadDocument: jest.fn(),
|
uploadDocument: jest.fn(),
|
||||||
|
validateAsnNotExists: jest.fn(),
|
||||||
};
|
};
|
||||||
fingerprints = {
|
fingerprints = {
|
||||||
ensure: jest.fn().mockResolvedValue(null),
|
ensure: jest.fn().mockResolvedValue(null),
|
||||||
@@ -65,24 +88,20 @@ describe('EmailImportService – Duplikatprüfung', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
EmailImportService,
|
EmailImportService,
|
||||||
{ provide: ConfigService, useValue: { get: jest.fn() } },
|
{ provide: ConfigService, useValue: { get: jest.fn() } },
|
||||||
{ provide: getRepositoryToken(Email), useValue: {} },
|
{ provide: getRepositoryToken(Email), useValue: emailRepo },
|
||||||
{ provide: getRepositoryToken(Attachment), useValue: attachmentRepo },
|
{ provide: getRepositoryToken(Attachment), useValue: attachmentRepo },
|
||||||
{ provide: getRepositoryToken(Content), useValue: contentRepo },
|
{ provide: getRepositoryToken(Content), useValue: contentRepo },
|
||||||
{
|
{
|
||||||
provide: getRepositoryToken(CorrespondentEmailMapping),
|
provide: getRepositoryToken(CorrespondentEmailMapping),
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{ provide: getRepositoryToken(Task), useValue: taskRepo },
|
||||||
provide: getRepositoryToken(Task),
|
|
||||||
useValue: {
|
|
||||||
createQueryBuilder: jest.fn().mockReturnValue(taskQuery),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ provide: PaperlessService, useValue: paperlessService },
|
{ provide: PaperlessService, useValue: paperlessService },
|
||||||
{ provide: PdfService, useValue: {} },
|
{ provide: PdfService, useValue: {} },
|
||||||
{ provide: EmailPageCacheService, useValue: {} },
|
{ provide: EmailPageCacheService, useValue: {} },
|
||||||
{ provide: ImapFolderService, useValue: {} },
|
{ provide: ImapFolderService, useValue: {} },
|
||||||
{ provide: AttachmentFingerprintService, useValue: fingerprints },
|
{ provide: AttachmentFingerprintService, useValue: fingerprints },
|
||||||
|
{ provide: DokumentVersionService, useValue: dokumentVersion },
|
||||||
],
|
],
|
||||||
}).compile();
|
}).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', () => {
|
describe('executeImport', () => {
|
||||||
const importDaten = (splitRanges?: { start: number; end: number }[]) => ({
|
const importDaten = (splitRanges?: { start: number; end: number }[]) => ({
|
||||||
emailDate: '2026-09-25',
|
emailDate: '2026-09-25',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import * as QRCode from 'qrcode';
|
|||||||
import { EmailPageCacheService } from './email-page-cache.service';
|
import { EmailPageCacheService } from './email-page-cache.service';
|
||||||
import { ImapFolderService } from './imap-folder.service';
|
import { ImapFolderService } from './imap-folder.service';
|
||||||
import { AttachmentFingerprintService } from './attachment-fingerprint.service';
|
import { AttachmentFingerprintService } from './attachment-fingerprint.service';
|
||||||
|
import { DokumentVersionService } from '../paperless/dokument-version.service';
|
||||||
import { PdfService } from '../preprocessing/pdf.service';
|
import { PdfService } from '../preprocessing/pdf.service';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
@@ -73,6 +74,7 @@ export class EmailImportService {
|
|||||||
private readonly pageCache: EmailPageCacheService,
|
private readonly pageCache: EmailPageCacheService,
|
||||||
private readonly imapFolderService: ImapFolderService,
|
private readonly imapFolderService: ImapFolderService,
|
||||||
private readonly fingerprints: AttachmentFingerprintService,
|
private readonly fingerprints: AttachmentFingerprintService,
|
||||||
|
private readonly dokumentVersion: DokumentVersionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ensurePreviews(emailId: number): Promise<void> {
|
async ensurePreviews(emailId: number): Promise<void> {
|
||||||
@@ -568,6 +570,8 @@ export class EmailImportService {
|
|||||||
jahr: string;
|
jahr: string;
|
||||||
};
|
};
|
||||||
belegnummer?: string;
|
belegnummer?: string;
|
||||||
|
/** Stammdokument in Paperless, an das der Beleg als neue Version kommt */
|
||||||
|
versionZuDokumentId?: number | null;
|
||||||
}[];
|
}[];
|
||||||
emailDate: string;
|
emailDate: string;
|
||||||
}): Promise<{ success: boolean; results: any[] }> {
|
}): 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<string, number>,
|
||||||
|
jobId?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
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: {
|
private async runImport(data: {
|
||||||
jobId?: string;
|
jobId?: string;
|
||||||
attachments: {
|
attachments: {
|
||||||
@@ -650,6 +674,8 @@ export class EmailImportService {
|
|||||||
jahr: string;
|
jahr: string;
|
||||||
};
|
};
|
||||||
belegnummer?: string;
|
belegnummer?: string;
|
||||||
|
/** Stammdokument in Paperless, an das der Beleg als neue Version kommt */
|
||||||
|
versionZuDokumentId?: number | null;
|
||||||
}[];
|
}[];
|
||||||
emailDate: string;
|
emailDate: string;
|
||||||
}): Promise<{ success: boolean; results: any[] }> {
|
}): Promise<{ success: boolean; results: any[] }> {
|
||||||
@@ -677,7 +703,7 @@ export class EmailImportService {
|
|||||||
const baseFilename = sanitizeFilename(
|
const baseFilename = sanitizeFilename(
|
||||||
attachmentEntity.FileName.replace(/\.pdf$/i, ''),
|
attachmentEntity.FileName.replace(/\.pdf$/i, ''),
|
||||||
);
|
);
|
||||||
const paperlessIds: any = {};
|
const paperlessIds: Record<string, number> = {};
|
||||||
|
|
||||||
const uploadPromises = [];
|
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
|
// 0. Check if ASN already exists
|
||||||
if (att.belegnummer) {
|
if (att.belegnummer) {
|
||||||
await this.paperlessService.validateAsnNotExists(att.belegnummer);
|
await this.paperlessService.validateAsnNotExists(att.belegnummer);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface AttachmentImportData {
|
|||||||
barcode?: { x: number; y: number; nummer: string; datum: string; jahr: string };
|
barcode?: { x: number; y: number; nummer: string; datum: string; jahr: string };
|
||||||
belegnummer?: string;
|
belegnummer?: string;
|
||||||
isDuplicate?: boolean;
|
isDuplicate?: boolean;
|
||||||
|
/** Stammdokument in Paperless, an das der Beleg als neue Version kommt */
|
||||||
|
versionZuDokumentId?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emailImportApi = {
|
export const emailImportApi = {
|
||||||
|
|||||||
Reference in new issue
Block a user