feat: add AgrarmonitorWebService with livesearch and date setters

This commit is contained in:
2026-05-23 14:37:52 +02:00
parent f4131ebcf0
commit 433b3be7fa
@@ -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<string>(
'AGRARMONITOR_BASE_URL',
'https://admin7.agrarmonitor.de',
);
}
async eingangsrechnungenLivesearch(suchstring: string): Promise<EingangsrechnungEntry[]> {
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<string>(searchUrl, { responseType: 'text' });
const root = parse('<doc>' + html + '</doc>');
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<string>(
`/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<string>(
`/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<boolean> {
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<boolean> {
const client = await this.agrarmonitorService.getClient();
const { data: editHtml } = await client.http.get<string>(
`/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}`;
}
}