dad0136365
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>
251 lines
8.2 KiB
TypeScript
251 lines
8.2 KiB
TypeScript
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;
|
||
}
|
||
}
|