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:
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 {};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user