feat: implement checksum-based duplicate detection for split email attachments
Build and Push Multi-Platform Images / build-and-push (push) Successful in 33s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 33s
This commit is contained in:
@@ -67,6 +67,17 @@ export class EmailImportController {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Split Checksum Check ---
|
||||||
|
@Post('attachments/:attachmentId/check-split-checksum')
|
||||||
|
@RequirePermissions(Permission.VIEW_MAIL)
|
||||||
|
async checkSplitChecksum(
|
||||||
|
@Param('attachmentId') attachmentId: number,
|
||||||
|
@Body() body: { pages: { start: number; end: number } },
|
||||||
|
) {
|
||||||
|
const isDuplicate = await this.importService.checkSplitChecksum(attachmentId, body.pages);
|
||||||
|
return { isDuplicate };
|
||||||
|
}
|
||||||
|
|
||||||
// --- Print Preview ---
|
// --- Print Preview ---
|
||||||
@Post('attachments/:attachmentId/print-preview')
|
@Post('attachments/:attachmentId/print-preview')
|
||||||
@RequirePermissions(Permission.VIEW_MAIL)
|
@RequirePermissions(Permission.VIEW_MAIL)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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';
|
||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailImportService {
|
export class EmailImportService {
|
||||||
@@ -154,6 +155,25 @@ export class EmailImportService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Checksum Check for Split Documents ---
|
||||||
|
async checkSplitChecksum(attachmentId: number, pages: { start: number; end: number }): Promise<boolean> {
|
||||||
|
const content = await this.contentRepo.findOne({ where: { AttachmentEntityId: attachmentId } });
|
||||||
|
if (!content) return false;
|
||||||
|
|
||||||
|
const pdfDoc = await PDFDocument.load(content.Content1, { ignoreEncryption: true });
|
||||||
|
const total = pdfDoc.getPageCount();
|
||||||
|
const startIdx = Math.max(1, pages.start) - 1;
|
||||||
|
const endIdx = Math.min(pages.end === 999 ? total : pages.end, total) - 1;
|
||||||
|
|
||||||
|
const sliced = await PDFDocument.create();
|
||||||
|
const indices = Array.from({ length: endIdx - startIdx + 1 }, (_, i) => startIdx + i);
|
||||||
|
const copied = await sliced.copyPages(pdfDoc, indices);
|
||||||
|
copied.forEach(p => sliced.addPage(p));
|
||||||
|
|
||||||
|
const checksum = crypto.createHash('md5').update(Buffer.from(await sliced.save())).digest('hex');
|
||||||
|
return this.paperlessService.checksumExists(checksum);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Print Preview ---
|
// --- Print Preview ---
|
||||||
async generatePrintPdf(attachmentId: number, barcodeData: any): Promise<Buffer> {
|
async generatePrintPdf(attachmentId: number, barcodeData: any): Promise<Buffer> {
|
||||||
const content = await this.contentRepo.findOne({ where: { AttachmentEntityId: attachmentId } });
|
const content = await this.contentRepo.findOne({ where: { AttachmentEntityId: attachmentId } });
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ export const emailImportApi = {
|
|||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
checkSplitChecksum: async (attachmentId: number, pages: { start: number; end: number }): Promise<boolean> => {
|
||||||
|
const res = await api.post<{ isDuplicate: boolean }>(`/api/email-import/attachments/${attachmentId}/check-split-checksum`, { pages });
|
||||||
|
return res.data.isDuplicate;
|
||||||
|
},
|
||||||
|
|
||||||
executeImport: async (emailDate: string, attachments: AttachmentImportData[]): Promise<{ success: boolean; results: any[] }> => {
|
executeImport: async (emailDate: string, attachments: AttachmentImportData[]): Promise<{ success: boolean; results: any[] }> => {
|
||||||
const res = await api.post('/api/email-import/execute', { emailDate, attachments });
|
const res = await api.post('/api/email-import/execute', { emailDate, attachments });
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|||||||
@@ -131,19 +131,20 @@ export default function MailImportWizard({ visible, onClose, email, attachments
|
|||||||
setImportData(prev => prev.map(item => item.virtualId === virtualId ? { ...item, [key]: value } : item));
|
setImportData(prev => prev.map(item => item.virtualId === virtualId ? { ...item, [key]: value } : item));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSplit = (virtualId: string, splitPage: number) => {
|
const handleSplit = async (virtualId: string, splitPage: number) => {
|
||||||
setImportData(prev => {
|
const idx = importData.findIndex(i => i.virtualId === virtualId);
|
||||||
const idx = prev.findIndex(i => i.virtualId === virtualId);
|
if (idx === -1) return;
|
||||||
if (idx === -1) return prev;
|
|
||||||
|
|
||||||
const itemToSplit = prev[idx];
|
const itemToSplit = importData[idx];
|
||||||
const start = itemToSplit.pages?.start || 1;
|
const start = itemToSplit.pages?.start || 1;
|
||||||
const end = itemToSplit.pages?.end || 999; // 999 means to the end
|
const end = itemToSplit.pages?.end || 999;
|
||||||
|
|
||||||
const part1 = { ...itemToSplit, virtualId: `${itemToSplit.attachmentId}_${start}_${splitPage}`, pages: { start, end: splitPage }, fileName: `${itemToSplit.fileName} (Teil 1)` };
|
const part1Pages = { start, end: splitPage };
|
||||||
const part2 = { ...itemToSplit, virtualId: `${itemToSplit.attachmentId}_${splitPage+1}_${end}`, pages: { start: splitPage + 1, end }, fileName: `${itemToSplit.fileName} (Teil 2)` };
|
const part2Pages = { start: splitPage + 1, end };
|
||||||
|
|
||||||
|
const part1 = { ...itemToSplit, virtualId: `${itemToSplit.attachmentId}_${start}_${splitPage}`, pages: part1Pages, fileName: `${itemToSplit.fileName} (Teil 1)` };
|
||||||
|
const part2 = { ...itemToSplit, virtualId: `${itemToSplit.attachmentId}_${splitPage+1}_${end}`, pages: part2Pages, fileName: `${itemToSplit.fileName} (Teil 2)` };
|
||||||
|
|
||||||
// Propagate date and barcode
|
|
||||||
const parentDate = eingangsdaten[virtualId] || dayjs(email.Date);
|
const parentDate = eingangsdaten[virtualId] || dayjs(email.Date);
|
||||||
const parentBarcode = barcodes[virtualId];
|
const parentBarcode = barcodes[virtualId];
|
||||||
|
|
||||||
@@ -161,10 +162,28 @@ export default function MailImportWizard({ visible, onClose, email, attachments
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setImportData(prev => {
|
||||||
const newArray = [...prev];
|
const newArray = [...prev];
|
||||||
newArray.splice(idx, 1, part1, part2);
|
newArray.splice(idx, 1, part1, part2);
|
||||||
return newArray;
|
return newArray;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Checksumme der geteilten Teile prüfen
|
||||||
|
try {
|
||||||
|
const [dup1, dup2] = await Promise.all([
|
||||||
|
emailImportApi.checkSplitChecksum(itemToSplit.attachmentId, part1Pages),
|
||||||
|
emailImportApi.checkSplitChecksum(itemToSplit.attachmentId, part2Pages),
|
||||||
|
]);
|
||||||
|
if (dup1 || dup2) {
|
||||||
|
setImportData(prev => prev.map(item => {
|
||||||
|
if (item.virtualId === part1.virtualId && dup1) return { ...item, isDuplicate: true, type: 'IGNORE' as const };
|
||||||
|
if (item.virtualId === part2.virtualId && dup2) return { ...item, isDuplicate: true, type: 'IGNORE' as const };
|
||||||
|
return item;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Fehler bei Checksummen-Prüfung nach Split', e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadBelegnummern = async () => {
|
const loadBelegnummern = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user