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:
2026-06-08 09:02:02 +02:00
parent 4c75a1ded2
commit dad0136365
74 changed files with 4022 additions and 1052 deletions
@@ -43,9 +43,11 @@ export class AgrarmonitorPollingService implements OnModuleInit {
constructor(
private readonly agrarmonitorService: AgrarmonitorService,
private readonly paperlessService: PaperlessService,
@InjectRepository(Setting) private readonly settingRepo: Repository<Setting>,
@InjectRepository(Setting)
private readonly settingRepo: Repository<Setting>,
@InjectRepository(Client) private readonly clientRepo: Repository<Client>,
@InjectRepository(CorrespondentSetting) private readonly corrSettingRepo: Repository<CorrespondentSetting>,
@InjectRepository(CorrespondentSetting)
private readonly corrSettingRepo: Repository<CorrespondentSetting>,
) {}
async onModuleInit() {
@@ -59,23 +61,34 @@ export class AgrarmonitorPollingService implements OnModuleInit {
@Cron(process.env['AGRARMONITOR_POLLING_CRON'] || '0 */30 * * * *')
async scheduledPolling() {
if (!process.env['AGRARMONITOR_POLLING_CRON']) return;
this.runPolling().catch((err) => this.logger.error('Cron-Polling-Fehler:', err));
this.runPolling().catch((err) =>
this.logger.error('Cron-Polling-Fehler:', err),
);
}
@Cron(process.env['AGRARMONITOR_UPLOAD_CHECK_CRON'] || '0 * * * * *')
async scheduledUploadCheck() {
if (!process.env['AGRARMONITOR_UPLOAD_CHECK_CRON']) return;
this.processVerarbeiteteDocuments().catch((err) => this.logger.error('Cron-Upload-Check-Fehler:', err));
this.processVerarbeiteteDocuments().catch((err) =>
this.logger.error('Cron-Upload-Check-Fehler:', err),
);
}
async getPollingConfig(): Promise<{ tagFertig: string; tagVerbucht: string; tagHochgeladen: string; linkField: string; tagManuell: string }> {
const [fertig, verbucht, hochgeladen, linkField, manuell] = await Promise.all([
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_verbucht' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_manuell' }),
]);
async getPollingConfig(): Promise<{
tagFertig: string;
tagVerbucht: string;
tagHochgeladen: string;
linkField: string;
tagManuell: string;
}> {
const [fertig, verbucht, hochgeladen, linkField, manuell] =
await Promise.all([
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_verbucht' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_manuell' }),
]);
return {
tagFertig: fertig?.Wert ?? '4',
tagVerbucht: verbucht?.Wert ?? '9',
@@ -91,13 +104,34 @@ export class AgrarmonitorPollingService implements OnModuleInit {
tagHochgeladen: string,
linkField: string,
tagManuell: string,
): Promise<{ tagFertig: string; tagVerbucht: string; tagHochgeladen: string; linkField: string; tagManuell: string }> {
): Promise<{
tagFertig: string;
tagVerbucht: string;
tagHochgeladen: string;
linkField: string;
tagManuell: string;
}> {
await Promise.all([
this.settingRepo.update({ Tag: 'agrarmonitor_tag_fertig' }, { Wert: tagFertig }),
this.settingRepo.update({ Tag: 'agrarmonitor_tag_verbucht' }, { Wert: tagVerbucht }),
this.settingRepo.update({ Tag: 'agrarmonitor_tag_hochgeladen' }, { Wert: tagHochgeladen }),
this.settingRepo.update({ Tag: 'agrarmonitor_link_field' }, { Wert: linkField }),
this.settingRepo.update({ Tag: 'agrarmonitor_tag_manuell' }, { Wert: tagManuell }),
this.settingRepo.update(
{ Tag: 'agrarmonitor_tag_fertig' },
{ Wert: tagFertig },
),
this.settingRepo.update(
{ Tag: 'agrarmonitor_tag_verbucht' },
{ Wert: tagVerbucht },
),
this.settingRepo.update(
{ Tag: 'agrarmonitor_tag_hochgeladen' },
{ Wert: tagHochgeladen },
),
this.settingRepo.update(
{ Tag: 'agrarmonitor_link_field' },
{ Wert: linkField },
),
this.settingRepo.update(
{ Tag: 'agrarmonitor_tag_manuell' },
{ Wert: tagManuell },
),
]);
return { tagFertig, tagVerbucht, tagHochgeladen, linkField, tagManuell };
}
@@ -105,11 +139,21 @@ export class AgrarmonitorPollingService implements OnModuleInit {
async runPolling(): Promise<PollingResult> {
if (this.pollingRunning) {
this.logger.warn('Polling läuft bereits, überspringe');
return { processed: 0, updated: 0, skipped: 0, errors: ['Polling bereits aktiv'] };
return {
processed: 0,
updated: 0,
skipped: 0,
errors: ['Polling bereits aktiv'],
};
}
this.pollingRunning = true;
const result: PollingResult = { processed: 0, updated: 0, skipped: 0, errors: [] };
const result: PollingResult = {
processed: 0,
updated: 0,
skipped: 0,
errors: [],
};
this.logger.log('Starte Agrarmonitor-Polling');
try {
@@ -126,7 +170,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
return { ...result, errors: [msg] };
}
let amClient: Awaited<ReturnType<typeof this.agrarmonitorService.getClient>>;
let amClient: Awaited<
ReturnType<typeof this.agrarmonitorService.getClient>
>;
try {
amClient = await this.agrarmonitorService.getClient();
} catch (err: unknown) {
@@ -150,7 +196,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
try {
await this.getOrCreateCorrespondent(customer, Number(customer.id));
} catch (err: unknown) {
this.logger.warn(`Korrespondenten-Sync fehlgeschlagen: ${err instanceof Error ? err.message : err}`);
this.logger.warn(
`Korrespondenten-Sync fehlgeschlagen: ${err instanceof Error ? err.message : err}`,
);
}
}
@@ -162,7 +210,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
});
const docs: any[] = docsResponse?.results ?? [];
if ((docsResponse?.count ?? 0) > DOCS_PAGE_SIZE) {
this.logger.warn(`Mehr als ${DOCS_PAGE_SIZE} Dokumente bereit — nur erste ${DOCS_PAGE_SIZE} werden verarbeitet`);
this.logger.warn(
`Mehr als ${DOCS_PAGE_SIZE} Dokumente bereit — nur erste ${DOCS_PAGE_SIZE} werden verarbeitet`,
);
}
this.logger.log(`${docs.length} Dokumente fertig in Agrarmonitor`);
@@ -170,20 +220,25 @@ export class AgrarmonitorPollingService implements OnModuleInit {
result.processed++;
const interneBelegnummer =
((doc.custom_fields as any[]) ?? []).find(
(((doc.custom_fields as any[]) ?? []).find(
(cf: any) => cf.field === INTERN_BELEGNUMMER_FIELD_ID,
)?.value as string ?? '';
)?.value as string) ?? '';
if (!interneBelegnummer) {
this.logger.log(`Dokument ${doc.id as number} hat keine interne Belegnummer`);
this.logger.log(
`Dokument ${doc.id as number} hat keine interne Belegnummer`,
);
result.skipped++;
await this.delay(500);
continue;
}
let amResults: Awaited<ReturnType<typeof amClient.eingangsrechnungenLivesearch>>;
let amResults: Awaited<
ReturnType<typeof amClient.eingangsrechnungenLivesearch>
>;
try {
amResults = await amClient.eingangsrechnungenLivesearch(interneBelegnummer);
amResults =
await amClient.eingangsrechnungenLivesearch(interneBelegnummer);
} catch (err: unknown) {
const status = (err as any)?.response?.status;
if (status === 401 || status === 403) {
@@ -194,14 +249,18 @@ export class AgrarmonitorPollingService implements OnModuleInit {
break;
}
const msg = `${interneBelegnummer}: Livesearch-Fehler`;
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`${msg}: ${err instanceof Error ? err.message : err}`,
);
result.errors.push(msg);
await this.delay(500);
continue;
}
if (amResults.length === 0) {
this.logger.log(`${interneBelegnummer} nicht in Agrarmonitor gefunden`);
this.logger.log(
`${interneBelegnummer} nicht in Agrarmonitor gefunden`,
);
result.skipped++;
await this.delay(500);
continue;
@@ -219,9 +278,14 @@ export class AgrarmonitorPollingService implements OnModuleInit {
if (!amDoc.interneBelegNummer && interneBelegnummer) {
try {
await amClient.setLieferscheinNummer(amDoc.eingangId, interneBelegnummer);
await amClient.setLieferscheinNummer(
amDoc.eingangId,
interneBelegnummer,
);
} catch (err: unknown) {
this.logger.warn(`${interneBelegnummer}: Lieferscheinnummer setzen fehlgeschlagen: ${err instanceof Error ? err.message : err}`);
this.logger.warn(
`${interneBelegnummer}: Lieferscheinnummer setzen fehlgeschlagen: ${err instanceof Error ? err.message : err}`,
);
}
}
@@ -233,17 +297,24 @@ export class AgrarmonitorPollingService implements OnModuleInit {
const eingangsdatum = new Date(eingangsdatumField.value as string);
if (!isNaN(eingangsdatum.getTime())) {
await amClient.setEingangsdatum(amDoc.eingangId, eingangsdatum);
this.logger.log(`Eingangsdatum für ${interneBelegnummer} gesetzt`);
this.logger.log(
`Eingangsdatum für ${interneBelegnummer} gesetzt`,
);
}
}
}
}
if (amDoc.buchungsDatum) {
try {
let correspondentId: number | undefined;
const customer = customers.find((c) => Number(c.id) === amDoc.kundenId);
const customer = customers.find(
(c) => Number(c.id) === amDoc.kundenId,
);
if (customer) {
const corr = await this.getOrCreateCorrespondent(customer, amDoc.kundenId);
const corr = await this.getOrCreateCorrespondent(
customer,
amDoc.kundenId,
);
if (corr) correspondentId = corr.id as number;
}
@@ -254,28 +325,40 @@ export class AgrarmonitorPollingService implements OnModuleInit {
if (matchedClient) ownerId = matchedClient.PaperlessUserId;
const currentTags: number[] = (doc.tags as number[]) ?? [];
const newTags = [...new Set(currentTags.filter((t) => t !== tagFertigId).concat([tagVerbuchtId]))];
const newTags = [
...new Set(
currentTags
.filter((t) => t !== tagFertigId)
.concat([tagVerbuchtId]),
),
];
const updateData: Record<string, any> = { tags: newTags };
if (correspondentId !== undefined) updateData.correspondent = correspondentId;
if (correspondentId !== undefined)
updateData.correspondent = correspondentId;
if (ownerId !== undefined) updateData.owner = ownerId;
await this.paperlessService.updateDocument(doc.id as number, updateData);
await this.paperlessService.updateDocument(
doc.id as number,
updateData,
);
this.logger.log(`Beleg ${interneBelegnummer} gebucht`);
result.updated++;
} catch (err: unknown) {
const msg = `${interneBelegnummer}: Update-Fehler`;
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`${msg}: ${err instanceof Error ? err.message : err}`,
);
result.errors.push(msg);
}
}
}
await this.delay(500);
}
this.logger.log(
`Polling abgeschlossen: ${result.processed} verarbeitet, ${result.updated} aktualisiert, ` +
`${result.skipped} übersprungen, ${result.errors.length} Fehler`,
`${result.skipped} übersprungen, ${result.errors.length} Fehler`,
);
} finally {
this.pollingRunning = false;
@@ -287,15 +370,30 @@ export class AgrarmonitorPollingService implements OnModuleInit {
async processVerarbeiteteDocuments(): Promise<PollingResult> {
if (this.uploadCheckRunning) {
this.logger.warn('Upload-Check läuft bereits, überspringe');
return { processed: 0, updated: 0, skipped: 0, errors: ['Upload-Check bereits aktiv'] };
return {
processed: 0,
updated: 0,
skipped: 0,
errors: ['Upload-Check bereits aktiv'],
};
}
this.uploadCheckRunning = true;
const result: PollingResult = { processed: 0, updated: 0, skipped: 0, errors: [] };
const result: PollingResult = {
processed: 0,
updated: 0,
skipped: 0,
errors: [],
};
this.logger.log('Starte Upload-Check');
try {
const [hochgeladenSetting, fertigSetting, linkFieldSetting, manuellSetting] = await Promise.all([
const [
hochgeladenSetting,
fertigSetting,
linkFieldSetting,
manuellSetting,
] = await Promise.all([
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }),
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }),
@@ -308,11 +406,15 @@ export class AgrarmonitorPollingService implements OnModuleInit {
const tagManuellId = parseInt(manuellSetting?.Wert ?? '', 10);
if (isNaN(tagHochgeladenId)) {
this.logger.warn('Tag "hochgeladen" nicht konfiguriert — Upload-Check übersprungen');
this.logger.warn(
'Tag "hochgeladen" nicht konfiguriert — Upload-Check übersprungen',
);
return { ...result, errors: ['Tag "hochgeladen" nicht konfiguriert'] };
}
let amClient: Awaited<ReturnType<typeof this.agrarmonitorService.getClient>>;
let amClient: Awaited<
ReturnType<typeof this.agrarmonitorService.getClient>
>;
try {
amClient = await this.agrarmonitorService.getClient();
} catch (err: unknown) {
@@ -329,20 +431,26 @@ export class AgrarmonitorPollingService implements OnModuleInit {
});
const docs: any[] = docsResponse?.results ?? [];
if ((docsResponse?.count ?? 0) > DOCS_PAGE_SIZE) {
this.logger.warn(`Mehr als ${DOCS_PAGE_SIZE} Dokumente hochgeladen — nur erste ${DOCS_PAGE_SIZE} werden geprüft`);
this.logger.warn(
`Mehr als ${DOCS_PAGE_SIZE} Dokumente hochgeladen — nur erste ${DOCS_PAGE_SIZE} werden geprüft`,
);
}
this.logger.log(`${docs.length} Dokumente laut Paperless im Dateieingang`);
this.logger.log(
`${docs.length} Dokumente laut Paperless im Dateieingang`,
);
for (const doc of docs) {
result.processed++;
const interneBelegnummer =
((doc.custom_fields as any[]) ?? []).find(
(((doc.custom_fields as any[]) ?? []).find(
(cf: any) => cf.field === INTERN_BELEGNUMMER_FIELD_ID,
)?.value as string ?? '';
)?.value as string) ?? '';
if (!interneBelegnummer) {
this.logger.log(`Dokument ${doc.id as number} hat keine interne Belegnummer`);
this.logger.log(
`Dokument ${doc.id as number} hat keine interne Belegnummer`,
);
result.skipped++;
await this.delay(500);
continue;
@@ -350,7 +458,8 @@ export class AgrarmonitorPollingService implements OnModuleInit {
let vorhanden: boolean;
try {
vorhanden = await amClient.eingangsrechnungVorhanden(interneBelegnummer);
vorhanden =
await amClient.eingangsrechnungVorhanden(interneBelegnummer);
} catch (err: unknown) {
const status = (err as any)?.response?.status;
if (status === 401 || status === 403) {
@@ -361,7 +470,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
break;
}
const msg = `${interneBelegnummer}: Vorhanden-Check fehlgeschlagen`;
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`${msg}: ${err instanceof Error ? err.message : err}`,
);
result.errors.push(msg);
await this.delay(500);
continue;
@@ -371,7 +482,10 @@ export class AgrarmonitorPollingService implements OnModuleInit {
// Prüfen ob Beleg noch im Dateieingang von Agrarmonitor liegt
let imDateieingang: boolean;
try {
imDateieingang = await amClient.eingangsrechnungImDateieingangVorhanden(interneBelegnummer);
imDateieingang =
await amClient.eingangsrechnungImDateieingangVorhanden(
interneBelegnummer,
);
} catch (err: unknown) {
const status = (err as any)?.response?.status;
if (status === 401 || status === 403) {
@@ -383,7 +497,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
}
// Bei Fehler vorsichtig: nicht verschieben
const msg = `${interneBelegnummer}: Dateieingang-Check fehlgeschlagen`;
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`${msg}: ${err instanceof Error ? err.message : err}`,
);
result.errors.push(msg);
await this.delay(500);
continue;
@@ -399,9 +515,19 @@ export class AgrarmonitorPollingService implements OnModuleInit {
// Weder verbucht noch im Dateieingang → Tags "Manuell bearbeiten" + "Von AM zurück" setzen
if (!isNaN(tagManuellId)) {
const currentTags: number[] = (doc.tags as number[]) ?? [];
const newTags = [...new Set(currentTags.filter(t => t !== tagHochgeladenId).concat([tagManuellId, 19]))];
await this.paperlessService.updateDocument(doc.id as number, { tags: newTags });
this.logger.log(`${interneBelegnummer} nicht mehr in Agrarmonitor — als manuell bearbeiten markiert`);
const newTags = [
...new Set(
currentTags
.filter((t) => t !== tagHochgeladenId)
.concat([tagManuellId, 19]),
),
];
await this.paperlessService.updateDocument(doc.id as number, {
tags: newTags,
});
this.logger.log(
`${interneBelegnummer} nicht mehr in Agrarmonitor — als manuell bearbeiten markiert`,
);
result.updated++;
} else {
result.skipped++;
@@ -410,11 +536,16 @@ export class AgrarmonitorPollingService implements OnModuleInit {
continue;
}
this.logger.log(`Dokument ${interneBelegnummer} ist bereits verarbeitet, aktualisiere Paperless`);
this.logger.log(
`Dokument ${interneBelegnummer} ist bereits verarbeitet, aktualisiere Paperless`,
);
let amResults: Awaited<ReturnType<typeof amClient.eingangsrechnungenLivesearch>>;
let amResults: Awaited<
ReturnType<typeof amClient.eingangsrechnungenLivesearch>
>;
try {
amResults = await amClient.eingangsrechnungenLivesearch(interneBelegnummer);
amResults =
await amClient.eingangsrechnungenLivesearch(interneBelegnummer);
} catch (err: unknown) {
const status = (err as any)?.response?.status;
if (status === 401 || status === 403) {
@@ -425,14 +556,18 @@ export class AgrarmonitorPollingService implements OnModuleInit {
break;
}
const msg = `${interneBelegnummer}: Livesearch-Fehler`;
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`${msg}: ${err instanceof Error ? err.message : err}`,
);
result.errors.push(msg);
await this.delay(500);
continue;
}
if (amResults.length > 1) {
this.logger.log(`Dokument ${interneBelegnummer} ist doppelt vorhanden`);
this.logger.log(
`Dokument ${interneBelegnummer} ist doppelt vorhanden`,
);
result.skipped++;
await this.delay(500);
continue;
@@ -443,29 +578,49 @@ export class AgrarmonitorPollingService implements OnModuleInit {
try {
// Kundendaten abrufen
const customer = await amClient.getCustomerById(amDoc.kundenId);
const lieferantennummer = (customer['lieferantennummer'] as string) ?? '';
const lieferantennummer =
(customer['lieferantennummer'] as string) ?? '';
if (!lieferantennummer) {
this.logger.log(`Kunde ${amDoc.kundenId} hat keine Lieferantennummer — Dokument wird übersprungen`);
this.logger.log(
`Kunde ${amDoc.kundenId} hat keine Lieferantennummer — Dokument wird übersprungen`,
);
result.skipped++;
await this.delay(500);
continue;
}
// Korrespondent ermitteln oder anlegen
const corr = await this.getOrCreateCorrespondent(customer, amDoc.kundenId);
const corr = await this.getOrCreateCorrespondent(
customer,
amDoc.kundenId,
);
// Owner aus Client-Tabelle
let ownerId: number | undefined;
const matchedClient = await this.clientRepo.findOneBy({ AgrarmonitorBetriebId: amDoc.betriebId });
const matchedClient = await this.clientRepo.findOneBy({
AgrarmonitorBetriebId: amDoc.betriebId,
});
if (matchedClient) ownerId = matchedClient.PaperlessUserId;
// Tags: hochgeladen entfernen, fertig hinzufügen
const currentTags: number[] = (doc.tags as number[]) ?? [];
const newTags = [...new Set(currentTags.filter((t) => t !== tagHochgeladenId).concat([tagFertigId]))];
const newTags = [
...new Set(
currentTags
.filter((t) => t !== tagHochgeladenId)
.concat([tagFertigId]),
),
];
// Custom fields aufbauen: bestehende behalten, extern + link setzen
const existingFields: any[] = ((doc.custom_fields as any[]) ?? []).map((f: any) => ({ ...f }));
this.setCustomField(existingFields, EXTERN_BELEGNUMMER_FIELD_ID, amDoc.belegNummer);
const existingFields: any[] = (
(doc.custom_fields as any[]) ?? []
).map((f: any) => ({ ...f }));
this.setCustomField(
existingFields,
EXTERN_BELEGNUMMER_FIELD_ID,
amDoc.belegNummer,
);
if (!isNaN(linkFieldId)) {
this.setCustomField(
existingFields,
@@ -475,16 +630,21 @@ export class AgrarmonitorPollingService implements OnModuleInit {
}
const updateData: Record<string, any> = {
title: (amDoc.dokumentTyp === 0 ? 'ERG ' : 'EGU ') + amDoc.belegNummer,
title:
(amDoc.dokumentTyp === 0 ? 'ERG ' : 'EGU ') + amDoc.belegNummer,
document_type: amDoc.dokumentTyp === 0 ? 1 : 2,
tags: newTags,
custom_fields: existingFields,
};
if (amDoc.belegDatum) updateData.created = amDoc.belegDatum.toISOString().slice(0, 10);
if (amDoc.belegDatum)
updateData.created = amDoc.belegDatum.toISOString().slice(0, 10);
if (corr) updateData.correspondent = corr.id as number;
if (ownerId !== undefined) updateData.owner = ownerId;
await this.paperlessService.updateDocument(doc.id as number, updateData);
await this.paperlessService.updateDocument(
doc.id as number,
updateData,
);
await this.paperlessService.addNote(
doc.id as number,
`Beleg in Agrarmonitor verarbeitet: ${new Date().toLocaleString('de-DE')}`,
@@ -493,7 +653,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
result.updated++;
} catch (err: unknown) {
const msg = `${interneBelegnummer}: Update-Fehler`;
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`${msg}: ${err instanceof Error ? err.message : err}`,
);
result.errors.push(msg);
}
@@ -502,7 +664,7 @@ export class AgrarmonitorPollingService implements OnModuleInit {
this.logger.log(
`Upload-Check abgeschlossen: ${result.processed} geprüft, ${result.updated} aktualisiert, ` +
`${result.skipped} übersprungen, ${result.errors.length} Fehler`,
`${result.skipped} übersprungen, ${result.errors.length} Fehler`,
);
} finally {
this.uploadCheckRunning = false;
@@ -521,16 +683,20 @@ export class AgrarmonitorPollingService implements OnModuleInit {
}
async syncCorrespondentIds(): Promise<SyncCorrespondentsResult> {
let amClient: Awaited<ReturnType<typeof this.agrarmonitorService.getClient>>;
let amClient: Awaited<
ReturnType<typeof this.agrarmonitorService.getClient>
>;
try {
amClient = await this.agrarmonitorService.getClient();
} catch (err: unknown) {
throw new Error(`Connector-Fehler: ${err instanceof Error ? err.message : 'unbekannt'}`);
throw new Error(
`Connector-Fehler: ${err instanceof Error ? err.message : 'unbekannt'}`,
);
}
const customers = await amClient.fetchCustomers();
const lieferantMap = new Map<string, number>(); // lieferantennummer → AM-ID
const kundenMap = new Map<string, number>(); // kundennummer → AM-ID
const kundenMap = new Map<string, number>(); // kundennummer → AM-ID
for (const c of customers) {
const liefNr = String(c['lieferantennummer'] ?? '').trim();
if (liefNr) lieferantMap.set(liefNr, Number(c.id));
@@ -541,14 +707,17 @@ export class AgrarmonitorPollingService implements OnModuleInit {
const allCorrespondents: any[] = [];
let page = 1;
while (true) {
const resp = await this.paperlessService.getCorrespondents({ page, page_size: 250 });
const resp = await this.paperlessService.getCorrespondents({
page,
page_size: 250,
});
allCorrespondents.push(...(resp.results ?? []));
if (!resp.next) break;
page++;
}
const lieferantRegex = /\((\d+)\)$/; // reine Zahl → Lieferantennummer
const kundenRegex = /\(KD(\d+)\)$/; // KD-Prefix → Kundennummer
const lieferantRegex = /\((\d+)\)$/; // reine Zahl → Lieferantennummer
const kundenRegex = /\(KD(\d+)\)$/; // KD-Prefix → Kundennummer
let matched = 0;
let unmatched = 0;
@@ -564,11 +733,19 @@ export class AgrarmonitorPollingService implements OnModuleInit {
if (liefMatch) amId = lieferantMap.get(liefMatch[1]);
}
if (amId === undefined) { unmatched++; continue; }
if (amId === undefined) {
unmatched++;
continue;
}
let setting = await this.corrSettingRepo.findOneBy({ CorrespondentId: corr.id as number });
let setting = await this.corrSettingRepo.findOneBy({
CorrespondentId: corr.id as number,
});
if (!setting) {
setting = this.corrSettingRepo.create({ CorrespondentId: corr.id as number, AgrarmonitorId: amId });
setting = this.corrSettingRepo.create({
CorrespondentId: corr.id as number,
AgrarmonitorId: amId,
});
} else {
setting.AgrarmonitorId = amId;
}
@@ -594,29 +771,47 @@ export class AgrarmonitorPollingService implements OnModuleInit {
for (const [amId, corrIds] of byAmId) {
if (corrIds.length <= 1) continue;
const corrs = await Promise.all(corrIds.map(id => this.paperlessService.getCorrespondent(id)));
const corrs = await Promise.all(
corrIds.map((id) => this.paperlessService.getCorrespondent(id)),
);
const uniqueNames = new Set(corrs.map((c: any) => c.name as string));
if (uniqueNames.size === 1) {
// Gleicher Name — automatisch zusammenführen
const withoutDocs = corrs.filter((c: any) => Number(c.document_count) === 0);
const withoutDocs = corrs.filter(
(c: any) => Number(c.document_count) === 0,
);
const withDocs = corrs.filter((c: any) => Number(c.document_count) > 0);
if (withoutDocs.length > 0) {
for (const toDelete of withoutDocs) {
await this.paperlessService.deleteCorrespondent(toDelete.id as number);
await this.corrSettingRepo.delete({ CorrespondentId: toDelete.id as number });
await this.paperlessService.deleteCorrespondent(
toDelete.id as number,
);
await this.corrSettingRepo.delete({
CorrespondentId: toDelete.id as number,
});
autoMerged++;
this.logger.log(`Duplikat gelöscht (keine Dokumente): ${toDelete.name as string} (ID ${toDelete.id as number})`);
this.logger.log(
`Duplikat gelöscht (keine Dokumente): ${toDelete.name as string} (ID ${toDelete.id as number})`,
);
}
} else {
// Alle haben Dokumente — in den mit den meisten Dokumenten zusammenführen
const sorted = [...withDocs].sort((a: any, b: any) => Number(b.document_count) - Number(a.document_count));
const keep = sorted[0] as any;
const sorted = [...withDocs].sort(
(a: any, b: any) =>
Number(b.document_count) - Number(a.document_count),
);
const keep = sorted[0];
for (const toMerge of sorted.slice(1)) {
await this.mergeCorrespondents(keep.id as number, toMerge.id as number);
await this.mergeCorrespondents(
keep.id as number,
toMerge.id as number,
);
autoMerged++;
this.logger.log(`Duplikat zusammengeführt in ${keep.name as string} (ID ${keep.id as number})`);
this.logger.log(
`Duplikat zusammengeführt in ${keep.name as string} (ID ${keep.id as number})`,
);
}
}
} else {
@@ -634,12 +829,21 @@ export class AgrarmonitorPollingService implements OnModuleInit {
this.logger.log(
`Korrespondenten-Abgleich: ${matched} zugeordnet, ${unmatched} ohne Treffer, ` +
`${autoMerged} automatisch zusammengeführt, ${conflicts.length} Konflikte`,
`${autoMerged} automatisch zusammengeführt, ${conflicts.length} Konflikte`,
);
return { total: allCorrespondents.length, matched, unmatched, autoMerged, conflicts };
return {
total: allCorrespondents.length,
matched,
unmatched,
autoMerged,
conflicts,
};
}
async mergeCorrespondents(keepId: number, deleteId: number): Promise<{ mergedDocuments: number }> {
async mergeCorrespondents(
keepId: number,
deleteId: number,
): Promise<{ mergedDocuments: number }> {
let mergedDocuments = 0;
let page = 1;
while (true) {
@@ -651,7 +855,9 @@ export class AgrarmonitorPollingService implements OnModuleInit {
});
const docs: any[] = resp?.results ?? [];
for (const doc of docs) {
await this.paperlessService.updateDocument(doc.id as number, { correspondent: keepId });
await this.paperlessService.updateDocument(doc.id as number, {
correspondent: keepId,
});
mergedDocuments++;
}
if (!resp?.next) break;
@@ -659,14 +865,21 @@ export class AgrarmonitorPollingService implements OnModuleInit {
}
await this.paperlessService.deleteCorrespondent(deleteId);
await this.corrSettingRepo.delete({ CorrespondentId: deleteId });
this.logger.log(`Korrespondent ${deleteId}${keepId} zusammengeführt (${mergedDocuments} Dokumente)`);
this.logger.log(
`Korrespondent ${deleteId}${keepId} zusammengeführt (${mergedDocuments} Dokumente)`,
);
return { mergedDocuments };
}
private async getOrCreateCorrespondent(customer: Record<string, unknown>, kundenId?: number): Promise<any> {
private async getOrCreateCorrespondent(
customer: Record<string, unknown>,
kundenId?: number,
): Promise<any> {
// Direkter Lookup über gespeicherte Agrarmonitor-ID
if (kundenId !== undefined) {
const setting = await this.corrSettingRepo.findOneBy({ AgrarmonitorId: kundenId });
const setting = await this.corrSettingRepo.findOneBy({
AgrarmonitorId: kundenId,
});
if (setting) {
return { id: setting.CorrespondentId };
}
@@ -688,9 +901,14 @@ export class AgrarmonitorPollingService implements OnModuleInit {
// Link für künftige Läufe speichern
if (corr && kundenId !== undefined) {
let setting = await this.corrSettingRepo.findOneBy({ CorrespondentId: corr.id as number });
let setting = await this.corrSettingRepo.findOneBy({
CorrespondentId: corr.id as number,
});
if (!setting) {
setting = this.corrSettingRepo.create({ CorrespondentId: corr.id as number, AgrarmonitorId: kundenId });
setting = this.corrSettingRepo.create({
CorrespondentId: corr.id as number,
AgrarmonitorId: kundenId,
});
} else {
setting.AgrarmonitorId = kundenId;
}
@@ -700,11 +918,14 @@ export class AgrarmonitorPollingService implements OnModuleInit {
return corr;
}
private buildCustomerName(customer: Record<string, unknown>, nummer: string): string {
private buildCustomerName(
customer: Record<string, unknown>,
nummer: string,
): string {
const firma = (customer['firma'] as string) ?? '';
const nachname = (customer['nachname'] as string) ?? '';
const vorname = (customer['vorname'] as string) ?? '';
const name = firma || (nachname + (vorname ? ', ' + vorname : ''));
const name = firma || nachname + (vorname ? ', ' + vorname : '');
return `${name} (${nummer})`;
}
@@ -712,7 +933,10 @@ export class AgrarmonitorPollingService implements OnModuleInit {
return new Promise((resolve) => setTimeout(resolve, ms));
}
private async upsertSetting(tag: string, defaultValue: string): Promise<void> {
private async upsertSetting(
tag: string,
defaultValue: string,
): Promise<void> {
const existing = await this.settingRepo.findOneBy({ Tag: tag });
if (!existing) {
await this.settingRepo.save(