fix(mail): Duplikate auch bei gerade laufenden Importen erkennen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 39s
Wurde dieselbe Mail zweimal weitergeleitet, erkannte der Import-Assistent das Duplikat nicht, solange Paperless das erste Dokument noch verarbeitete – geprüft wurde nur die Checksumme in Paperless. - checkDuplicate() berücksichtigt zusätzlich offene Paperless-Tasks von Anhängen mit gleicher Checksumme (gleicher Seitenbereich) - Assistent prüft erneut vor dem Abrufen der Belegnummern und vor dem Import; Duplikate werden ignoriert, reservierte Nummern freigegeben - executeImport prüft serverseitig vor Upload und Festlegen der Nummer, sperrt laufende Importe gegen parallele Doppelimporte und meldet 409 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3cea3fb239
commit
534b313054
5 files changed
+472
-16
No files matched your search
@@ -57,6 +57,12 @@ export const emailImportApi = {
|
||||
return res.data;
|
||||
},
|
||||
|
||||
// Prüft auf Duplikate – in Paperless und bei Importen, die dort noch verarbeitet werden.
|
||||
checkDuplicate: async (attachmentId: number): Promise<boolean> => {
|
||||
const res = await api.post<{ isDuplicate: boolean }>(`/api/email-import/attachments/${attachmentId}/check-duplicate`);
|
||||
return res.data.isDuplicate;
|
||||
},
|
||||
|
||||
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;
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function MailImportWizard({ visible, onClose, onSuccess, email, a
|
||||
// Load correspondents and try to find suggestion
|
||||
loadCorrespondents();
|
||||
|
||||
// Check for duplicates in Paperless
|
||||
// Duplikate prüfen (Paperless und noch laufende Importe)
|
||||
checkDuplicates(initialData);
|
||||
}
|
||||
}, [visible, attachments]);
|
||||
@@ -94,7 +94,7 @@ export default function MailImportWizard({ visible, onClose, onSuccess, email, a
|
||||
const item = updatedData[i];
|
||||
const attachment = attachments.find(a => a.Id === item.attachmentId);
|
||||
if (attachment && attachment.Checksum) {
|
||||
const exists = await paperlessApi.checksumExists(attachment.Checksum);
|
||||
const exists = await emailImportApi.checkDuplicate(attachment.Id);
|
||||
if (exists) {
|
||||
updatedData[i] = { ...updatedData[i], type: 'IGNORE', isDuplicate: true };
|
||||
}
|
||||
@@ -187,6 +187,29 @@ export default function MailImportWizard({ visible, onClose, onSuccess, email, a
|
||||
}
|
||||
};
|
||||
|
||||
// Prüft alle noch zu importierenden Dokumente erneut auf Duplikate – seit dem
|
||||
// Öffnen kann dasselbe Dokument aus einer anderen E-Mail importiert worden
|
||||
// sein. Treffer werden als ignoriert markiert und zurückgegeben.
|
||||
const recheckDuplicates = async (): Promise<AttachmentImportData[]> => {
|
||||
const candidates = importData.filter(i => i.type !== 'IGNORE');
|
||||
const results = await Promise.all(candidates.map(item =>
|
||||
item.pages
|
||||
? emailImportApi.checkSplitChecksum(item.attachmentId, item.pages)
|
||||
: emailImportApi.checkDuplicate(item.attachmentId)
|
||||
));
|
||||
const duplicates = candidates.filter((_, idx) => results[idx]);
|
||||
if (duplicates.length > 0) {
|
||||
const ids = new Set(duplicates.map(d => d.virtualId));
|
||||
setImportData(prev => prev.map(item =>
|
||||
ids.has(item.virtualId) ? { ...item, type: 'IGNORE' as const, isDuplicate: true } : item
|
||||
));
|
||||
message.warning(
|
||||
`Bereits in Paperless vorhanden, wird ignoriert: ${duplicates.map(d => d.fileName).join(', ')}`
|
||||
);
|
||||
}
|
||||
return duplicates;
|
||||
};
|
||||
|
||||
const loadBelegnummern = async () => {
|
||||
// No longer fetching from API here, just initializing state for Step 2
|
||||
for (const item of importData) {
|
||||
@@ -309,6 +332,21 @@ export default function MailImportWizard({ visible, onClose, onSuccess, email, a
|
||||
if (currentStep === 0) {
|
||||
await loadBelegnummern();
|
||||
} else if (currentStep === 1) {
|
||||
// Vor dem Abrufen der Belegnummern erneut prüfen, damit für ein
|
||||
// Duplikat gar nicht erst eine Nummer reserviert wird
|
||||
setLoading(true);
|
||||
try {
|
||||
const duplicates = await recheckDuplicates();
|
||||
if (duplicates.length > 0) {
|
||||
setCurrentStep(0);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
message.error('Duplikatprüfung fehlgeschlagen – bitte erneut versuchen.');
|
||||
return;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
await preFetchBelegnummern();
|
||||
}
|
||||
setCurrentStep(currentStep + 1);
|
||||
@@ -316,6 +354,33 @@ export default function MailImportWizard({ visible, onClose, onSuccess, email, a
|
||||
|
||||
const executeImport = async () => {
|
||||
setLoading(true);
|
||||
// Letzte Prüfung vor dem Import: Duplikate fliegen raus, ihre bereits
|
||||
// reservierten Belegnummern werden wieder freigegeben
|
||||
try {
|
||||
const duplicates = await recheckDuplicates();
|
||||
if (duplicates.length > 0) {
|
||||
for (const item of duplicates) {
|
||||
const vid = item.virtualId;
|
||||
const num = belegnummern[vid];
|
||||
if ((belegnummerMode[vid] || 'neu') === 'neu' && num && num !== '000000') {
|
||||
const dateStr = (eingangsdaten[vid] || dayjs(email.Date)).format('YYYY-MM-DD');
|
||||
await emailImportApi.releaseBelegnummer(dateStr, num).catch(() => {});
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
message.error('Duplikatprüfung fehlgeschlagen – bitte erneut versuchen.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!importData.some(i => i.type !== 'IGNORE')) {
|
||||
message.warning('Keine Dokumente zum Importieren ausgewählt.');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setImportStatus('Import wird gestartet...');
|
||||
const jobId = crypto.randomUUID ? crypto.randomUUID() : `job-${Date.now()}`;
|
||||
const statusPoll = setInterval(async () => {
|
||||
@@ -371,7 +436,7 @@ export default function MailImportWizard({ visible, onClose, onSuccess, email, a
|
||||
setImportSuccess(true);
|
||||
setCurrentStep(2);
|
||||
} catch (e: any) {
|
||||
message.error(`Fehler beim Import: ${e.message}`);
|
||||
message.error(`Fehler beim Import: ${e.response?.data?.message ?? e.message}`);
|
||||
} finally {
|
||||
clearInterval(statusPoll);
|
||||
setImportStatus('');
|
||||
|
||||
Reference in new issue
Block a user