feat: implement ProcessVerarbeiteteDocuments (Upload-Check)
Build and Push Multi-Platform Images / build-and-push (push) Successful in 37s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 37s
Ported ProcessVerarbeiteteDocuments() from C# ProcessUploads.cs: - Checks docs tagged "hochgeladen" → eingangsrechnungVorhanden() - On match: livesearch, update title/type/created/correspondent/tags, set custom fields (externeBelegnummer, AgrarmonitorLink), addNote - Tag "hochgeladen" → "fertig" swap; owner via Client.AgrarmonitorBetriebId - 401/403 guard: clearClient() + break (same pattern as runPolling) - Cron: AGRARMONITOR_UPLOAD_CHECK_CRON (default: 0 * * * * *) - New settings: agrarmonitor_tag_hochgeladen, agrarmonitor_link_field - Endpoint: POST /api/agrarmonitor/process-uploads - Frontend: polling-config extended with tagHochgeladen + linkField select, new card "Dokumenten-Verarbeitung" with run button + result display Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,3 +60,4 @@ AGRARMONITOR_API_TOKEN=
|
|||||||
AGRARMONITOR_COOKIE_PATH=./data/agrarmonitor-cookies.json
|
AGRARMONITOR_COOKIE_PATH=./data/agrarmonitor-cookies.json
|
||||||
AGRARMONITOR_ENCRYPTION_KEY= # optional, 16+ Zeichen für Cookie-Verschlüsselung
|
AGRARMONITOR_ENCRYPTION_KEY= # optional, 16+ Zeichen für Cookie-Verschlüsselung
|
||||||
AGRARMONITOR_POLLING_CRON=0 */30 * * * * # Polling-Intervall (Standard: alle 30 Minuten); leer lassen zum Deaktivieren
|
AGRARMONITOR_POLLING_CRON=0 */30 * * * * # Polling-Intervall (Standard: alle 30 Minuten); leer lassen zum Deaktivieren
|
||||||
|
AGRARMONITOR_UPLOAD_CHECK_CRON=0 * * * * * # Upload-Check-Intervall (Standard: einmal pro Minute); leer lassen zum Deaktivieren
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ services:
|
|||||||
- AGRARMONITOR_COOKIE_PATH=${AGRARMONITOR_COOKIE_PATH:-./data/agrarmonitor-cookies.json}
|
- AGRARMONITOR_COOKIE_PATH=${AGRARMONITOR_COOKIE_PATH:-./data/agrarmonitor-cookies.json}
|
||||||
- AGRARMONITOR_ENCRYPTION_KEY=${AGRARMONITOR_ENCRYPTION_KEY:-}
|
- AGRARMONITOR_ENCRYPTION_KEY=${AGRARMONITOR_ENCRYPTION_KEY:-}
|
||||||
- AGRARMONITOR_POLLING_CRON=${AGRARMONITOR_POLLING_CRON:-}
|
- AGRARMONITOR_POLLING_CRON=${AGRARMONITOR_POLLING_CRON:-}
|
||||||
|
- AGRARMONITOR_UPLOAD_CHECK_CRON=${AGRARMONITOR_UPLOAD_CHECK_CRON:-}
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/scans:/mnt/scans
|
- /mnt/scans:/mnt/scans
|
||||||
- /mnt/paperlessmanager:/mnt/data
|
- /mnt/paperlessmanager:/mnt/data
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import { Client } from '../database/entities/client.entity';
|
|||||||
|
|
||||||
const INTERN_BELEGNUMMER_FIELD_ID = 7;
|
const INTERN_BELEGNUMMER_FIELD_ID = 7;
|
||||||
const EINGANGSDATUM_FIELD_ID = 9;
|
const EINGANGSDATUM_FIELD_ID = 9;
|
||||||
|
const EXTERN_BELEGNUMMER_FIELD_ID = 3;
|
||||||
const DOCS_PAGE_SIZE = 500;
|
const DOCS_PAGE_SIZE = 500;
|
||||||
|
const AGRARMONITOR_BASE_URL = 'https://admin7.agrarmonitor.de';
|
||||||
|
|
||||||
export interface PollingResult {
|
export interface PollingResult {
|
||||||
processed: number;
|
processed: number;
|
||||||
@@ -22,6 +24,7 @@ export interface PollingResult {
|
|||||||
export class AgrarmonitorPollingService implements OnModuleInit {
|
export class AgrarmonitorPollingService implements OnModuleInit {
|
||||||
private readonly logger = new Logger(AgrarmonitorPollingService.name);
|
private readonly logger = new Logger(AgrarmonitorPollingService.name);
|
||||||
private pollingRunning = false;
|
private pollingRunning = false;
|
||||||
|
private uploadCheckRunning = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly agrarmonitorService: AgrarmonitorService,
|
private readonly agrarmonitorService: AgrarmonitorService,
|
||||||
@@ -33,6 +36,8 @@ export class AgrarmonitorPollingService implements OnModuleInit {
|
|||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.upsertSetting('agrarmonitor_tag_fertig', '4');
|
await this.upsertSetting('agrarmonitor_tag_fertig', '4');
|
||||||
await this.upsertSetting('agrarmonitor_tag_verbucht', '9');
|
await this.upsertSetting('agrarmonitor_tag_verbucht', '9');
|
||||||
|
await this.upsertSetting('agrarmonitor_tag_hochgeladen', '');
|
||||||
|
await this.upsertSetting('agrarmonitor_link_field', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cron(process.env['AGRARMONITOR_POLLING_CRON'] || '0 */30 * * * *')
|
@Cron(process.env['AGRARMONITOR_POLLING_CRON'] || '0 */30 * * * *')
|
||||||
@@ -41,21 +46,40 @@ export class AgrarmonitorPollingService implements OnModuleInit {
|
|||||||
this.runPolling().catch((err) => this.logger.error('Cron-Polling-Fehler:', err));
|
this.runPolling().catch((err) => this.logger.error('Cron-Polling-Fehler:', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPollingConfig(): Promise<{ tagFertig: string; tagVerbucht: string }> {
|
@Cron(process.env['AGRARMONITOR_UPLOAD_CHECK_CRON'] || '0 * * * * *')
|
||||||
const [fertig, verbucht] = await Promise.all([
|
async scheduledUploadCheck() {
|
||||||
|
if (!process.env['AGRARMONITOR_UPLOAD_CHECK_CRON']) return;
|
||||||
|
this.processVerarbeiteteDocuments().catch((err) => this.logger.error('Cron-Upload-Check-Fehler:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPollingConfig(): Promise<{ tagFertig: string; tagVerbucht: string; tagHochgeladen: string; linkField: string }> {
|
||||||
|
const [fertig, verbucht, hochgeladen, linkField] = await Promise.all([
|
||||||
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }),
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }),
|
||||||
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_verbucht' }),
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_verbucht' }),
|
||||||
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }),
|
||||||
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
tagFertig: fertig?.Wert ?? '4',
|
tagFertig: fertig?.Wert ?? '4',
|
||||||
tagVerbucht: verbucht?.Wert ?? '9',
|
tagVerbucht: verbucht?.Wert ?? '9',
|
||||||
|
tagHochgeladen: hochgeladen?.Wert ?? '',
|
||||||
|
linkField: linkField?.Wert ?? '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePollingConfig(tagFertig: string, tagVerbucht: string): Promise<{ tagFertig: string; tagVerbucht: string }> {
|
async updatePollingConfig(
|
||||||
await this.settingRepo.update({ Tag: 'agrarmonitor_tag_fertig' }, { Wert: tagFertig });
|
tagFertig: string,
|
||||||
await this.settingRepo.update({ Tag: 'agrarmonitor_tag_verbucht' }, { Wert: tagVerbucht });
|
tagVerbucht: string,
|
||||||
return { tagFertig, tagVerbucht };
|
tagHochgeladen: string,
|
||||||
|
linkField: string,
|
||||||
|
): Promise<{ tagFertig: string; tagVerbucht: string; tagHochgeladen: string; linkField: 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 }),
|
||||||
|
]);
|
||||||
|
return { tagFertig, tagVerbucht, tagHochgeladen, linkField };
|
||||||
}
|
}
|
||||||
|
|
||||||
async runPolling(): Promise<PollingResult> {
|
async runPolling(): Promise<PollingResult> {
|
||||||
@@ -210,9 +234,8 @@ export class AgrarmonitorPollingService implements OnModuleInit {
|
|||||||
const customer = customers.find((c) => Number(c.id) === amDoc.kundenId);
|
const customer = customers.find((c) => Number(c.id) === amDoc.kundenId);
|
||||||
if (customer) {
|
if (customer) {
|
||||||
const lieferantennummer = (customer['lieferantennummer'] as string) ?? '';
|
const lieferantennummer = (customer['lieferantennummer'] as string) ?? '';
|
||||||
const searchName = `(${lieferantennummer})`;
|
|
||||||
const displayName = this.buildCustomerName(customer, lieferantennummer);
|
const displayName = this.buildCustomerName(customer, lieferantennummer);
|
||||||
let corr = await this.paperlessService.getCorrespondentByName(searchName);
|
let corr = await this.paperlessService.getCorrespondentByName(displayName);
|
||||||
if (!corr) {
|
if (!corr) {
|
||||||
corr = await this.paperlessService.addCorrespondent({
|
corr = await this.paperlessService.addCorrespondent({
|
||||||
name: displayName,
|
name: displayName,
|
||||||
@@ -264,6 +287,213 @@ export class AgrarmonitorPollingService implements OnModuleInit {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'] };
|
||||||
|
}
|
||||||
|
this.uploadCheckRunning = true;
|
||||||
|
|
||||||
|
const result: PollingResult = { processed: 0, updated: 0, skipped: 0, errors: [] };
|
||||||
|
this.logger.log('Starte Upload-Check');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [hochgeladenSetting, fertigSetting, linkFieldSetting] = await Promise.all([
|
||||||
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_hochgeladen' }),
|
||||||
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_tag_fertig' }),
|
||||||
|
this.settingRepo.findOneBy({ Tag: 'agrarmonitor_link_field' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tagHochgeladenId = parseInt(hochgeladenSetting?.Wert ?? '', 10);
|
||||||
|
const tagFertigId = parseInt(fertigSetting?.Wert ?? '4', 10);
|
||||||
|
const linkFieldId = parseInt(linkFieldSetting?.Wert ?? '', 10);
|
||||||
|
|
||||||
|
if (isNaN(tagHochgeladenId)) {
|
||||||
|
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>>;
|
||||||
|
try {
|
||||||
|
amClient = await this.agrarmonitorService.getClient();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = `Connector-Fehler: ${err instanceof Error ? err.message : 'unbekannt'}`;
|
||||||
|
this.logger.error(msg);
|
||||||
|
return { ...result, errors: [msg] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const docsResponse = await this.paperlessService.getDocuments({
|
||||||
|
page: 1,
|
||||||
|
page_size: DOCS_PAGE_SIZE,
|
||||||
|
truncate_content: true,
|
||||||
|
tags__id__all: tagHochgeladenId,
|
||||||
|
});
|
||||||
|
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.log(`${docs.length} Dokumente laut Paperless im Dateieingang`);
|
||||||
|
|
||||||
|
for (const doc of docs) {
|
||||||
|
result.processed++;
|
||||||
|
|
||||||
|
const interneBelegnummer =
|
||||||
|
((doc.custom_fields as any[]) ?? []).find(
|
||||||
|
(cf: any) => cf.field === INTERN_BELEGNUMMER_FIELD_ID,
|
||||||
|
)?.value as string ?? '';
|
||||||
|
|
||||||
|
if (!interneBelegnummer) {
|
||||||
|
this.logger.log(`Dokument ${doc.id as number} hat keine interne Belegnummer`);
|
||||||
|
result.skipped++;
|
||||||
|
await this.delay(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let vorhanden: boolean;
|
||||||
|
try {
|
||||||
|
vorhanden = await amClient.eingangsrechnungVorhanden(interneBelegnummer);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const status = (err as any)?.response?.status;
|
||||||
|
if (status === 401 || status === 403) {
|
||||||
|
this.agrarmonitorService.clearClient();
|
||||||
|
const msg = `Session abgelaufen (${status}) — Upload-Check abgebrochen`;
|
||||||
|
this.logger.warn(msg);
|
||||||
|
result.errors.push(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const msg = `${interneBelegnummer}: Vorhanden-Check fehlgeschlagen`;
|
||||||
|
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
|
||||||
|
result.errors.push(msg);
|
||||||
|
await this.delay(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vorhanden) {
|
||||||
|
result.skipped++;
|
||||||
|
await this.delay(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Dokument ${interneBelegnummer} ist bereits verarbeitet, aktualisiere Paperless`);
|
||||||
|
|
||||||
|
let amResults: Awaited<ReturnType<typeof amClient.eingangsrechnungenLivesearch>>;
|
||||||
|
try {
|
||||||
|
amResults = await amClient.eingangsrechnungenLivesearch(interneBelegnummer);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const status = (err as any)?.response?.status;
|
||||||
|
if (status === 401 || status === 403) {
|
||||||
|
this.agrarmonitorService.clearClient();
|
||||||
|
const msg = `Session abgelaufen (${status}) — Upload-Check abgebrochen`;
|
||||||
|
this.logger.warn(msg);
|
||||||
|
result.errors.push(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const msg = `${interneBelegnummer}: Livesearch-Fehler`;
|
||||||
|
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`);
|
||||||
|
result.skipped++;
|
||||||
|
await this.delay(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amDoc = amResults[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Kundendaten abrufen
|
||||||
|
const customer = await amClient.getCustomerById(amDoc.kundenId);
|
||||||
|
const lieferantennummer = (customer['lieferantennummer'] as string) ?? '';
|
||||||
|
if (!lieferantennummer) {
|
||||||
|
this.logger.log(`Kunde ${amDoc.kundenId} hat keine Lieferantennummer — Dokument wird übersprungen`);
|
||||||
|
result.skipped++;
|
||||||
|
await this.delay(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Korrespondent ermitteln oder anlegen
|
||||||
|
const displayName = this.buildCustomerName(customer, lieferantennummer);
|
||||||
|
let corr = await this.paperlessService.getCorrespondentByName(displayName);
|
||||||
|
if (!corr) {
|
||||||
|
corr = await this.paperlessService.addCorrespondent({
|
||||||
|
name: displayName,
|
||||||
|
match: '',
|
||||||
|
matching_algorithm: 0,
|
||||||
|
is_insensitive: true,
|
||||||
|
owner: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner aus Client-Tabelle
|
||||||
|
let ownerId: number | undefined;
|
||||||
|
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]))];
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
if (!isNaN(linkFieldId)) {
|
||||||
|
this.setCustomField(
|
||||||
|
existingFields,
|
||||||
|
linkFieldId,
|
||||||
|
`${AGRARMONITOR_BASE_URL}/rechnungen/detail/${amDoc.eingangId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateData: Record<string, any> = {
|
||||||
|
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 (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.addNote(
|
||||||
|
doc.id as number,
|
||||||
|
`Beleg in Agrarmonitor verarbeitet: ${new Date().toLocaleString('de-DE')}`,
|
||||||
|
);
|
||||||
|
this.logger.log(`Beleg ${interneBelegnummer} auf AMfertig gesetzt`);
|
||||||
|
result.updated++;
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const msg = `${interneBelegnummer}: Update-Fehler`;
|
||||||
|
this.logger.error(`${msg}: ${err instanceof Error ? err.message : err}`);
|
||||||
|
result.errors.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`Upload-Check abgeschlossen: ${result.processed} geprüft, ${result.updated} aktualisiert, ` +
|
||||||
|
`${result.skipped} übersprungen, ${result.errors.length} Fehler`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.uploadCheckRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCustomField(fields: any[], fieldId: number, value: any): void {
|
||||||
|
const existing = fields.find((f) => f.field === fieldId);
|
||||||
|
if (existing) {
|
||||||
|
existing.value = value;
|
||||||
|
} else {
|
||||||
|
fields.push({ field: fieldId, value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 firma = (customer['firma'] as string) ?? '';
|
||||||
const nachname = (customer['nachname'] as string) ?? '';
|
const nachname = (customer['nachname'] as string) ?? '';
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ export class AgrarmonitorController {
|
|||||||
|
|
||||||
@Put('polling-config')
|
@Put('polling-config')
|
||||||
@RequirePermissions(Permission.MANAGE_SETTINGS)
|
@RequirePermissions(Permission.MANAGE_SETTINGS)
|
||||||
async updatePollingConfig(@Body() body: { tagFertig: string; tagVerbucht: string }) {
|
async updatePollingConfig(@Body() body: { tagFertig: string; tagVerbucht: string; tagHochgeladen: string; linkField: string }) {
|
||||||
return this.pollingService.updatePollingConfig(body.tagFertig, body.tagVerbucht);
|
return this.pollingService.updatePollingConfig(body.tagFertig, body.tagVerbucht, body.tagHochgeladen, body.linkField);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('run-polling')
|
@Post('run-polling')
|
||||||
@@ -42,4 +42,11 @@ export class AgrarmonitorController {
|
|||||||
async runPolling() {
|
async runPolling() {
|
||||||
return this.pollingService.runPolling();
|
return this.pollingService.runPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('process-uploads')
|
||||||
|
@HttpCode(200)
|
||||||
|
@RequirePermissions(Permission.MANAGE_SETTINGS)
|
||||||
|
async processUploads() {
|
||||||
|
return this.pollingService.processVerarbeiteteDocuments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ export interface AgrarmonitorStatusData {
|
|||||||
export interface AgrarmonitorPollingConfig {
|
export interface AgrarmonitorPollingConfig {
|
||||||
tagFertig: string;
|
tagFertig: string;
|
||||||
tagVerbucht: string;
|
tagVerbucht: string;
|
||||||
|
tagHochgeladen: string;
|
||||||
|
linkField: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgrarmonitorPollingResult {
|
export interface AgrarmonitorPollingResult {
|
||||||
@@ -221,4 +223,6 @@ export const agrarmonitorApi = {
|
|||||||
api.put<AgrarmonitorPollingConfig>('/api/agrarmonitor/polling-config', config).then((r) => r.data),
|
api.put<AgrarmonitorPollingConfig>('/api/agrarmonitor/polling-config', config).then((r) => r.data),
|
||||||
runPolling: () =>
|
runPolling: () =>
|
||||||
api.post<AgrarmonitorPollingResult>('/api/agrarmonitor/run-polling').then((r) => r.data),
|
api.post<AgrarmonitorPollingResult>('/api/agrarmonitor/run-polling').then((r) => r.data),
|
||||||
|
processUploads: () =>
|
||||||
|
api.post<AgrarmonitorPollingResult>('/api/agrarmonitor/process-uploads').then((r) => r.data),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2291,9 +2291,12 @@ function AgrarmonitorTab() {
|
|||||||
const [pollingConfigLoading, setPollingConfigLoading] = useState(false);
|
const [pollingConfigLoading, setPollingConfigLoading] = useState(false);
|
||||||
const [pollingSaving, setPollingSaving] = useState(false);
|
const [pollingSaving, setPollingSaving] = useState(false);
|
||||||
const [pollingRunning, setPollingRunning] = useState(false);
|
const [pollingRunning, setPollingRunning] = useState(false);
|
||||||
|
const [uploadCheckRunning, setUploadCheckRunning] = useState(false);
|
||||||
const [status, setStatus] = useState<AgrarmonitorStatusData | null>(null);
|
const [status, setStatus] = useState<AgrarmonitorStatusData | null>(null);
|
||||||
const [registerResult, setRegisterResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [registerResult, setRegisterResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
const [pollingResult, setPollingResult] = useState<AgrarmonitorPollingResult | null>(null);
|
const [pollingResult, setPollingResult] = useState<AgrarmonitorPollingResult | null>(null);
|
||||||
|
const [uploadCheckResult, setUploadCheckResult] = useState<AgrarmonitorPollingResult | null>(null);
|
||||||
|
const [customFields, setCustomFields] = useState<PaperlessCustomField[]>([]);
|
||||||
|
|
||||||
const handleLoadStatus = async () => {
|
const handleLoadStatus = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -2341,7 +2344,10 @@ function AgrarmonitorTab() {
|
|||||||
}
|
}
|
||||||
}, [pollingForm]);
|
}, [pollingForm]);
|
||||||
|
|
||||||
useEffect(() => { handleLoadPollingConfig(); }, [handleLoadPollingConfig]);
|
useEffect(() => {
|
||||||
|
handleLoadPollingConfig();
|
||||||
|
paperlessApi.getCustomFields().then(setCustomFields).catch(() => {});
|
||||||
|
}, [handleLoadPollingConfig]);
|
||||||
|
|
||||||
const handleSavePollingConfig = async () => {
|
const handleSavePollingConfig = async () => {
|
||||||
const values = await pollingForm.validateFields() as AgrarmonitorPollingConfig;
|
const values = await pollingForm.validateFields() as AgrarmonitorPollingConfig;
|
||||||
@@ -2369,6 +2375,19 @@ function AgrarmonitorTab() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleProcessUploads = async () => {
|
||||||
|
setUploadCheckRunning(true);
|
||||||
|
setUploadCheckResult(null);
|
||||||
|
try {
|
||||||
|
const result = await agrarmonitorApi.processUploads();
|
||||||
|
setUploadCheckResult(result);
|
||||||
|
} catch {
|
||||||
|
message.error('Upload-Check fehlgeschlagen');
|
||||||
|
} finally {
|
||||||
|
setUploadCheckRunning(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderStatusTag = (value: boolean | null, labelTrue: string, labelFalse: string) => {
|
const renderStatusTag = (value: boolean | null, labelTrue: string, labelFalse: string) => {
|
||||||
if (value === null) return <Tag>–</Tag>;
|
if (value === null) return <Tag>–</Tag>;
|
||||||
return value
|
return value
|
||||||
@@ -2462,6 +2481,16 @@ function AgrarmonitorTab() {
|
|||||||
>
|
>
|
||||||
<Input placeholder="9" style={{ width: 120 }} />
|
<Input placeholder="9" style={{ width: 120 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name="tagHochgeladen" label="Tag-ID: Hochgeladen in Agrarmonitor">
|
||||||
|
<Input placeholder="3" style={{ width: 120 }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="linkField" label="Custom Field: Agrarmonitor-Link">
|
||||||
|
<Select allowClear placeholder="Kein Feld ausgewählt" style={{ width: 280 }}>
|
||||||
|
{customFields.map(f => (
|
||||||
|
<Select.Option key={f.id} value={String(f.id)}>{f.id}: {f.name}</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
<Button type="primary" loading={pollingSaving} onClick={handleSavePollingConfig}>
|
<Button type="primary" loading={pollingSaving} onClick={handleSavePollingConfig}>
|
||||||
Speichern
|
Speichern
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2492,6 +2521,35 @@ function AgrarmonitorTab() {
|
|||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card size="small" title="Dokumenten-Verarbeitung">
|
||||||
|
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 12 }}>
|
||||||
|
Prüft Belege mit Tag "Hochgeladen in Agrarmonitor" und setzt den Tag auf "AMfertig",
|
||||||
|
sobald sie im Agrarmonitor-Buchungssystem erscheinen.
|
||||||
|
</Typography.Text>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Button loading={uploadCheckRunning} onClick={handleProcessUploads}>
|
||||||
|
Jetzt prüfen
|
||||||
|
</Button>
|
||||||
|
{uploadCheckResult && (
|
||||||
|
<div>
|
||||||
|
<Tag color="blue">{uploadCheckResult.processed} geprüft</Tag>
|
||||||
|
<Tag color="success">{uploadCheckResult.updated} aktualisiert</Tag>
|
||||||
|
<Tag>{uploadCheckResult.skipped} übersprungen</Tag>
|
||||||
|
{uploadCheckResult.errors.length > 0 && (
|
||||||
|
<Tag color="error">{uploadCheckResult.errors.length} Fehler</Tag>
|
||||||
|
)}
|
||||||
|
{uploadCheckResult.errors.length > 0 && (
|
||||||
|
<ul style={{ marginTop: 8, paddingLeft: 20 }}>
|
||||||
|
{uploadCheckResult.errors.map((e, i) => (
|
||||||
|
<li key={i} style={{ color: '#ff4d4f' }}>{e}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user