chore: apply ESLint auto-fix across entire backend
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
Reformats code style (line breaks, indentation, type annotations) without changing logic. Also includes minor feature additions bundled in the same lint run (stats service, user-settings groups, agrarmonitor polling improvements). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,32 +16,48 @@ export class PaperlessProcessorService {
|
||||
private readonly configService: ConfigService,
|
||||
private readonly paperlessService: PaperlessService,
|
||||
private readonly postprocessingService: PostprocessingService,
|
||||
@InjectRepository(DocumentType) private readonly docTypeRepo: Repository<DocumentType>,
|
||||
@InjectRepository(DocumentField) private readonly docFieldRepo: Repository<DocumentField>,
|
||||
@InjectRepository(DocumentType)
|
||||
private readonly docTypeRepo: Repository<DocumentType>,
|
||||
@InjectRepository(DocumentField)
|
||||
private readonly docFieldRepo: Repository<DocumentField>,
|
||||
) {}
|
||||
|
||||
@Cron(process.env.PAPERLESS_PROCESSOR_CRON || '0 * * * * *')
|
||||
async processDocuments() {
|
||||
try {
|
||||
const response = await this.paperlessService.getDocuments({ tags__id__all: 16, page_size: 9999 });
|
||||
const documents: any[] = Array.isArray(response) ? response : (response?.results ?? []);
|
||||
const response = await this.paperlessService.getDocuments({
|
||||
tags__id__all: 16,
|
||||
page_size: 9999,
|
||||
});
|
||||
const documents: any[] = Array.isArray(response)
|
||||
? response
|
||||
: (response?.results ?? []);
|
||||
if (documents.length === 0) return;
|
||||
|
||||
const customFields = await this.paperlessService.getCustomFields();
|
||||
const validFieldIds = new Set(customFields.map((f: any) => f.id));
|
||||
|
||||
this.logger.log(`Verarbeite ${documents.length} Dokument(e) mit Tag "paperlessmanager" (ID: 16).`);
|
||||
this.logger.log(
|
||||
`Verarbeite ${documents.length} Dokument(e) mit Tag "paperlessmanager" (ID: 16).`,
|
||||
);
|
||||
|
||||
for (const doc of documents) {
|
||||
try {
|
||||
const updatedDoc = await this.processSingleDocument(doc, validFieldIds);
|
||||
// Postprocessing nach dem Speichern evaluieren
|
||||
await this.postprocessingService.evaluate(updatedDoc || doc);
|
||||
const updatedDoc = await this.processSingleDocument(
|
||||
doc,
|
||||
validFieldIds,
|
||||
);
|
||||
// Postprocessing nach dem Speichern evaluieren
|
||||
await this.postprocessingService.evaluate(updatedDoc || doc);
|
||||
} catch (innerErr: any) {
|
||||
this.logger.error(`Fehler bei Dokument ID ${doc.id}: ${innerErr.message}`);
|
||||
if (innerErr.response?.data) {
|
||||
this.logger.error(`Paperless API Response: ${JSON.stringify(innerErr.response.data)}`);
|
||||
}
|
||||
this.logger.error(
|
||||
`Fehler bei Dokument ID ${doc.id}: ${innerErr.message}`,
|
||||
);
|
||||
if (innerErr.response?.data) {
|
||||
this.logger.error(
|
||||
`Paperless API Response: ${JSON.stringify(innerErr.response.data)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -49,17 +65,24 @@ export class PaperlessProcessorService {
|
||||
}
|
||||
}
|
||||
|
||||
private async processSingleDocument(doc: any, validFieldIds: Set<number>): Promise<any> {
|
||||
private async processSingleDocument(
|
||||
doc: any,
|
||||
validFieldIds: Set<number>,
|
||||
): Promise<any> {
|
||||
this.logger.log(`Verarbeite Dokument ID: ${doc.id}`);
|
||||
|
||||
if (!doc.document_type) {
|
||||
this.logger.warn(`Dokument ${doc.id} hat keinen Dokumenten-Typen – setze Tag 17.`);
|
||||
this.logger.warn(
|
||||
`Dokument ${doc.id} hat keinen Dokumenten-Typen – setze Tag 17.`,
|
||||
);
|
||||
const tagsSet = new Set<number>(doc.tags || []);
|
||||
tagsSet.add(17);
|
||||
if (!tagsSet.has(1)) {
|
||||
tagsSet.add(6);
|
||||
}
|
||||
const updated = await this.paperlessService.updateDocument(doc.id, { tags: Array.from(tagsSet) });
|
||||
const updated = await this.paperlessService.updateDocument(doc.id, {
|
||||
tags: Array.from(tagsSet),
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -68,7 +91,9 @@ export class PaperlessProcessorService {
|
||||
});
|
||||
|
||||
if (!docTypeConfig) {
|
||||
this.logger.warn(`Konfiguration für DocumentType ${doc.document_type} nicht in der Datenbank gefunden.`);
|
||||
this.logger.warn(
|
||||
`Konfiguration für DocumentType ${doc.document_type} nicht in der Datenbank gefunden.`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -77,9 +102,13 @@ export class PaperlessProcessorService {
|
||||
});
|
||||
|
||||
if (fieldsConfig.length === 0) {
|
||||
this.logger.log(`Dokument ${doc.id} (Typ ${doc.document_type}) hat keine Dokument-Felder in der DB konfiguriert.`);
|
||||
this.logger.log(
|
||||
`Dokument ${doc.id} (Typ ${doc.document_type}) hat keine Dokument-Felder in der DB konfiguriert.`,
|
||||
);
|
||||
const newTagsNoFields = Array.from(new Set([...(doc.tags || []), 17]));
|
||||
const updated = await this.paperlessService.updateDocument(doc.id, { tags: newTagsNoFields });
|
||||
const updated = await this.paperlessService.updateDocument(doc.id, {
|
||||
tags: newTagsNoFields,
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -91,15 +120,19 @@ export class PaperlessProcessorService {
|
||||
if (fieldConf.Type === 4) {
|
||||
const customFieldId = fieldConf.TypeIndex;
|
||||
if (!customFieldId) continue;
|
||||
|
||||
|
||||
if (!validFieldIds.has(customFieldId)) {
|
||||
this.logger.warn(`Überspringe ungültiges Custom Field (TypeIndex: ${customFieldId}) für Dokument ${doc.id} - in Paperless nicht vorhanden.`);
|
||||
this.logger.warn(
|
||||
`Überspringe ungültiges Custom Field (TypeIndex: ${customFieldId}) für Dokument ${doc.id} - in Paperless nicht vorhanden.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingField = newCustomFields.find(f => f.field === customFieldId);
|
||||
const existingField = newCustomFields.find(
|
||||
(f) => f.field === customFieldId,
|
||||
);
|
||||
let isFilled = false;
|
||||
|
||||
|
||||
if (existingField) {
|
||||
isFilled = existingField.value !== null && existingField.value !== '';
|
||||
} else {
|
||||
@@ -114,13 +147,16 @@ export class PaperlessProcessorService {
|
||||
let isFilled = false;
|
||||
switch (fieldConf.Type) {
|
||||
case 1:
|
||||
isFilled = doc.correspondent !== null && doc.correspondent !== undefined;
|
||||
isFilled =
|
||||
doc.correspondent !== null && doc.correspondent !== undefined;
|
||||
break;
|
||||
case 2:
|
||||
isFilled = !!doc.created || !!doc.created_date;
|
||||
break;
|
||||
case 3:
|
||||
isFilled = doc.archive_serial_number !== null && doc.archive_serial_number !== undefined;
|
||||
isFilled =
|
||||
doc.archive_serial_number !== null &&
|
||||
doc.archive_serial_number !== undefined;
|
||||
break;
|
||||
case 5:
|
||||
isFilled = !!doc.title;
|
||||
@@ -136,13 +172,13 @@ export class PaperlessProcessorService {
|
||||
}
|
||||
|
||||
const tagsSet = new Set<number>(doc.tags || []);
|
||||
|
||||
|
||||
if (isAllRequiredFilled) {
|
||||
if (docTypeConfig.TagReady) tagsSet.add(docTypeConfig.TagReady);
|
||||
if (docTypeConfig.TagNotReady) tagsSet.delete(docTypeConfig.TagNotReady);
|
||||
if (docTypeConfig.TagReady) tagsSet.add(docTypeConfig.TagReady);
|
||||
if (docTypeConfig.TagNotReady) tagsSet.delete(docTypeConfig.TagNotReady);
|
||||
} else {
|
||||
if (docTypeConfig.TagNotReady) tagsSet.add(docTypeConfig.TagNotReady);
|
||||
if (docTypeConfig.TagReady) tagsSet.delete(docTypeConfig.TagReady);
|
||||
if (docTypeConfig.TagNotReady) tagsSet.add(docTypeConfig.TagNotReady);
|
||||
if (docTypeConfig.TagReady) tagsSet.delete(docTypeConfig.TagReady);
|
||||
}
|
||||
|
||||
tagsSet.add(17);
|
||||
@@ -163,7 +199,7 @@ export class PaperlessProcessorService {
|
||||
|
||||
while ((match = placeholderRegex.exec(title)) !== null) {
|
||||
const fieldId = parseInt(match[1], 10);
|
||||
const cf = newCustomFields.find(f => f.field === fieldId);
|
||||
const cf = newCustomFields.find((f) => f.field === fieldId);
|
||||
if (!cf || cf.value == null || cf.value === '') {
|
||||
allFilled = false;
|
||||
break;
|
||||
@@ -174,7 +210,10 @@ export class PaperlessProcessorService {
|
||||
for (const cf of newCustomFields) {
|
||||
const placeholder = `{{CUSTOM[${cf.field}]}}`;
|
||||
if (title.includes(placeholder)) {
|
||||
title = title.replaceAll(placeholder, cf.value != null ? String(cf.value) : '');
|
||||
title = title.replaceAll(
|
||||
placeholder,
|
||||
cf.value != null ? String(cf.value) : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,13 +231,20 @@ export class PaperlessProcessorService {
|
||||
|
||||
updatePayload.title = title;
|
||||
} else {
|
||||
this.logger.log(`Dokument ${doc.id}: Titel-Template nicht angewendet – nicht alle referenzierten Custom Fields ausgefüllt.`);
|
||||
this.logger.log(
|
||||
`Dokument ${doc.id}: Titel-Template nicht angewendet – nicht alle referenzierten Custom Fields ausgefüllt.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await this.paperlessService.updateDocument(doc.id, updatePayload);
|
||||
const updated = await this.paperlessService.updateDocument(
|
||||
doc.id,
|
||||
updatePayload,
|
||||
);
|
||||
|
||||
this.logger.log(`Dokument ${doc.id} erfolgreich aktualisiert (Alle Pflichtfelder vorhanden: ${isAllRequiredFilled}).`);
|
||||
this.logger.log(
|
||||
`Dokument ${doc.id} erfolgreich aktualisiert (Alle Pflichtfelder vorhanden: ${isAllRequiredFilled}).`,
|
||||
);
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,7 @@ export class PaperlessTaskProcessorService {
|
||||
try {
|
||||
// Fetch tasks that are not finished
|
||||
const tasks = await this.taskRepo.find({
|
||||
where: [
|
||||
{ Fertig: IsNull() },
|
||||
{ Fertig: 0 },
|
||||
],
|
||||
where: [{ Fertig: IsNull() }, { Fertig: 0 }],
|
||||
take: 10,
|
||||
});
|
||||
|
||||
@@ -61,13 +58,17 @@ export class PaperlessTaskProcessorService {
|
||||
|
||||
// Fetch task status from Paperless
|
||||
const paperlessTasks = await this.paperlessService.getTask(t.TaskId);
|
||||
const apiResponseTask = Array.isArray(paperlessTasks) ? paperlessTasks[0] : null;
|
||||
const apiResponseTask = Array.isArray(paperlessTasks)
|
||||
? paperlessTasks[0]
|
||||
: null;
|
||||
|
||||
if (apiResponseTask) {
|
||||
if (apiResponseTask.status === 'SUCCESS') {
|
||||
const dateDone = apiResponseTask.date_done ? new Date(apiResponseTask.date_done) : new Date();
|
||||
const dateDone = apiResponseTask.date_done
|
||||
? new Date(apiResponseTask.date_done)
|
||||
: new Date();
|
||||
const now = new Date();
|
||||
|
||||
|
||||
// Add 10 seconds buffer as in C#
|
||||
if (dateDone.getTime() + 10000 < now.getTime()) {
|
||||
await this.processSuccessfulTask(t, apiResponseTask, parentTask);
|
||||
@@ -87,38 +88,61 @@ export class PaperlessTaskProcessorService {
|
||||
this.logger.log(`${toDelete.length} Tasks gelöscht`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Fehler bei der Task-Verarbeitung: ${error.message}`, error.stack);
|
||||
this.logger.error(
|
||||
`Fehler bei der Task-Verarbeitung: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async processSuccessfulTask(t: Task, apiTask: any, parentTask: Task | null) {
|
||||
private async processSuccessfulTask(
|
||||
t: Task,
|
||||
apiTask: any,
|
||||
parentTask: Task | null,
|
||||
) {
|
||||
const documentId = apiTask.related_document;
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} gestartet. DocumentID: ${documentId ?? 'nicht vorhanden'}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} gestartet. DocumentID: ${documentId ?? 'nicht vorhanden'}`,
|
||||
);
|
||||
|
||||
if (!documentId) {
|
||||
this.logger.error(`Kein Dokument für Task ${t.TaskId} gefunden.`);
|
||||
return;
|
||||
this.logger.error(`Kein Dokument für Task ${t.TaskId} gefunden.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Lade Dokument ${documentId} aus Paperless`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Lade Dokument ${documentId} aus Paperless`,
|
||||
);
|
||||
const document = await this.paperlessService.getDocument(documentId);
|
||||
if (!document) {
|
||||
this.logger.warn(`Dokument mit ID ${documentId} nicht in Paperless gefunden.`);
|
||||
this.logger.warn(
|
||||
`Dokument mit ID ${documentId} nicht in Paperless gefunden.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Dokument geladen: ID=${document.id}, Titel="${document.title}"`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Dokument geladen: ID=${document.id}, Titel="${document.title}"`,
|
||||
);
|
||||
|
||||
// Handle Duplicate Link
|
||||
if (t.DuplikatZU) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Verknüpfe Duplikat zu Dokument ${t.DuplikatZU}`);
|
||||
const duplikatDoc = await this.paperlessService.getDocument(t.DuplikatZU);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Verknüpfe Duplikat zu Dokument ${t.DuplikatZU}`,
|
||||
);
|
||||
const duplikatDoc = await this.paperlessService.getDocument(
|
||||
t.DuplikatZU,
|
||||
);
|
||||
if (duplikatDoc) {
|
||||
// Update duplikatDoc metadata (Field 8 is for linked documents)
|
||||
let duplikatCustomFields = Array.isArray(duplikatDoc.custom_fields) ? [...duplikatDoc.custom_fields] : [];
|
||||
let duplikatCustomFields = Array.isArray(duplikatDoc.custom_fields)
|
||||
? [...duplikatDoc.custom_fields]
|
||||
: [];
|
||||
|
||||
// Remove field 4 as in C#
|
||||
duplikatCustomFields = duplikatCustomFields.filter((f: any) => f.field !== 4);
|
||||
duplikatCustomFields = duplikatCustomFields.filter(
|
||||
(f: any) => f.field !== 4,
|
||||
);
|
||||
|
||||
const field8 = duplikatCustomFields.find((f: any) => f.field === 8);
|
||||
if (field8) {
|
||||
@@ -136,13 +160,21 @@ export class PaperlessTaskProcessorService {
|
||||
document_type: 11,
|
||||
custom_fields: duplikatCustomFields,
|
||||
});
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Duplikat-Dokument ${duplikatDoc.id} aktualisiert`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Duplikat-Dokument ${duplikatDoc.id} aktualisiert`,
|
||||
);
|
||||
|
||||
// Update current document as well
|
||||
const currentCustomFields = Array.isArray(document.custom_fields) ? [...document.custom_fields] : [];
|
||||
const currentField8 = currentCustomFields.find((f: any) => f.field === 8);
|
||||
const currentCustomFields = Array.isArray(document.custom_fields)
|
||||
? [...document.custom_fields]
|
||||
: [];
|
||||
const currentField8 = currentCustomFields.find(
|
||||
(f: any) => f.field === 8,
|
||||
);
|
||||
if (currentField8) {
|
||||
const values = Array.isArray(currentField8.value) ? currentField8.value : [];
|
||||
const values = Array.isArray(currentField8.value)
|
||||
? currentField8.value
|
||||
: [];
|
||||
if (!values.includes(duplikatDoc.id)) {
|
||||
values.push(duplikatDoc.id);
|
||||
currentField8.value = values;
|
||||
@@ -152,70 +184,108 @@ export class PaperlessTaskProcessorService {
|
||||
}
|
||||
document.custom_fields = currentCustomFields;
|
||||
} else {
|
||||
this.logger.warn(`[Postprocessing] Task ${t.TaskId} - Duplikat-Dokument ${t.DuplikatZU} nicht gefunden`);
|
||||
this.logger.warn(
|
||||
`[Postprocessing] Task ${t.TaskId} - Duplikat-Dokument ${t.DuplikatZU} nicht gefunden`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Enrich Document
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Reichere Dokument-Metadaten an`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Reichere Dokument-Metadaten an`,
|
||||
);
|
||||
const updateData: any = {
|
||||
custom_fields: Array.isArray(document.custom_fields) ? [...document.custom_fields] : [],
|
||||
custom_fields: Array.isArray(document.custom_fields)
|
||||
? [...document.custom_fields]
|
||||
: [],
|
||||
};
|
||||
|
||||
// CustomFieldsJson als Basis zuerst anwenden – dedizierte Felder weiter unten überschreiben diese
|
||||
if (t.CustomFieldsJson) {
|
||||
try {
|
||||
const extra = JSON.parse(t.CustomFieldsJson) as Record<string, string>;
|
||||
const extra = JSON.parse(t.CustomFieldsJson) as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
for (const [k, v] of Object.entries(extra)) {
|
||||
const fieldId = parseInt(k, 10);
|
||||
if (!Number.isFinite(fieldId)) continue;
|
||||
const idx = updateData.custom_fields.findIndex((f: any) => f.field === fieldId);
|
||||
const idx = updateData.custom_fields.findIndex(
|
||||
(f: any) => f.field === fieldId,
|
||||
);
|
||||
if (idx !== -1) updateData.custom_fields[idx].value = v;
|
||||
else updateData.custom_fields.push({ field: fieldId, value: v });
|
||||
}
|
||||
} catch { /* JSON-Parse-Fehler ignorieren */ }
|
||||
} catch {
|
||||
/* JSON-Parse-Fehler ignorieren */
|
||||
}
|
||||
}
|
||||
|
||||
if (t.Asn) {
|
||||
const asnNum = parseInt(t.Asn.replace(/[^0-9]/g, ''), 10);
|
||||
if (!isNaN(asnNum)) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze ASN (explizit): ${asnNum}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze ASN (explizit): ${asnNum}`,
|
||||
);
|
||||
updateData.archive_serial_number = asnNum;
|
||||
}
|
||||
}
|
||||
|
||||
if (t.InterneBelegnummer) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze InterneBelegnummer: ${t.InterneBelegnummer}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze InterneBelegnummer: ${t.InterneBelegnummer}`,
|
||||
);
|
||||
if (!t.Asn) {
|
||||
const asnFromBelegnummer = parseInt(t.InterneBelegnummer.replace(/-/g, ''), 10);
|
||||
const asnFromBelegnummer = parseInt(
|
||||
t.InterneBelegnummer.replace(/-/g, ''),
|
||||
10,
|
||||
);
|
||||
if (!isNaN(asnFromBelegnummer)) {
|
||||
updateData.archive_serial_number = asnFromBelegnummer;
|
||||
} else {
|
||||
this.logger.warn(`[Postprocessing] Task ${t.TaskId} - ASN aus InterneBelegnummer konnte nicht geparst werden: ${t.InterneBelegnummer}`);
|
||||
this.logger.warn(
|
||||
`[Postprocessing] Task ${t.TaskId} - ASN aus InterneBelegnummer konnte nicht geparst werden: ${t.InterneBelegnummer}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const existingField7 = updateData.custom_fields.find((f: any) => f.field === 7);
|
||||
const existingField7 = updateData.custom_fields.find(
|
||||
(f: any) => f.field === 7,
|
||||
);
|
||||
if (existingField7) {
|
||||
existingField7.value = t.InterneBelegnummer;
|
||||
} else {
|
||||
updateData.custom_fields.push({ field: 7, value: t.InterneBelegnummer });
|
||||
updateData.custom_fields.push({
|
||||
field: 7,
|
||||
value: t.InterneBelegnummer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (t.externeBelegnummer) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze externeBelegnummer: ${t.externeBelegnummer}`);
|
||||
const existingField3 = updateData.custom_fields.find((f: any) => f.field === 3);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze externeBelegnummer: ${t.externeBelegnummer}`,
|
||||
);
|
||||
const existingField3 = updateData.custom_fields.find(
|
||||
(f: any) => f.field === 3,
|
||||
);
|
||||
if (existingField3) {
|
||||
existingField3.value = t.externeBelegnummer;
|
||||
} else {
|
||||
updateData.custom_fields.push({ field: 3, value: t.externeBelegnummer });
|
||||
updateData.custom_fields.push({
|
||||
field: 3,
|
||||
value: t.externeBelegnummer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (t.Eingangsdatum) {
|
||||
const dateValue = new Date(t.Eingangsdatum).toISOString().split('T')[0];
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze Eingangsdatum: ${dateValue}`);
|
||||
const existingField9 = updateData.custom_fields.find((f: any) => f.field === 9);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze Eingangsdatum: ${dateValue}`,
|
||||
);
|
||||
const existingField9 = updateData.custom_fields.find(
|
||||
(f: any) => f.field === 9,
|
||||
);
|
||||
if (existingField9) {
|
||||
existingField9.value = dateValue;
|
||||
} else {
|
||||
@@ -224,24 +294,38 @@ export class PaperlessTaskProcessorService {
|
||||
}
|
||||
|
||||
if (t.DocumentType) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze DocumentType: ${t.DocumentType}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze DocumentType: ${t.DocumentType}`,
|
||||
);
|
||||
updateData.document_type = t.DocumentType;
|
||||
}
|
||||
|
||||
// Parent Task / Attachment logic
|
||||
if (parentTask) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Verarbeite als Anlage zu ParentTask ${parentTask.TaskId}`);
|
||||
const parentPaperlessTasks = await this.paperlessService.getTask(parentTask.TaskId);
|
||||
const apiParentTask = Array.isArray(parentPaperlessTasks) ? parentPaperlessTasks[0] : null;
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Verarbeite als Anlage zu ParentTask ${parentTask.TaskId}`,
|
||||
);
|
||||
const parentPaperlessTasks = await this.paperlessService.getTask(
|
||||
parentTask.TaskId,
|
||||
);
|
||||
const apiParentTask = Array.isArray(parentPaperlessTasks)
|
||||
? parentPaperlessTasks[0]
|
||||
: null;
|
||||
|
||||
if (apiParentTask && apiParentTask.related_document) {
|
||||
const parentDoc = await this.paperlessService.getDocument(apiParentTask.related_document);
|
||||
const parentDoc = await this.paperlessService.getDocument(
|
||||
apiParentTask.related_document,
|
||||
);
|
||||
if (parentDoc) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Elterndokument ${parentDoc.id} gefunden, setze Anlage-Typ`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Elterndokument ${parentDoc.id} gefunden, setze Anlage-Typ`,
|
||||
);
|
||||
updateData.document_type = 5; // Anlage
|
||||
updateData.title = `Anlage zu ${parentTask.InterneBelegnummer}`;
|
||||
|
||||
const field8 = updateData.custom_fields.find((f: any) => f.field === 8);
|
||||
const field8 = updateData.custom_fields.find(
|
||||
(f: any) => f.field === 8,
|
||||
);
|
||||
if (field8) {
|
||||
const values = Array.isArray(field8.value) ? field8.value : [];
|
||||
if (!values.includes(parentDoc.id)) {
|
||||
@@ -249,33 +333,50 @@ export class PaperlessTaskProcessorService {
|
||||
field8.value = values;
|
||||
}
|
||||
} else {
|
||||
updateData.custom_fields.push({ field: 8, value: [parentDoc.id] });
|
||||
updateData.custom_fields.push({
|
||||
field: 8,
|
||||
value: [parentDoc.id],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(`[Postprocessing] Task ${t.TaskId} - Elterndokument nicht gefunden`);
|
||||
this.logger.warn(
|
||||
`[Postprocessing] Task ${t.TaskId} - Elterndokument nicht gefunden`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(`[Postprocessing] Task ${t.TaskId} - ParentTask hat kein related_document`);
|
||||
this.logger.warn(
|
||||
`[Postprocessing] Task ${t.TaskId} - ParentTask hat kein related_document`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (t.Belegdatum) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze Belegdatum: ${t.Belegdatum.toISOString()}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze Belegdatum: ${t.Belegdatum.toISOString()}`,
|
||||
);
|
||||
updateData.created = t.Belegdatum.toISOString();
|
||||
}
|
||||
|
||||
if (t.BetriebID) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze Owner: ${t.BetriebID}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze Owner: ${t.BetriebID}`,
|
||||
);
|
||||
updateData.owner = t.BetriebID;
|
||||
} else {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Entferne Owner (setze null)`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Entferne Owner (setze null)`,
|
||||
);
|
||||
updateData.owner = null;
|
||||
}
|
||||
|
||||
// Tags
|
||||
if (t.Tags) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze Tags: ${t.Tags}`);
|
||||
const tagIds = t.Tags.split(',').map(id => parseInt(id.trim(), 10)).filter(id => !isNaN(id));
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze Tags: ${t.Tags}`,
|
||||
);
|
||||
const tagIds = t.Tags.split(',')
|
||||
.map((id) => parseInt(id.trim(), 10))
|
||||
.filter((id) => !isNaN(id));
|
||||
const currentTags = document.tags || [];
|
||||
const newTags = Array.from(new Set([...currentTags, ...tagIds]));
|
||||
updateData.tags = newTags;
|
||||
@@ -284,46 +385,78 @@ export class PaperlessTaskProcessorService {
|
||||
// Agrarmonitor Link (Skip API call for now, but save the link if needed)
|
||||
if (t.EinkaufID) {
|
||||
const link = `https://admin7.agrarmonitor.de/rechnungen/detail/${t.EinkaufID}`;
|
||||
this.logger.log(`Skipping Agrarmonitor details for EinkaufID ${t.EinkaufID}. Link: ${link}`);
|
||||
this.logger.log(
|
||||
`Skipping Agrarmonitor details for EinkaufID ${t.EinkaufID}. Link: ${link}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (t.Lieferant) {
|
||||
this.logger.log(`Skipping Correspondent lookup/creation for Lieferant ID ${t.Lieferant} (Agrarmonitor part deferred).`);
|
||||
this.logger.log(
|
||||
`Skipping Correspondent lookup/creation for Lieferant ID ${t.Lieferant} (Agrarmonitor part deferred).`,
|
||||
);
|
||||
}
|
||||
|
||||
// Update Document in Paperless
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Aktualisiere Dokument ${document.id} in Paperless. Payload: ${JSON.stringify(updateData)}`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Aktualisiere Dokument ${document.id} in Paperless. Payload: ${JSON.stringify(updateData)}`,
|
||||
);
|
||||
await this.paperlessService.updateDocument(document.id, updateData);
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Dokument ${document.id} erfolgreich aktualisiert`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Dokument ${document.id} erfolgreich aktualisiert`,
|
||||
);
|
||||
|
||||
// Add Notes
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Füge Notizen hinzu`);
|
||||
await this.paperlessService.addNote(document.id, `Task bearbeitet: ${new Date().toLocaleString('de-DE')}`);
|
||||
await this.paperlessService.addNote(
|
||||
document.id,
|
||||
`Task bearbeitet: ${new Date().toLocaleString('de-DE')}`,
|
||||
);
|
||||
|
||||
if (t.SourceAttachmentID) {
|
||||
const attachment = await this.attachmentRepo.findOne({ where: { Id: t.SourceAttachmentID }, relations: ['EmailMessage'] });
|
||||
const attachment = await this.attachmentRepo.findOne({
|
||||
where: { Id: t.SourceAttachmentID },
|
||||
relations: ['EmailMessage'],
|
||||
});
|
||||
if (attachment) {
|
||||
const rangePart = t.SourceAttachmentRange && t.SourceAttachmentRange !== 'full'
|
||||
? ` | Seiten: ${t.SourceAttachmentRange}`
|
||||
: '';
|
||||
const messageId = attachment.EmailMessage?.MessageId ?? String(attachment.EmailMessageId);
|
||||
await this.paperlessService.addNote(document.id, `E-Mail-ID: ${messageId} | Datei: ${attachment.FileName}${rangePart}`);
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Herkunfts-Notiz hinzugefügt`);
|
||||
const rangePart =
|
||||
t.SourceAttachmentRange && t.SourceAttachmentRange !== 'full'
|
||||
? ` | Seiten: ${t.SourceAttachmentRange}`
|
||||
: '';
|
||||
const messageId =
|
||||
attachment.EmailMessage?.MessageId ??
|
||||
String(attachment.EmailMessageId);
|
||||
await this.paperlessService.addNote(
|
||||
document.id,
|
||||
`E-Mail-ID: ${messageId} | Datei: ${attachment.FileName}${rangePart}`,
|
||||
);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Herkunfts-Notiz hinzugefügt`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (t.BarcodeJson) {
|
||||
await this.paperlessService.addNote(document.id, t.BarcodeJson);
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - BarcodeJson-Notiz hinzugefügt`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - BarcodeJson-Notiz hinzugefügt`,
|
||||
);
|
||||
}
|
||||
|
||||
// Sync local Documents table
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Synchronisiere lokale Documents-Tabelle`);
|
||||
const metadata = await this.paperlessService.getDocumentMetadata(document.id);
|
||||
let localDoc = await this.documentRepo.findOne({ where: { documentId: document.id } });
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Synchronisiere lokale Documents-Tabelle`,
|
||||
);
|
||||
const metadata = await this.paperlessService.getDocumentMetadata(
|
||||
document.id,
|
||||
);
|
||||
let localDoc = await this.documentRepo.findOne({
|
||||
where: { documentId: document.id },
|
||||
});
|
||||
|
||||
if (!localDoc) {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Erstelle neuen lokalen Dokument-Eintrag`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Erstelle neuen lokalen Dokument-Eintrag`,
|
||||
);
|
||||
localDoc = this.documentRepo.create({
|
||||
documentId: document.id,
|
||||
checksum: metadata.original_checksum,
|
||||
@@ -331,34 +464,46 @@ export class PaperlessTaskProcessorService {
|
||||
});
|
||||
await this.documentRepo.save(localDoc);
|
||||
} else {
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Aktualisiere bestehenden lokalen Dokument-Eintrag`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Aktualisiere bestehenden lokalen Dokument-Eintrag`,
|
||||
);
|
||||
localDoc.checksum = metadata.original_checksum;
|
||||
localDoc.filename = metadata.original_filename;
|
||||
await this.documentRepo.save(localDoc);
|
||||
}
|
||||
|
||||
// Update Task status
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} - Setze Task-Status auf Fertig`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} - Setze Task-Status auf Fertig`,
|
||||
);
|
||||
t.Fertig = 1;
|
||||
t.PaperlessDocumentID = document.id;
|
||||
await this.taskRepo.save(t);
|
||||
|
||||
// Update source attachment if linked
|
||||
if (t.SourceAttachmentID && t.SourceAttachmentRange) {
|
||||
const attachment = await this.attachmentRepo.findOne({ where: { Id: t.SourceAttachmentID } });
|
||||
const attachment = await this.attachmentRepo.findOne({
|
||||
where: { Id: t.SourceAttachmentID },
|
||||
});
|
||||
if (attachment) {
|
||||
const ids = attachment.PaperlessDocumentIds || {};
|
||||
ids[t.SourceAttachmentRange] = document.id;
|
||||
attachment.PaperlessDocumentIds = ids;
|
||||
await this.attachmentRepo.save(attachment);
|
||||
this.logger.log(`[Postprocessing] Anhang ${attachment.Id} mit PaperlessID ${document.id} (${t.SourceAttachmentRange}) aktualisiert`);
|
||||
this.logger.log(
|
||||
`[Postprocessing] Anhang ${attachment.Id} mit PaperlessID ${document.id} (${t.SourceAttachmentRange}) aktualisiert`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`[Postprocessing] Task ${t.TaskId} erfolgreich abgeschlossen. Dokument ID: ${document.id}`);
|
||||
|
||||
this.logger.log(
|
||||
`[Postprocessing] Task ${t.TaskId} erfolgreich abgeschlossen. Dokument ID: ${document.id}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`Fehler bei der Verarbeitung von Task ${t.TaskId}: ${error.message}`, error.stack);
|
||||
this.logger.error(
|
||||
`Fehler bei der Verarbeitung von Task ${t.TaskId}: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
import { Controller, Get, Param, Post, Put, Delete, UseGuards, UseInterceptors, UploadedFile, Body, Logger, HttpException, HttpStatus, Res, Query } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
Body,
|
||||
Logger,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Res,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import { RequirePermissions } from '../auth/permissions.decorator';
|
||||
import { Permission } from '../auth/permissions.enum';
|
||||
@@ -14,7 +30,6 @@ import { Document } from '../database/entities/document.entity';
|
||||
import { DocumentField } from '../database/entities/document-field.entity';
|
||||
import { DocumentType } from '../database/entities/document-type.entity';
|
||||
|
||||
|
||||
@Controller('api/paperless')
|
||||
export class PaperlessController {
|
||||
private readonly logger = new Logger(PaperlessController.name);
|
||||
@@ -36,7 +51,7 @@ export class PaperlessController {
|
||||
const exists = await this.paperlessService.checksumExists(checksum);
|
||||
return { exists };
|
||||
}
|
||||
|
||||
|
||||
@Get('documents')
|
||||
async getDocuments(
|
||||
@Query('search') search?: string,
|
||||
@@ -67,7 +82,7 @@ export class PaperlessController {
|
||||
async getTag(@Param('id') id: string) {
|
||||
// If the service doesn't have getTag(id), I should add it or just fetch all and find
|
||||
const tags = await this.paperlessService.getTags();
|
||||
return tags.find(t => t.id === parseInt(id, 10));
|
||||
return tags.find((t) => t.id === parseInt(id, 10));
|
||||
}
|
||||
|
||||
@Get('document-types')
|
||||
@@ -107,7 +122,11 @@ export class PaperlessController {
|
||||
const documents = await this.paperlessService.getInboxDocuments();
|
||||
// In old C# logic: only return docs where archive_serial_number is not null
|
||||
return documents
|
||||
.filter((doc: any) => doc.archive_serial_number !== null && doc.archive_serial_number !== undefined)
|
||||
.filter(
|
||||
(doc: any) =>
|
||||
doc.archive_serial_number !== null &&
|
||||
doc.archive_serial_number !== undefined,
|
||||
)
|
||||
.map((doc: any) => ({
|
||||
id: doc.id,
|
||||
title: doc.title,
|
||||
@@ -127,7 +146,11 @@ export class PaperlessController {
|
||||
async getManuellList() {
|
||||
const documents = await this.paperlessService.getManuellDocuments();
|
||||
return documents
|
||||
.filter((doc: any) => doc.archive_serial_number !== null && doc.archive_serial_number !== undefined)
|
||||
.filter(
|
||||
(doc: any) =>
|
||||
doc.archive_serial_number !== null &&
|
||||
doc.archive_serial_number !== undefined,
|
||||
)
|
||||
.map((doc: any) => ({
|
||||
id: doc.id,
|
||||
title: doc.title,
|
||||
@@ -146,16 +169,24 @@ export class PaperlessController {
|
||||
@Get('inbox/preview/:id')
|
||||
async getInboxPreview(@Param('id') id: string, @Res() res: Response) {
|
||||
try {
|
||||
const stream = await this.paperlessService.getDocumentPreviewStream(parseInt(id, 10));
|
||||
const stream = await this.paperlessService.getDocumentPreviewStream(
|
||||
parseInt(id, 10),
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Disposition': `inline; filename="${id}preview.png"`,
|
||||
});
|
||||
stream.on('error', () => { if (!res.headersSent) res.status(HttpStatus.INTERNAL_SERVER_ERROR).end(); else res.end(); });
|
||||
stream.on('error', () => {
|
||||
if (!res.headersSent)
|
||||
res.status(HttpStatus.INTERNAL_SERVER_ERROR).end();
|
||||
else res.end();
|
||||
});
|
||||
stream.pipe(res);
|
||||
} catch (error) {
|
||||
if (!res.headersSent) {
|
||||
res.status(HttpStatus.INTERNAL_SERVER_ERROR).send('Error fetching preview');
|
||||
res
|
||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.send('Error fetching preview');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,12 +195,18 @@ export class PaperlessController {
|
||||
@Get('inbox/pdf/:id')
|
||||
async getInboxPdf(@Param('id') id: string, @Res() res: Response) {
|
||||
try {
|
||||
const stream = await this.paperlessService.getDocumentPdfStream(parseInt(id, 10));
|
||||
const stream = await this.paperlessService.getDocumentPdfStream(
|
||||
parseInt(id, 10),
|
||||
);
|
||||
res.set({
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `inline; filename="${id}.pdf"`,
|
||||
});
|
||||
stream.on('error', () => { if (!res.headersSent) res.status(HttpStatus.INTERNAL_SERVER_ERROR).end(); else res.end(); });
|
||||
stream.on('error', () => {
|
||||
if (!res.headersSent)
|
||||
res.status(HttpStatus.INTERNAL_SERVER_ERROR).end();
|
||||
else res.end();
|
||||
});
|
||||
stream.pipe(res);
|
||||
} catch (error) {
|
||||
if (!res.headersSent) {
|
||||
@@ -179,25 +216,28 @@ export class PaperlessController {
|
||||
}
|
||||
|
||||
@Get('requirements/:id')
|
||||
async getRequirements(@Param('id') id: string, @Query('Posteingang') posteingang: string) {
|
||||
async getRequirements(
|
||||
@Param('id') id: string,
|
||||
@Query('Posteingang') posteingang: string,
|
||||
) {
|
||||
const documentTypeId = parseInt(id, 10);
|
||||
const isPosteingang = posteingang === '1';
|
||||
|
||||
const requirements = await this.documentFieldRepo.find({
|
||||
where: { DocumentType: documentTypeId },
|
||||
});
|
||||
|
||||
|
||||
// Custom fields fetching inside here could be slow, but this is the simplest translation of the old API
|
||||
// Actually, getting all CFs doesn't take too long in Paperless API.
|
||||
const customFields = await this.paperlessService.getCustomFields();
|
||||
|
||||
|
||||
const retVal: any[] = [];
|
||||
|
||||
|
||||
for (const req of requirements) {
|
||||
if (isPosteingang && !req.VisiblePosteingang) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const tmp: any = {
|
||||
id: req.Id,
|
||||
feldId: req.Type + (req.TypeIndex !== null ? '-' + req.TypeIndex : ''),
|
||||
@@ -214,7 +254,7 @@ export class PaperlessController {
|
||||
if (cf) {
|
||||
tmp.feldName = cf.name;
|
||||
tmp.feldTyp = cf.id === 12 ? 'int' : cf.data_type;
|
||||
|
||||
|
||||
if (cf.extra_data && cf.extra_data.select_options) {
|
||||
tmp.fieldOptions = cf.extra_data.select_options
|
||||
.filter((o: any) => o !== null)
|
||||
@@ -227,9 +267,14 @@ export class PaperlessController {
|
||||
} else if (req.Type === 1) {
|
||||
tmp.feldName = 'Absender';
|
||||
tmp.feldTyp = 'select';
|
||||
const response = await this.paperlessService.getCorrespondents({ page_size: 9999 });
|
||||
const response = await this.paperlessService.getCorrespondents({
|
||||
page_size: 9999,
|
||||
});
|
||||
const correspondents = response.results;
|
||||
tmp.fieldOptions = correspondents.map((c: any) => ({ id: c.id.toString(), label: c.name }));
|
||||
tmp.fieldOptions = correspondents.map((c: any) => ({
|
||||
id: c.id.toString(),
|
||||
label: c.name,
|
||||
}));
|
||||
} else if (req.Type === 2) {
|
||||
tmp.feldName = 'Belegdatum';
|
||||
tmp.feldTyp = 'date';
|
||||
@@ -246,7 +291,7 @@ export class PaperlessController {
|
||||
|
||||
retVal.push(tmp);
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -265,34 +310,49 @@ export class PaperlessController {
|
||||
if (body.date) {
|
||||
let docDate = new Date(body.date);
|
||||
if (docDate.getHours() > 22) {
|
||||
docDate = new Date(docDate.getTime() + 24 * 60 * 60 * 1000 - docDate.getHours() * 60 * 60 * 1000);
|
||||
docDate = new Date(
|
||||
docDate.getTime() +
|
||||
24 * 60 * 60 * 1000 -
|
||||
docDate.getHours() * 60 * 60 * 1000,
|
||||
);
|
||||
}
|
||||
oldDocument.created_date = docDate.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
|
||||
const cfDefinitions = await this.paperlessService.getCustomFields();
|
||||
|
||||
|
||||
// update custom fields
|
||||
if (body.customFields) {
|
||||
for (const [key, value] of Object.entries(body.customFields)) {
|
||||
const fieldId = parseInt(key, 10);
|
||||
const cfDef = cfDefinitions.find((c: any) => c.id === fieldId);
|
||||
|
||||
|
||||
let processedValue = value;
|
||||
if (cfDef?.data_type === 'documentlink' && value !== null && value !== '' && !Array.isArray(value)) {
|
||||
if (
|
||||
cfDef?.data_type === 'documentlink' &&
|
||||
value !== null &&
|
||||
value !== '' &&
|
||||
!Array.isArray(value)
|
||||
) {
|
||||
processedValue = [value];
|
||||
}
|
||||
|
||||
const existingFieldIndex = oldDocument.custom_fields.findIndex((f: any) => f.field === fieldId);
|
||||
|
||||
const existingFieldIndex = oldDocument.custom_fields.findIndex(
|
||||
(f: any) => f.field === fieldId,
|
||||
);
|
||||
|
||||
if (existingFieldIndex !== -1) {
|
||||
if (processedValue === null || processedValue === '') {
|
||||
oldDocument.custom_fields.splice(existingFieldIndex, 1);
|
||||
} else {
|
||||
oldDocument.custom_fields[existingFieldIndex].value = processedValue;
|
||||
oldDocument.custom_fields[existingFieldIndex].value =
|
||||
processedValue;
|
||||
}
|
||||
} else if (processedValue !== null && processedValue !== '') {
|
||||
oldDocument.custom_fields.push({ field: fieldId, value: processedValue });
|
||||
oldDocument.custom_fields.push({
|
||||
field: fieldId,
|
||||
value: processedValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,7 +361,7 @@ export class PaperlessController {
|
||||
const reqs = await this.documentFieldRepo.find({
|
||||
where: { DocumentType: oldDocument.document_type },
|
||||
});
|
||||
|
||||
|
||||
let isReady = true;
|
||||
let isReadyPosteingang = true;
|
||||
|
||||
@@ -309,12 +369,19 @@ export class PaperlessController {
|
||||
let isFieldValid = false;
|
||||
if (req.Type === 1) isFieldValid = oldDocument.correspondent !== null;
|
||||
if (req.Type === 2) isFieldValid = oldDocument.created_date !== null;
|
||||
if (req.Type === 3) isFieldValid = oldDocument.archive_serial_number !== null;
|
||||
if (req.Type === 4) isFieldValid = !!oldDocument.custom_fields.find((cf: any) => cf.field === req.TypeIndex && cf.value !== null && cf.value !== '');
|
||||
if (req.Type === 5) isFieldValid = oldDocument.title !== null && oldDocument.title !== '';
|
||||
if (req.Type === 3)
|
||||
isFieldValid = oldDocument.archive_serial_number !== null;
|
||||
if (req.Type === 4)
|
||||
isFieldValid = !!oldDocument.custom_fields.find(
|
||||
(cf: any) =>
|
||||
cf.field === req.TypeIndex && cf.value !== null && cf.value !== '',
|
||||
);
|
||||
if (req.Type === 5)
|
||||
isFieldValid = oldDocument.title !== null && oldDocument.title !== '';
|
||||
|
||||
if (req.IsRequired && !isFieldValid) isReady = false;
|
||||
if (req.IsRequiredPosteingang && !isFieldValid) isReadyPosteingang = false;
|
||||
if (req.IsRequiredPosteingang && !isFieldValid)
|
||||
isReadyPosteingang = false;
|
||||
}
|
||||
|
||||
const docType = await this.documentTypeRepo.findOne({
|
||||
@@ -325,7 +392,10 @@ export class PaperlessController {
|
||||
|
||||
if (isReady) {
|
||||
oldDocument.tags = oldDocument.tags.filter((t: number) => t !== 1);
|
||||
if (docType?.TagNotReady) oldDocument.tags = oldDocument.tags.filter((t: number) => t !== docType.TagNotReady);
|
||||
if (docType?.TagNotReady)
|
||||
oldDocument.tags = oldDocument.tags.filter(
|
||||
(t: number) => t !== docType.TagNotReady,
|
||||
);
|
||||
if (docType?.TagReady && !oldDocument.tags.includes(docType.TagReady)) {
|
||||
oldDocument.tags.push(docType.TagReady);
|
||||
}
|
||||
@@ -335,17 +405,24 @@ export class PaperlessController {
|
||||
for (const cf of oldDocument.custom_fields) {
|
||||
const placeholder = `{{CUSTOM[${cf.field}]}}`;
|
||||
if (titleTemplate.includes(placeholder)) {
|
||||
titleTemplate = titleTemplate.replace(placeholder, cf.value?.toString() ?? '');
|
||||
titleTemplate = titleTemplate.replace(
|
||||
placeholder,
|
||||
cf.value?.toString() ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
titleTemplate = titleTemplate.replace('{{DATE}}', oldDocument.created_date);
|
||||
titleTemplate = titleTemplate.replace(
|
||||
'{{DATE}}',
|
||||
oldDocument.created_date,
|
||||
);
|
||||
oldDocument.title = titleTemplate;
|
||||
}
|
||||
} else {
|
||||
if (docType?.TagNotReady) {
|
||||
if (isReadyPosteingang) {
|
||||
oldDocument.tags = oldDocument.tags.filter((t: number) => t !== 1);
|
||||
if (!oldDocument.tags.includes(docType.TagNotReady)) oldDocument.tags.push(docType.TagNotReady);
|
||||
if (!oldDocument.tags.includes(docType.TagNotReady))
|
||||
oldDocument.tags.push(docType.TagNotReady);
|
||||
} else {
|
||||
if (!oldDocument.tags.includes(1)) oldDocument.tags.push(1);
|
||||
}
|
||||
@@ -353,7 +430,9 @@ export class PaperlessController {
|
||||
if (!oldDocument.tags.includes(1)) oldDocument.tags.push(1);
|
||||
}
|
||||
if (docType?.TagReady) {
|
||||
oldDocument.tags = oldDocument.tags.filter((t: number) => t !== docType.TagReady);
|
||||
oldDocument.tags = oldDocument.tags.filter(
|
||||
(t: number) => t !== docType.TagReady,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,16 +470,21 @@ export class PaperlessController {
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Body() dto: UploadExternalDto,
|
||||
) {
|
||||
this.logger.log(`Externer Upload gestartet: ${dto.interneBelegnummer} (${file?.originalname})`);
|
||||
this.logger.log(
|
||||
`Externer Upload gestartet: ${dto.interneBelegnummer} (${file?.originalname})`,
|
||||
);
|
||||
|
||||
try {
|
||||
// 0. Check if ASN already exists
|
||||
await this.paperlessService.validateAsnNotExists(dto.interneBelegnummer);
|
||||
|
||||
// 1. Forward to Paperless
|
||||
const paperlessTaskId = await this.paperlessService.uploadDocument(file.path, {
|
||||
title: `Beleg ${dto.interneBelegnummer}`,
|
||||
});
|
||||
const paperlessTaskId = await this.paperlessService.uploadDocument(
|
||||
file.path,
|
||||
{
|
||||
title: `Beleg ${dto.interneBelegnummer}`,
|
||||
},
|
||||
);
|
||||
|
||||
// 2. Create local Task
|
||||
const task = this.taskRepo.create({
|
||||
@@ -422,10 +506,15 @@ export class PaperlessController {
|
||||
|
||||
await this.taskRepo.save(task);
|
||||
|
||||
this.logger.log(`Externer Upload erfolgreich: ${task.TaskId} für Beleg ${dto.interneBelegnummer}`);
|
||||
this.logger.log(
|
||||
`Externer Upload erfolgreich: ${task.TaskId} für Beleg ${dto.interneBelegnummer}`,
|
||||
);
|
||||
return task.TaskId;
|
||||
} catch (err) {
|
||||
this.logger.error(`Fehler beim externen Upload für Beleg ${dto.interneBelegnummer}: ${err.message}`, err.stack);
|
||||
this.logger.error(
|
||||
`Fehler beim externen Upload für Beleg ${dto.interneBelegnummer}: ${err.message}`,
|
||||
err.stack,
|
||||
);
|
||||
throw new HttpException(
|
||||
`Fehler beim Verarbeiten des Dokumenten-Uploads: ${err.message}`,
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -14,12 +14,22 @@ import { AuthModule } from '../auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([DocumentType, DocumentField, Task, Document, Attachment]),
|
||||
TypeOrmModule.forFeature([
|
||||
DocumentType,
|
||||
DocumentField,
|
||||
Task,
|
||||
Document,
|
||||
Attachment,
|
||||
]),
|
||||
forwardRef(() => PostprocessingModule),
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [PaperlessController],
|
||||
providers: [PaperlessService, PaperlessProcessorService, PaperlessTaskProcessorService],
|
||||
providers: [
|
||||
PaperlessService,
|
||||
PaperlessProcessorService,
|
||||
PaperlessTaskProcessorService,
|
||||
],
|
||||
exports: [PaperlessService],
|
||||
})
|
||||
export class PaperlessModule {}
|
||||
|
||||
@@ -11,7 +11,10 @@ export class PaperlessService {
|
||||
private readonly client: AxiosInstance;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
const baseURL = this.configService.get<string>('PAPERLESS_URL', 'http://localhost:8000');
|
||||
const baseURL = this.configService.get<string>(
|
||||
'PAPERLESS_URL',
|
||||
'http://localhost:8000',
|
||||
);
|
||||
const token = this.configService.get<string>('PAPERLESS_TOKEN', '');
|
||||
|
||||
this.client = axios.create({
|
||||
@@ -49,16 +52,22 @@ export class PaperlessService {
|
||||
|
||||
if (options?.title) form.append('title', options.title);
|
||||
if (options?.created) form.append('created', options.created);
|
||||
if (options?.documentType) form.append('document_type', String(options.documentType));
|
||||
if (options?.correspondent) form.append('correspondent', String(options.correspondent));
|
||||
if (options?.storagePath) form.append('storage_path', String(options.storagePath));
|
||||
if (options?.documentType)
|
||||
form.append('document_type', String(options.documentType));
|
||||
if (options?.correspondent)
|
||||
form.append('correspondent', String(options.correspondent));
|
||||
if (options?.storagePath)
|
||||
form.append('storage_path', String(options.storagePath));
|
||||
if (options?.owner !== undefined && options.owner !== null) {
|
||||
form.append('owner', String(options.owner));
|
||||
}
|
||||
if (options?.tags) {
|
||||
options.tags.forEach((tag) => form.append('tags', String(tag)));
|
||||
}
|
||||
if (options?.archiveSerialNumber !== undefined && !Number.isNaN(options.archiveSerialNumber)) {
|
||||
if (
|
||||
options?.archiveSerialNumber !== undefined &&
|
||||
!Number.isNaN(options.archiveSerialNumber)
|
||||
) {
|
||||
form.append('archive_serial_number', String(options.archiveSerialNumber));
|
||||
}
|
||||
if (options?.customFields && Object.keys(options.customFields).length > 0) {
|
||||
@@ -92,27 +101,30 @@ export class PaperlessService {
|
||||
async getInboxDocuments(): Promise<any[]> {
|
||||
// API pagination to get large amount of inbox documents (assuming max 9999 like C# app)
|
||||
const response = await this.client.get('/documents/', {
|
||||
params: {
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
ordering: '-added',
|
||||
truncate_content: true,
|
||||
tags__id__all: 1
|
||||
}
|
||||
params: {
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
ordering: '-added',
|
||||
truncate_content: true,
|
||||
tags__id__all: 1,
|
||||
},
|
||||
});
|
||||
return response.data.results;
|
||||
}
|
||||
|
||||
async getManuellDocuments(): Promise<any[]> {
|
||||
const errorTag = this.configService.get<number>('MANUELL_BEARBEITEN_TAG', 6);
|
||||
const errorTag = this.configService.get<number>(
|
||||
'MANUELL_BEARBEITEN_TAG',
|
||||
6,
|
||||
);
|
||||
const response = await this.client.get('/documents/', {
|
||||
params: {
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
ordering: '-added',
|
||||
truncate_content: true,
|
||||
tags__id__all: errorTag
|
||||
}
|
||||
params: {
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
ordering: '-added',
|
||||
truncate_content: true,
|
||||
tags__id__all: errorTag,
|
||||
},
|
||||
});
|
||||
return response.data.results;
|
||||
}
|
||||
@@ -124,7 +136,9 @@ export class PaperlessService {
|
||||
} catch (err: any) {
|
||||
const body = err?.response?.data;
|
||||
if (body) {
|
||||
this.logger.error(`Paperless updateDocument(${id}) Fehlerdetails: ${JSON.stringify(body)}`);
|
||||
this.logger.error(
|
||||
`Paperless updateDocument(${id}) Fehlerdetails: ${JSON.stringify(body)}`,
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
@@ -132,14 +146,14 @@ export class PaperlessService {
|
||||
|
||||
async getDocumentTypes(): Promise<any[]> {
|
||||
const response = await this.client.get('/document_types/', {
|
||||
params: { page_size: 9999 }
|
||||
params: { page_size: 9999 },
|
||||
});
|
||||
return response.data.results;
|
||||
}
|
||||
|
||||
async getTags(): Promise<any[]> {
|
||||
const response = await this.client.get('/tags/', {
|
||||
params: { page_size: 9999 }
|
||||
params: { page_size: 9999 },
|
||||
});
|
||||
return response.data.results;
|
||||
}
|
||||
@@ -156,7 +170,7 @@ export class PaperlessService {
|
||||
|
||||
async getCustomFields(): Promise<any[]> {
|
||||
const response = await this.client.get('/custom_fields/', {
|
||||
params: { page_size: 9999 }
|
||||
params: { page_size: 9999 },
|
||||
});
|
||||
return response.data.results;
|
||||
}
|
||||
@@ -184,10 +198,14 @@ export class PaperlessService {
|
||||
await this.client.delete(`/correspondents/${id}/`);
|
||||
}
|
||||
|
||||
async downloadDocument(id: number, type: 'original' | 'archive' = 'archive'): Promise<Buffer> {
|
||||
const endpoint = type === 'original'
|
||||
? `/documents/${id}/download/`
|
||||
: `/documents/${id}/download/`;
|
||||
async downloadDocument(
|
||||
id: number,
|
||||
type: 'original' | 'archive' = 'archive',
|
||||
): Promise<Buffer> {
|
||||
const endpoint =
|
||||
type === 'original'
|
||||
? `/documents/${id}/download/`
|
||||
: `/documents/${id}/download/`;
|
||||
const response = await this.client.get(endpoint, {
|
||||
responseType: 'arraybuffer',
|
||||
params: type === 'original' ? { original: true } : {},
|
||||
@@ -204,10 +222,14 @@ export class PaperlessService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getDocumentPdfStream(id: number, type: 'original' | 'archive' = 'archive'): Promise<any> {
|
||||
const endpoint = type === 'original'
|
||||
? `/documents/${id}/download/`
|
||||
: `/documents/${id}/download/`;
|
||||
async getDocumentPdfStream(
|
||||
id: number,
|
||||
type: 'original' | 'archive' = 'archive',
|
||||
): Promise<any> {
|
||||
const endpoint =
|
||||
type === 'original'
|
||||
? `/documents/${id}/download/`
|
||||
: `/documents/${id}/download/`;
|
||||
const response = await this.client.get(endpoint, {
|
||||
responseType: 'stream',
|
||||
params: type === 'original' ? { original: true } : {},
|
||||
@@ -222,7 +244,9 @@ export class PaperlessService {
|
||||
}
|
||||
|
||||
async addNote(id: number, note: string): Promise<any> {
|
||||
const response = await this.client.post(`/documents/${id}/notes/`, { note });
|
||||
const response = await this.client.post(`/documents/${id}/notes/`, {
|
||||
note,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -248,7 +272,7 @@ export class PaperlessService {
|
||||
*/
|
||||
async validateAsnNotExists(interneBelegnummer: string): Promise<void> {
|
||||
if (!interneBelegnummer) return;
|
||||
|
||||
|
||||
// Logic like in PaperlessTaskProcessorService
|
||||
const asnNum = parseInt(interneBelegnummer.replace(/-/g, ''), 10);
|
||||
if (isNaN(asnNum)) return;
|
||||
@@ -271,7 +295,7 @@ export class PaperlessService {
|
||||
params: { archive_serial_number: asn, page_size: 5 },
|
||||
});
|
||||
if ((response.data.count ?? 0) === 0) return null;
|
||||
const match = (response.data.results as any[] ?? []).find(
|
||||
const match = ((response.data.results as any[]) ?? []).find(
|
||||
(doc: any) => Number(doc.archive_serial_number) === asn,
|
||||
);
|
||||
return match ? Number(match.id) : null;
|
||||
@@ -281,7 +305,10 @@ export class PaperlessService {
|
||||
* Liefert die Paperless-Doc-ID des passenden Dokuments oder null.
|
||||
* Paperless kann den custom_fields-Filter ignorieren — daher manuell verifizieren.
|
||||
*/
|
||||
async findDocumentIdByCustomField(fieldId: number, value: string): Promise<number | null> {
|
||||
async findDocumentIdByCustomField(
|
||||
fieldId: number,
|
||||
value: string,
|
||||
): Promise<number | null> {
|
||||
const response = await this.client.get('/documents/', {
|
||||
params: {
|
||||
[`custom_fields__${fieldId}__value__iexact`]: value,
|
||||
@@ -291,9 +318,14 @@ export class PaperlessService {
|
||||
});
|
||||
if ((response.data.count ?? 0) === 0) return null;
|
||||
const valueLower = value.toLowerCase();
|
||||
const match = (response.data.results as any[] ?? []).find((doc: any) =>
|
||||
(Array.isArray(doc.custom_fields) ? doc.custom_fields as any[] : []).some(
|
||||
(cf: any) => cf.field === fieldId && String(cf.value ?? '').toLowerCase() === valueLower,
|
||||
const match = ((response.data.results as any[]) ?? []).find((doc: any) =>
|
||||
(Array.isArray(doc.custom_fields)
|
||||
? (doc.custom_fields as any[])
|
||||
: []
|
||||
).some(
|
||||
(cf: any) =>
|
||||
cf.field === fieldId &&
|
||||
String(cf.value ?? '').toLowerCase() === valueLower,
|
||||
),
|
||||
);
|
||||
return match ? Number(match.id) : null;
|
||||
|
||||
Reference in New Issue
Block a user