Files
paperlessmanager/paperless-backend/src/paperless/paperless-processor.service.ts
T
bjoernpoettker dad0136365
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
chore: apply ESLint auto-fix across entire backend
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>
2026-06-08 09:02:02 +02:00

251 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DocumentField } from '../database/entities/document-field.entity';
import { DocumentType } from '../database/entities/document-type.entity';
import { PaperlessService } from './paperless.service';
import { PostprocessingService } from '../postprocessing/postprocessing.service';
@Injectable()
export class PaperlessProcessorService {
private readonly logger = new Logger(PaperlessProcessorService.name);
constructor(
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>,
) {}
@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 ?? []);
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).`,
);
for (const doc of documents) {
try {
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)}`,
);
}
}
}
} catch (err) {
this.logger.error('Fehler bei der Dokumentenverarbeitung:', err);
}
}
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.`,
);
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),
});
return updated;
}
const docTypeConfig = await this.docTypeRepo.findOne({
where: { DocumentTypeId: doc.document_type },
});
if (!docTypeConfig) {
this.logger.warn(
`Konfiguration für DocumentType ${doc.document_type} nicht in der Datenbank gefunden.`,
);
return null;
}
const fieldsConfig = await this.docFieldRepo.find({
where: { DocumentType: doc.document_type },
});
if (fieldsConfig.length === 0) {
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,
});
return updated;
}
const currentCustomFields = doc.custom_fields || [];
const newCustomFields = [...currentCustomFields];
let isAllRequiredFilled = true;
for (const fieldConf of fieldsConfig) {
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.`,
);
continue;
}
const existingField = newCustomFields.find(
(f) => f.field === customFieldId,
);
let isFilled = false;
if (existingField) {
isFilled = existingField.value !== null && existingField.value !== '';
} else {
newCustomFields.push({ field: customFieldId, value: null });
}
if (fieldConf.IsRequired && !isFilled) {
isAllRequiredFilled = false;
}
} else {
if (fieldConf.IsRequired) {
let isFilled = false;
switch (fieldConf.Type) {
case 1:
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;
break;
case 5:
isFilled = !!doc.title;
break;
default:
isFilled = true;
}
if (!isFilled) {
isAllRequiredFilled = false;
}
}
}
}
const tagsSet = new Set<number>(doc.tags || []);
if (isAllRequiredFilled) {
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);
}
tagsSet.add(17);
// Titel-Template auflösen
const updatePayload: any = {
custom_fields: newCustomFields,
tags: Array.from(tagsSet),
};
if (docTypeConfig.TitelTemplate) {
let title = docTypeConfig.TitelTemplate;
// Prüfen ob alle im Template referenzierten Custom Fields ausgefüllt sind
const placeholderRegex = /\{\{CUSTOM\[(\d+)\]\}\}/g;
let allFilled = true;
let match: RegExpExecArray | null;
while ((match = placeholderRegex.exec(title)) !== null) {
const fieldId = parseInt(match[1], 10);
const cf = newCustomFields.find((f) => f.field === fieldId);
if (!cf || cf.value == null || cf.value === '') {
allFilled = false;
break;
}
}
if (allFilled) {
for (const cf of newCustomFields) {
const placeholder = `{{CUSTOM[${cf.field}]}}`;
if (title.includes(placeholder)) {
title = title.replaceAll(
placeholder,
cf.value != null ? String(cf.value) : '',
);
}
}
if (title.includes('{{DATE}}')) {
const created = doc.created ? new Date(doc.created) : new Date();
const dateStr = `${String(created.getDate()).padStart(2, '0')}.${String(created.getMonth() + 1).padStart(2, '0')}.${created.getFullYear()}`;
title = title.replaceAll('{{DATE}}', dateStr);
}
if (title.includes('{{ZEITSTEMPEL}}')) {
const now = new Date();
const ts = `${String(now.getDate()).padStart(2, '0')}.${String(now.getMonth() + 1).padStart(2, '0')}.${now.getFullYear()} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
title = title.replaceAll('{{ZEITSTEMPEL}}', ts);
}
updatePayload.title = title;
} else {
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,
);
this.logger.log(
`Dokument ${doc.id} erfolgreich aktualisiert (Alle Pflichtfelder vorhanden: ${isAllRequiredFilled}).`,
);
return updated;
}
}