chore: apply ESLint auto-fix across entire backend
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:
bjoernpoettkerandClaude Sonnet 4.6 committed 2026-06-08 09:02:02 +02:00
1 parent 4c75a1ded2
commit dad0136365
74 files changed
+4022 -1052

No files matched your search

@@ -4,9 +4,7 @@ import { Repository } from 'typeorm';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as crypto from 'crypto';
import {
InboxPostprocessingAction,
} from '../database/entities/inbox-postprocessing-action.entity';
import { InboxPostprocessingAction } from '../database/entities/inbox-postprocessing-action.entity';
import { InboxDocument } from '../database/entities/inbox-document.entity';
import { BarcodeTemplate } from '../database/entities/barcode-template.entity';
import { Task } from '../database/entities/task.entity';
@@ -14,7 +12,11 @@ import { PageCacheService } from '../barcode/page-cache.service';
import { PaperlessService } from '../paperless/paperless.service';
import { MailService } from '../postprocessing/mail.service';
import { ExportService } from '../postprocessing/export.service';
import { applyEditsToTemp, cleanupTemp, extractSectionToTemp } from './edit-applier';
import {
applyEditsToTemp,
cleanupTemp,
extractSectionToTemp,
} from './edit-applier';
import { applyTemplate, buildVariables } from './variable-resolver';
function parseFlexDate(s: string): Date | null {
@@ -27,7 +29,9 @@ function parseFlexDate(s: string): Date | null {
// German: DD.MM.YYYY
const m = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
if (m) {
const d = new Date(`${m[3]}-${m[2].padStart(2, '0')}-${m[1].padStart(2, '0')}`);
const d = new Date(
`${m[3]}-${m[2].padStart(2, '0')}-${m[1].padStart(2, '0')}`,
);
return isNaN(d.getTime()) ? null : d;
}
return null;
@@ -96,21 +100,26 @@ export class InboxPostprocessorService {
const templates = await this.templateRepo.find({ order: { Id: 'ASC' } });
const matchedTemplateIds = [
...new Set(
doc.QrCodes
.map((qr) => {
const tpl = templates.find((t) => {
try { return new RegExp(t.Regex).test(qr.value); }
catch { return false; }
});
return tpl?.Id ?? null;
})
.filter((id): id is number => id !== null),
doc.QrCodes.map((qr) => {
const tpl = templates.find((t) => {
try {
return new RegExp(t.Regex).test(qr.value);
} catch {
return false;
}
});
return tpl?.Id ?? null;
}).filter((id): id is number => id !== null),
),
];
if (matchedTemplateIds.length === 0) return { results: [], totalSections: 0 };
if (matchedTemplateIds.length === 0)
return { results: [], totalSections: 0 };
const actions = await this.actionRepo.find({
where: matchedTemplateIds.map((tid) => ({ BarcodeTemplateId: tid, IsActive: true })),
where: matchedTemplateIds.map((tid) => ({
BarcodeTemplateId: tid,
IsActive: true,
})),
order: { BarcodeTemplateId: 'ASC', Order: 'ASC', Id: 'ASC' },
});
@@ -140,18 +149,31 @@ export class InboxPostprocessorService {
}
// Mapping: Original-Seite → 0-basierter Index in processedPdf
const processedPageIndex = new Map<number, number>();
survivingOriginalPages.forEach((origPage, idx) => processedPageIndex.set(origPage, idx));
survivingOriginalPages.forEach((origPage, idx) =>
processedPageIndex.set(origPage, idx),
);
// QR-Codes mit ihrem Index in der verarbeiteten PDF (gelöschte QR-Seiten herausfiltern)
const qrsWithIdx = doc.QrCodes
.filter(qr => processedPageIndex.has(qr.page))
.map(qr => ({ page: qr.page, value: qr.value, processedIdx: processedPageIndex.get(qr.page)! }))
const qrsWithIdx = doc.QrCodes.filter((qr) =>
processedPageIndex.has(qr.page),
)
.map((qr) => ({
page: qr.page,
value: qr.value,
processedIdx: processedPageIndex.get(qr.page)!,
}))
.sort((a, b) => a.processedIdx - b.processedIdx);
// Split-Punkte: 0-basierte Indizes in der verarbeiteten PDF wo SplitBefore-Templates beginnen
const splitPoints: number[] = [];
for (const qr of qrsWithIdx) {
const tplMatch = templates.find(t => { try { return new RegExp(t.Regex).test(qr.value); } catch { return false; } });
const tplMatch = templates.find((t) => {
try {
return new RegExp(t.Regex).test(qr.value);
} catch {
return false;
}
});
if (tplMatch?.SplitBefore) splitPoints.push(qr.processedIdx);
}
const processedPageCount = survivingOriginalPages.length;
@@ -160,7 +182,13 @@ export class InboxPostprocessorService {
let totalSections = 0;
for (let i = 0; i < qrsWithIdx.length; i++) {
const qr = qrsWithIdx[i];
const tpl = templates.find(t => { try { return new RegExp(t.Regex).test(qr.value); } catch { return false; } });
const tpl = templates.find((t) => {
try {
return new RegExp(t.Regex).test(qr.value);
} catch {
return false;
}
});
if (!tpl) continue;
if (i > 0 && !tpl.SplitBefore) continue;
totalSections++;
@@ -177,7 +205,13 @@ export class InboxPostprocessorService {
if (processedSection && processOnlyOne) break;
const qr = qrsWithIdx[i];
const tpl = templates.find(t => { try { return new RegExp(t.Regex).test(qr.value); } catch { return false; } });
const tpl = templates.find((t) => {
try {
return new RegExp(t.Regex).test(qr.value);
} catch {
return false;
}
});
if (!tpl) continue;
if (i > 0 && !tpl.SplitBefore) continue;
@@ -191,14 +225,27 @@ export class InboxPostprocessorService {
const tplActions = actionsByTemplate.get(tpl.Id);
if (!tplActions || tplActions.length === 0) continue;
const variables = buildVariables({ doc, template: tpl, matchingQrValue: qr.value });
const variables = buildVariables({
doc,
template: tpl,
matchingQrValue: qr.value,
});
// Abschnitt aus der verarbeiteten PDF extrahieren
const startIdx = qr.processedIdx;
const nextSplitIdx = splitPoints.find(sp => sp > startIdx);
const endIdx = nextSplitIdx !== undefined ? nextSplitIdx - 1 : processedPageCount - 1;
const pageIndices = Array.from({ length: endIdx - startIdx + 1 }, (_, i) => startIdx + i);
const sectionPdfPath = await extractSectionToTemp(processedPdfPath, pageIndices);
const nextSplitIdx = splitPoints.find((sp) => sp > startIdx);
const endIdx =
nextSplitIdx !== undefined
? nextSplitIdx - 1
: processedPageCount - 1;
const pageIndices = Array.from(
{ length: endIdx - startIdx + 1 },
(_, i) => startIdx + i,
);
const sectionPdfPath = await extractSectionToTemp(
processedPdfPath,
pageIndices,
);
const defaultFilenameBase = tpl.DateinameTemplate
? applyTemplate(tpl.DateinameTemplate, variables)
@@ -209,7 +256,13 @@ export class InboxPostprocessorService {
if (abortProcessing) break;
if (action.ActionType === 'PAPERLESS') {
try {
const res = await this.runPaperless(action.Content ?? {}, sectionPdfPath, variables, defaultFilenameBase, replaceDuplicate);
const res = await this.runPaperless(
action.Content ?? {},
sectionPdfPath,
variables,
defaultFilenameBase,
replaceDuplicate,
);
results.push({
sectionIndex: currentSectionIndex,
actionId: action.Id,
@@ -227,17 +280,40 @@ export class InboxPostprocessorService {
this.logger.error(
`Aktion PAPERLESS#${action.Id} für Dokument ${doc.Id} fehlgeschlagen: ${err.message}`,
);
results.push({ sectionIndex: currentSectionIndex, actionId: action.Id, actionType: action.ActionType, ok: false, message: err.message });
results.push({
sectionIndex: currentSectionIndex,
actionId: action.Id,
actionType: action.ActionType,
ok: false,
message: err.message,
});
}
} else {
try {
await this.runAction(action, sectionPdfPath, doc, variables, defaultFilenameBase);
results.push({ sectionIndex: currentSectionIndex, actionId: action.Id, actionType: action.ActionType, ok: true });
await this.runAction(
action,
sectionPdfPath,
doc,
variables,
defaultFilenameBase,
);
results.push({
sectionIndex: currentSectionIndex,
actionId: action.Id,
actionType: action.ActionType,
ok: true,
});
} catch (err: any) {
this.logger.error(
`Aktion ${action.ActionType}#${action.Id} für Dokument ${doc.Id} fehlgeschlagen: ${err.message}`,
);
results.push({ sectionIndex: currentSectionIndex, actionId: action.Id, actionType: action.ActionType, ok: false, message: err.message });
results.push({
sectionIndex: currentSectionIndex,
actionId: action.Id,
actionType: action.ActionType,
ok: false,
message: err.message,
});
}
}
}
@@ -265,7 +341,13 @@ export class InboxPostprocessorService {
switch (action.ActionType) {
case 'MAIL':
return this.runMail(content, pdfPath, doc, variables, defaultFilenameBase);
return this.runMail(
content,
pdfPath,
doc,
variables,
defaultFilenameBase,
);
case 'EXPORT':
return this.runExport(content, pdfPath, variables, defaultFilenameBase);
default:
@@ -326,9 +408,13 @@ export class InboxPostprocessorService {
): Promise<PaperlessRunResult> {
// 1. Interne Belegnummer auflösen (Pflicht)
const intNrTpl = String(content.interneBelegnummer ?? '').trim();
if (!intNrTpl) throw new Error('Interne Belegnummer ist in der Aktion nicht konfiguriert');
if (!intNrTpl)
throw new Error(
'Interne Belegnummer ist in der Aktion nicht konfiguriert',
);
const interneBelegnummer = applyTemplate(intNrTpl, variables).trim();
if (!interneBelegnummer) throw new Error('Interne Belegnummer konnte nicht aufgelöst werden');
if (!interneBelegnummer)
throw new Error('Interne Belegnummer konnte nicht aufgelöst werden');
// 2. ASN bestimmen (vor Duplikat-Checks, damit Paperless danach gefragt werden kann)
const asnTpl = String(content.asn ?? '').trim();
@@ -344,12 +430,18 @@ export class InboxPostprocessorService {
}
if (replaceDuplicate) {
this.logger.log(`Duplikat-Check übersprungen (replaceDuplicate=true) für ${interneBelegnummer}`);
this.logger.log(
`Duplikat-Check übersprungen (replaceDuplicate=true) für ${interneBelegnummer}`,
);
} else {
// 3. Duplikat-Check lokal (tasks-Tabelle)
const existingTask = await this.taskRepo.findOneBy({ InterneBelegnummer: interneBelegnummer });
const existingTask = await this.taskRepo.findOneBy({
InterneBelegnummer: interneBelegnummer,
});
if (existingTask) {
this.logger.warn(`Duplikat (lokal): Belegnummer ${interneBelegnummer} bereits in tasks-Tabelle`);
this.logger.warn(
`Duplikat (lokal): Belegnummer ${interneBelegnummer} bereits in tasks-Tabelle`,
);
return {
skipped: true,
message: `Duplikat – Belegnummer ${interneBelegnummer} bereits vorhanden`,
@@ -358,9 +450,14 @@ export class InboxPostprocessorService {
}
// 4. Duplikat-Check Paperless API (Custom Field 7 = InterneBelegnummer)
const cf7DocId = await this.paperlessService.findDocumentIdByCustomField(7, interneBelegnummer);
const cf7DocId = await this.paperlessService.findDocumentIdByCustomField(
7,
interneBelegnummer,
);
if (cf7DocId !== null) {
this.logger.warn(`Duplikat (Paperless CF7): Belegnummer ${interneBelegnummer} bereits in Paperless (Doc ${cf7DocId})`);
this.logger.warn(
`Duplikat (Paperless CF7): Belegnummer ${interneBelegnummer} bereits in Paperless (Doc ${cf7DocId})`,
);
return {
skipped: true,
message: `Duplikat – Belegnummer ${interneBelegnummer} bereits in Paperless`,
@@ -370,9 +467,12 @@ export class InboxPostprocessorService {
// 5. Duplikat-Check Paperless API (archive_serial_number)
if (archiveSerialNumber !== undefined) {
const asnDocId = await this.paperlessService.findDocumentIdByAsn(archiveSerialNumber);
const asnDocId =
await this.paperlessService.findDocumentIdByAsn(archiveSerialNumber);
if (asnDocId !== null) {
this.logger.warn(`Duplikat (Paperless ASN): ${archiveSerialNumber} bereits in Paperless (Doc ${asnDocId})`);
this.logger.warn(
`Duplikat (Paperless ASN): ${archiveSerialNumber} bereits in Paperless (Doc ${asnDocId})`,
);
return {
skipped: true,
message: `Duplikat – ASN ${archiveSerialNumber} bereits in Paperless`,
@@ -384,10 +484,14 @@ export class InboxPostprocessorService {
// 6. Checksum berechnen und prüfen
const buffer = await fs.readFile(pdfPath);
const checksum = crypto.createHash('md5').update(buffer).digest('hex');
const checksumExists = await this.paperlessService.checksumExists(checksum);
const checksumExists =
await this.paperlessService.checksumExists(checksum);
if (checksumExists) {
this.logger.warn(`Duplikat (Checksum): ${checksum}`);
return { skipped: true, message: 'Duplikat (Checksum-Übereinstimmung)' };
return {
skipped: true,
message: 'Duplikat (Checksum-Übereinstimmung)',
};
}
}
@@ -399,22 +503,30 @@ export class InboxPostprocessorService {
: defaultFilenameBase || undefined;
const tags = Array.isArray(content.tags)
? content.tags.map((t: any) => Number(t)).filter((n: number) => Number.isFinite(n))
? content.tags
.map((t: any) => Number(t))
.filter((n: number) => Number.isFinite(n))
: undefined;
const documentType = content.documentType ? Number(content.documentType) : undefined;
const correspondent = content.correspondent ? Number(content.correspondent) : undefined;
const owner = content.owner !== undefined && content.owner !== null && content.owner !== ''
? Number(content.owner)
const documentType = content.documentType
? Number(content.documentType)
: undefined;
const correspondent = content.correspondent
? Number(content.correspondent)
: undefined;
const owner =
content.owner !== undefined &&
content.owner !== null &&
content.owner !== ''
? Number(content.owner)
: undefined;
const rawCustomFields: Record<string, string> | null =
content.customFields && typeof content.customFields === 'object'
? Object.fromEntries(
Object.entries(content.customFields as Record<string, any>).map(([k, v]) => [
k,
applyTemplate(String(v ?? ''), variables),
]),
Object.entries(content.customFields as Record<string, any>).map(
([k, v]) => [k, applyTemplate(String(v ?? ''), variables)],
),
)
: null;
@@ -422,7 +534,9 @@ export class InboxPostprocessorService {
const eingangsdatumTpl = String(content.eingangsdatum ?? '').trim();
let eingangsdatum: Date;
if (eingangsdatumTpl) {
eingangsdatum = parseFlexDate(applyTemplate(eingangsdatumTpl, variables).trim()) ?? new Date();
eingangsdatum =
parseFlexDate(applyTemplate(eingangsdatumTpl, variables).trim()) ??
new Date();
} else if (rawCustomFields?.['9']) {
const parsed = parseFlexDate(rawCustomFields['9']);
if (parsed) {
@@ -441,10 +555,12 @@ export class InboxPostprocessorService {
// - User-konfigurierte Felder aus rawCustomFields
const uploadCustomFields: Record<string, string> = {};
if (rawCustomFields) {
for (const [k, v] of Object.entries(rawCustomFields)) uploadCustomFields[k] = v;
for (const [k, v] of Object.entries(rawCustomFields))
uploadCustomFields[k] = v;
}
uploadCustomFields['7'] = interneBelegnummer;
uploadCustomFields['9'] = `${eingangsdatum.getFullYear()}-${String(eingangsdatum.getMonth() + 1).padStart(2, '0')}-${String(eingangsdatum.getDate()).padStart(2, '0')}`;
uploadCustomFields['9'] =
`${eingangsdatum.getFullYear()}-${String(eingangsdatum.getMonth() + 1).padStart(2, '0')}-${String(eingangsdatum.getDate()).padStart(2, '0')}`;
// 6. Upload
const taskIdRaw = await this.paperlessService.uploadDocument(pdfPath, {
@@ -469,9 +585,10 @@ export class InboxPostprocessorService {
Tags: tags && tags.length > 0 ? tags.join(',') : null,
BetriebID: owner ?? null,
externeBelegnummer: null,
CustomFieldsJson: rawCustomFields && Object.keys(rawCustomFields).length > 0
? JSON.stringify(rawCustomFields)
: null,
CustomFieldsJson:
rawCustomFields && Object.keys(rawCustomFields).length > 0
? JSON.stringify(rawCustomFields)
: null,
Asn: asn || null,
Lieferant: null,
EinkaufID: null,
@@ -482,7 +599,9 @@ export class InboxPostprocessorService {
});
await this.taskRepo.save(task);
this.logger.log(`Dokument hochgeladen und Task angelegt: ${interneBelegnummer} → ${taskId}`);
this.logger.log(
`Dokument hochgeladen und Task angelegt: ${interneBelegnummer} → ${taskId}`,
);
return {};
}
}