diff --git a/paperless-backend/src/agrarmonitor/agrarmonitor-web.service.ts b/paperless-backend/src/agrarmonitor/agrarmonitor-web.service.ts new file mode 100644 index 0000000..02daa4b --- /dev/null +++ b/paperless-backend/src/agrarmonitor/agrarmonitor-web.service.ts @@ -0,0 +1,199 @@ +// paperless-backend/src/agrarmonitor/agrarmonitor-web.service.ts +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { parse } from 'node-html-parser'; +import { AgrarmonitorService } from './agrarmonitor.service'; + +export interface EingangsrechnungEntry { + eingangId: number; + belegNummer: string; + interneBelegNummer: string; + kundenId: number; + betriebId: number; + buchungsDatum: Date | null; + eingangsDatum: Date | null; +} + +@Injectable() +export class AgrarmonitorWebService { + private readonly logger = new Logger(AgrarmonitorWebService.name); + private readonly baseUrl: string; + + constructor( + private readonly agrarmonitorService: AgrarmonitorService, + private readonly configService: ConfigService, + ) { + this.baseUrl = this.configService.get( + 'AGRARMONITOR_BASE_URL', + 'https://admin7.agrarmonitor.de', + ); + } + + async eingangsrechnungenLivesearch(suchstring: string): Promise { + const client = await this.agrarmonitorService.getClient(); + + await client.http.get('/'); + const searchUrl = + `/module/dateien/livesearch.php?suchstring=${encodeURIComponent(suchstring)}` + + `&stammdatum_typ=-1&mobil=-1&sensibel=-1&firma=0&itemsperpage=100000&seite=1`; + const { data: html } = await client.http.get(searchUrl, { responseType: 'text' }); + + const root = parse('' + html + ''); + const table = root.querySelector('table#dateien'); + if (!table) return []; + + const rows = table.querySelectorAll('tbody tr'); + const results: EingangsrechnungEntry[] = []; + + for (const row of rows) { + const tds = row.querySelectorAll('td'); + if (tds.length < 4) continue; + if (!tds[3].text.trim().startsWith('Eingangsrechnungen')) continue; + + const linkEl = tds[3].querySelector('a'); + if (!linkEl) continue; + + const href = linkEl.getAttribute('href') ?? ''; + const eingangId = parseInt(href.split('/').pop() ?? '0', 10); + if (!eingangId) continue; + + const belegText = linkEl.text; + const belegParts = belegText.split(','); + const belegNummer = belegParts[0]?.trim() ?? ''; + + const { data: editHtml } = await client.http.get( + `/module/eingangsrechnungen/api/eingangsrechnungen.php?id=edit&rechnungId=${eingangId}`, + { responseType: 'text' }, + ); + const editRoot = parse(editHtml); + + const interneBelegNummer = + editRoot.querySelector('input[name="lieferscheinnummer"]')?.getAttribute('value') ?? ''; + const kundenId = parseInt( + editRoot + .querySelector('select[name="rgempf"] option[selected]') + ?.getAttribute('value') ?? '0', + 10, + ); + const betriebId = parseInt( + editRoot + .querySelector('select[name="firma_id"] option[selected]') + ?.getAttribute('value') ?? '0', + 10, + ); + + const { data: detailHtml } = await client.http.get( + `/eingangsrechnungen/detail/${eingangId}`, + { responseType: 'text' }, + ); + const detailRoot = parse(detailHtml); + const receivedEl = detailRoot.getElementById('receivedStatus'); + + let eingangsDatum: Date | null = null; + let buchungsDatum: Date | null = null; + + if (receivedEl) { + const eingangsText = receivedEl.text.trim(); + if (eingangsText !== 'Nicht empfangen' && eingangsText.length > 13) { + eingangsDatum = this.parseGermanDate(eingangsText.substring(13)); + } + + const parentText = receivedEl.parentNode?.text ?? ''; + const dashIdx = parentText.lastIndexOf('-'); + const buchenText = dashIdx >= 0 ? parentText.substring(dashIdx + 1).trim() : ''; + if (buchenText !== 'Nicht gebucht' && buchenText.length > 11) { + buchungsDatum = this.parseGermanDate(buchenText.substring(11)); + } + } + + results.push({ eingangId, belegNummer, interneBelegNummer, kundenId, betriebId, buchungsDatum, eingangsDatum }); + } + + return results; + } + + async setEingangsdatum(eingangId: number, _belegNummer: string, datum: Date): Promise { + const client = await this.agrarmonitorService.getClient(); + const params = new URLSearchParams(); + params.append('datum', this.formatGermanDate(datum)); + params.append('receiptID', String(eingangId)); + try { + const res = await client.http.post( + '/module/eingangsrechnungen/api/updateReceived.php', + params.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: `${this.baseUrl}/eingangsrechnungen/detail/${eingangId}`, + Origin: this.baseUrl, + }, + }, + ); + return res.status < 400; + } catch (err: any) { + this.logger.error(`setEingangsdatum(${eingangId}) Fehler: ${err?.message}`); + return false; + } + } + + async setLieferscheinNummer(eingangId: number, lieferscheinNummer: string): Promise { + const client = await this.agrarmonitorService.getClient(); + const { data: editHtml } = await client.http.get( + `/module/eingangsrechnungen/api/eingangsrechnungen.php?id=edit&rechnungId=${eingangId}`, + { responseType: 'text' }, + ); + const editRoot = parse(editHtml); + + const rechnungsnummer = + editRoot.querySelector('input[name="rechnungsnummer"]')?.getAttribute('value') ?? ''; + const rechnungsdatum = + editRoot.querySelector('input[name="rechnungsdatum"]')?.getAttribute('value') ?? ''; + const rgempf = + editRoot + .querySelector('select[name="rgempf"] option[selected]') + ?.getAttribute('value') ?? ''; + const addressName = + editRoot.querySelector('input[name="addressName"]')?.getAttribute('value') ?? ''; + + const params = new URLSearchParams(); + params.append('lieferscheinnummer', lieferscheinNummer); + params.append('rechnungsnummer', rechnungsnummer); + params.append('rechnungsdatum', rechnungsdatum); + params.append('rgempf', rgempf); + params.append('adresstext', addressName); + + try { + const res = await client.http.post( + `/module/eingangsrechnungen/api/eingangsrechnungen.php?id=update&rechnungId=${eingangId}`, + params.toString(), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Referer: `${this.baseUrl}/eingangsrechnungen/detail/${eingangId}`, + Origin: this.baseUrl, + }, + }, + ); + return res.status < 400; + } catch (err: any) { + this.logger.error(`setLieferscheinNummer(${eingangId}) Fehler: ${err?.message}`); + return false; + } + } + + private parseGermanDate(str: string): Date | null { + const parts = str.trim().split('.'); + if (parts.length !== 3) return null; + const [dd, mm, yy] = parts; + const year = parseInt(yy, 10) < 50 ? 2000 + parseInt(yy, 10) : 1900 + parseInt(yy, 10); + const d = new Date(year, parseInt(mm, 10) - 1, parseInt(dd, 10)); + return isNaN(d.getTime()) ? null : d; + } + + private formatGermanDate(date: Date): string { + const dd = String(date.getDate()).padStart(2, '0'); + const mm = String(date.getMonth() + 1).padStart(2, '0'); + const yy = String(date.getFullYear()).slice(-2); + return `${dd}.${mm}.${yy}`; + } +}