diff --git a/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts b/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts index 0c826eb..5772729 100644 --- a/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts +++ b/paperless-backend/src/agrarmonitor/agrarmonitor-polling.service.ts @@ -6,6 +6,7 @@ import { AgrarmonitorService } from './agrarmonitor.service'; import { PaperlessService } from '../paperless/paperless.service'; import { Setting } from '../database/entities/setting.entity'; import { Client } from '../database/entities/client.entity'; +import { CorrespondentSetting } from '../database/entities/correspondent-setting.entity'; const INTERN_BELEGNUMMER_FIELD_ID = 7; const EINGANGSDATUM_FIELD_ID = 9; @@ -31,6 +32,7 @@ export class AgrarmonitorPollingService implements OnModuleInit { private readonly paperlessService: PaperlessService, @InjectRepository(Setting) private readonly settingRepo: Repository, @InjectRepository(Client) private readonly clientRepo: Repository, + @InjectRepository(CorrespondentSetting) private readonly corrSettingRepo: Repository, ) {} async onModuleInit() { @@ -128,18 +130,7 @@ export class AgrarmonitorPollingService implements OnModuleInit { (c) => Number(c['ist_lieferant']) === 1 && Number(c['ist_aktiv']) === 1, )) { try { - const lieferantennummer = (customer['lieferantennummer'] as string) ?? ''; - const displayName = this.buildCustomerName(customer, lieferantennummer); - const existing = await this.paperlessService.getCorrespondentByName(displayName); - if (!existing) { - await this.paperlessService.addCorrespondent({ - name: displayName, - match: '', - matching_algorithm: 0, - is_insensitive: true, - owner: null, - }); - } + await this.getOrCreateCorrespondent(customer); } catch (err: unknown) { this.logger.warn(`Korrespondenten-Sync fehlgeschlagen: ${err instanceof Error ? err.message : err}`); } @@ -233,18 +224,7 @@ export class AgrarmonitorPollingService implements OnModuleInit { let correspondentId: number | undefined; const customer = customers.find((c) => Number(c.id) === amDoc.kundenId); if (customer) { - const lieferantennummer = (customer['lieferantennummer'] as string) ?? ''; - 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, - }); - } + const corr = await this.getOrCreateCorrespondent(customer); if (corr) correspondentId = corr.id as number; } @@ -416,17 +396,7 @@ export class AgrarmonitorPollingService implements OnModuleInit { } // 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, - }); - } + const corr = await this.getOrCreateCorrespondent(customer); // Owner aus Client-Tabelle let ownerId: number | undefined; @@ -494,6 +464,71 @@ export class AgrarmonitorPollingService implements OnModuleInit { } } + async syncCorrespondentIds(): Promise<{ total: number; matched: number; unmatched: number }> { + let amClient: Awaited>; + try { + amClient = await this.agrarmonitorService.getClient(); + } catch (err: unknown) { + throw new Error(`Connector-Fehler: ${err instanceof Error ? err.message : 'unbekannt'}`); + } + + const customers = await amClient.fetchCustomers(); + const lieferantMap = new Map(); + for (const c of customers) { + const nr = String(c['lieferantennummer'] ?? '').trim(); + if (nr) lieferantMap.set(nr, Number(c.id)); + } + + const allCorrespondents: any[] = []; + let page = 1; + while (true) { + const resp = await this.paperlessService.getCorrespondents({ page, page_size: 250 }); + allCorrespondents.push(...(resp.results ?? [])); + if (!resp.next) break; + page++; + } + + const lieferantRegex = /\((\d+)\)$/; + let matched = 0; + let unmatched = 0; + + for (const corr of allCorrespondents) { + const m = lieferantRegex.exec(corr.name as string); + if (!m) { unmatched++; continue; } + + const amId = lieferantMap.get(m[1]); + if (amId === undefined) { unmatched++; continue; } + + let setting = await this.corrSettingRepo.findOneBy({ CorrespondentId: corr.id as number }); + if (!setting) { + setting = this.corrSettingRepo.create({ CorrespondentId: corr.id as number, AgrarmonitorId: amId }); + } else { + setting.AgrarmonitorId = amId; + } + await this.corrSettingRepo.save(setting); + matched++; + } + + this.logger.log(`Korrespondenten-Abgleich: ${matched} zugeordnet, ${unmatched} ohne Treffer`); + return { total: allCorrespondents.length, matched, unmatched }; + } + + private async getOrCreateCorrespondent(customer: Record): Promise { + const lieferantennummer = (customer['lieferantennummer'] as string) ?? ''; + 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, + }); + } + return corr; + } + private buildCustomerName(customer: Record, nummer: string): string { const firma = (customer['firma'] as string) ?? ''; const nachname = (customer['nachname'] as string) ?? ''; diff --git a/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts b/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts index c5c5192..df3b0cf 100644 --- a/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts +++ b/paperless-backend/src/agrarmonitor/agrarmonitor.controller.ts @@ -49,4 +49,11 @@ export class AgrarmonitorController { async processUploads() { return this.pollingService.processVerarbeiteteDocuments(); } + + @Post('sync-correspondents') + @HttpCode(200) + @RequirePermissions(Permission.MANAGE_SETTINGS) + async syncCorrespondents() { + return this.pollingService.syncCorrespondentIds(); + } } diff --git a/paperless-backend/src/agrarmonitor/agrarmonitor.module.ts b/paperless-backend/src/agrarmonitor/agrarmonitor.module.ts index f805cfe..3d068fc 100644 --- a/paperless-backend/src/agrarmonitor/agrarmonitor.module.ts +++ b/paperless-backend/src/agrarmonitor/agrarmonitor.module.ts @@ -6,10 +6,11 @@ import { AgrarmonitorController } from './agrarmonitor.controller'; import { PaperlessModule } from '../paperless/paperless.module'; import { Setting } from '../database/entities/setting.entity'; import { Client } from '../database/entities/client.entity'; +import { CorrespondentSetting } from '../database/entities/correspondent-setting.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([Setting, Client]), + TypeOrmModule.forFeature([Setting, Client, CorrespondentSetting]), PaperlessModule, ], providers: [AgrarmonitorService, AgrarmonitorPollingService], diff --git a/paperless-frontend/src/api/settings.ts b/paperless-frontend/src/api/settings.ts index 6819ef6..b083026 100644 --- a/paperless-frontend/src/api/settings.ts +++ b/paperless-frontend/src/api/settings.ts @@ -225,4 +225,6 @@ export const agrarmonitorApi = { api.post('/api/agrarmonitor/run-polling').then((r) => r.data), processUploads: () => api.post('/api/agrarmonitor/process-uploads').then((r) => r.data), + syncCorrespondents: () => + api.post<{ total: number; matched: number; unmatched: number }>('/api/agrarmonitor/sync-correspondents').then((r) => r.data), }; diff --git a/paperless-frontend/src/pages/SettingsPage.tsx b/paperless-frontend/src/pages/SettingsPage.tsx index aff7136..6c8eba5 100644 --- a/paperless-frontend/src/pages/SettingsPage.tsx +++ b/paperless-frontend/src/pages/SettingsPage.tsx @@ -1408,6 +1408,7 @@ function CorrespondentsTab() { const [pageSize, setPageSize] = useState(50); const [searchText, setSearchText] = useState(''); const [createModalOpen, setCreateModalOpen] = useState(false); + const [syncLoading, setSyncLoading] = useState(false); const [form] = Form.useForm(); const load = useCallback(async (page: number, size: number, search?: string) => { @@ -1440,6 +1441,19 @@ function CorrespondentsTab() { } }; + const handleSync = async () => { + setSyncLoading(true); + try { + const result = await agrarmonitorApi.syncCorrespondents(); + message.success(`Abgleich abgeschlossen: ${result.matched} zugeordnet, ${result.unmatched} ohne Treffer (${result.total} gesamt)`); + load(currentPage, pageSize, searchText); + } catch (err) { + message.error('Fehler beim Agrarmonitor-Abgleich'); + } finally { + setSyncLoading(false); + } + }; + const updateAgrarmonitorId = async (id: number, val: number | null) => { try { await settingsApi.updateCorrespondentSetting(id, val); @@ -1497,9 +1511,14 @@ function CorrespondentsTab() { onSearch={(v) => { setSearchText(v); setCurrentPage(1); }} style={{ width: 300 }} /> - + + + +