Stammdaten: getTaetigkeiten und getAlleMaschinen

getTaetigkeiten liest alle Taetigkeiten (aktiv und inaktiv) mit Bereich
und den Flags Maschinen zuordnen, Abwesenheit und Notiz erzwingen.
getAlleMaschinen liest alle Kategorien und Seiten und fasst Maschinen,
die in mehreren Kategorien stehen, zu einem Eintrag zusammen.
Paging ueber .pagination[data-pages], Abbruch sobald eine Seite keine
neuen Zeilen bringt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bjoernpoettkerandClaude Opus 5.5 committed 2026-10-14 11:11:25 +02:00
1 parent d2c5db7e22
commit 6db6c5b278
10 files changed
+612 -4

No files matched your search

+141
View File
@@ -20,9 +20,11 @@ import type {
Firma,
Logger,
Maschine,
MaschineMitGruppe,
MaschinenKategorie,
Mitarbeiter,
Rechnungsdaten,
Taetigkeit,
} from './types';
type RetryableAxiosRequestConfig = AxiosRequestConfig & {
@@ -57,6 +59,7 @@ type AgrarmonitorCustomerPage = {
export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
public http!: AxiosInstance;
private static readonly maxLivesearchSeiten = 500;
private static readonly s3DateienBaseUrl = 'https://s3-eu-central-1.amazonaws.com/dateien.agrarmonitor.de/07';
private readonly baseUrl: string;
@@ -458,6 +461,49 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
});
}
async getAlleMaschinen(): Promise<MaschineMitGruppe[]> {
const documents = await this.loadLivesearchSeiten(
'/module/maschinen/livesearch.php',
{ suchstring: '', maschinen_aktiv: 1, maschinengruppe: 0 },
'table.maschinen_table tbody tr[data-id]'
);
const maschinen = new Map<number, MaschineMitGruppe>();
for (const document of documents) {
for (const table of Array.from(document.querySelectorAll<HTMLTableElement>('table.maschinen_table'))) {
const gruppenId = this.parseNumber(table.id.replace('maschinen_table_', ''));
const gruppe = this.vorherigeUeberschrift(table)?.textContent?.trim() ?? '';
for (const row of Array.from(table.querySelectorAll<HTMLTableRowElement>('tbody tr[data-id]'))) {
const id = this.parseNumber(row.getAttribute('data-id'));
const vorhanden = maschinen.get(id);
if (vorhanden) {
if (!vorhanden.gruppenIds.includes(gruppenId)) {
vorhanden.gruppenIds.push(gruppenId);
}
continue;
}
const cells = Array.from(row.querySelectorAll<HTMLTableCellElement>('td'));
maschinen.set(id, {
id,
nummer: this.livesearchText(cells[0]),
name: this.livesearchText(cells[1]),
kennzeichen: this.livesearchText(cells[2]),
aktiv: Boolean(cells[4]?.querySelector('.fa-check, .text-green')),
gruppenId,
gruppe,
gruppenIds: [gruppenId],
});
}
}
}
return Array.from(maschinen.values());
}
async getFirmen(): Promise<Firma[]> {
const response = await this.http.get('/einstellungen');
@@ -575,6 +621,101 @@ export class AgrarmonitorConnector implements AgrarmonitorConnectorResult {
});
}
async getTaetigkeiten(): Promise<Taetigkeit[]> {
const documents = await this.loadLivesearchSeiten(
'/module/arbeitszeit/taetigkeiten-livesearch.php',
{ taetigkeiten_aktiv: 1 },
'table.taetigkeit_table tbody tr[data-id]'
);
const taetigkeiten = new Map<number, Taetigkeit>();
for (const document of documents) {
for (const table of Array.from(document.querySelectorAll<HTMLTableElement>('table.taetigkeit_table'))) {
const bereichElement = this.vorherigeUeberschrift(table)?.querySelector('.bereich');
for (const row of Array.from(table.querySelectorAll<HTMLTableRowElement>('tbody tr[data-id]'))) {
const id = this.parseNumber(row.getAttribute('data-id'));
const cells = Array.from(row.querySelectorAll<HTMLTableCellElement>('td'));
if (!id || taetigkeiten.has(id)) {
continue;
}
taetigkeiten.set(id, {
id,
uuid: row.getAttribute('data-activityuuid') ?? '',
nummer: cells[0]?.textContent?.trim() ?? '',
name: cells[1]?.textContent?.trim() ?? '',
aktiv: Boolean(row.querySelector('td.toggle .fa-check, td.toggle .text-green')),
bereichId: this.parseNumber(bereichElement?.getAttribute('data-id')),
bereich: bereichElement?.textContent?.trim() ?? '',
maschineZuordnen: this.parseJaNein(cells[2]?.textContent ?? ''),
abwesenheit: this.parseJaNein(cells[3]?.textContent ?? ''),
notizErzwingen: this.parseJaNein(cells[5]?.textContent ?? ''),
});
}
}
}
return Array.from(taetigkeiten.values());
}
/**
* Laedt eine Livesearch-Liste Seite fuer Seite. Die Seitenzahl steht in
* `.pagination[data-pages]`; zusaetzlich bricht die Schleife ab, sobald eine
* Seite keine neuen Zeilen bringt (Agrarmonitor ignoriert `seite` teils).
*/
private async loadLivesearchSeiten(
url: string,
params: Record<string, string | number>,
rowSelector: string
): Promise<Document[]> {
const documents: Document[] = [];
const bekannteZeilen = new Set<string>();
for (let seite = 1; seite <= AgrarmonitorConnector.maxLivesearchSeiten; seite += 1) {
const response = await this.http.get(url, { params: { ...params, seite } });
await this.saveSession();
const document = this.parseHtmlDocument(response.data);
const neueZeilen = Array.from(document.querySelectorAll(rowSelector))
.map(row => `${row.closest('table')?.id ?? ''}:${row.getAttribute('data-id') ?? row.id}`)
.filter(key => !bekannteZeilen.has(key));
if (neueZeilen.length === 0) {
break;
}
neueZeilen.forEach(key => bekannteZeilen.add(key));
documents.push(document);
const seiten = this.parseNumber(document.querySelector('.pagination')?.getAttribute('data-pages'));
if (seite >= seiten) {
break;
}
}
return documents;
}
private vorherigeUeberschrift(element: Element): Element | null {
let sibling = element.previousElementSibling;
while (sibling && !/^H[1-6]$/.test(sibling.tagName)) {
sibling = sibling.previousElementSibling;
}
return sibling;
}
/** Agrarmonitor zeigt leere Werte in Livesearch-Tabellen als "-" an. */
private livesearchText(cell: HTMLTableCellElement | undefined): string {
const text = cell?.textContent?.trim() ?? '';
return text === '-' ? '' : text;
}
private createHttpClient(): AxiosInstance {
const client = wrapper(
axios.create({
+25
View File
@@ -56,11 +56,13 @@ export interface AgrarmonitorConnectorResult {
getCustomerById(id: number): Promise<AgrarmonitorApiCustomer>;
getMaschinenKategorien(): Promise<MaschinenKategorie[]>;
getMaschinen(gruppenId: number, suchstring?: string): Promise<Maschine[]>;
getAlleMaschinen(): Promise<MaschineMitGruppe[]>;
getFirmen(): Promise<Firma[]>;
getArtikelEinheiten(): Promise<ArtikelEinheit[]>;
getArtikelKategorien(): Promise<ArtikelKategorie[]>;
getArtikel(artikelGruppe: number, suchstring?: string): Promise<Artikel[]>;
getMitarbeiter(suchstring?: string, mitarbeitergruppe?: number): Promise<Mitarbeiter[]>;
getTaetigkeiten(): Promise<Taetigkeit[]>;
}
export interface AgrarmonitorFreischaltungStatus {
@@ -170,6 +172,14 @@ export interface Maschine {
aktiv: boolean;
}
export interface MaschineMitGruppe extends Maschine {
/** Erste Maschinenkategorie, in der die Maschine gelistet ist */
gruppenId: number;
gruppe: string;
/** Alle Maschinenkategorien - eine Maschine kann in mehreren stehen */
gruppenIds: number[];
}
export interface Firma {
id: number;
bezeichnung: string;
@@ -199,3 +209,18 @@ export interface Mitarbeiter {
vorname: string;
aktiv: boolean;
}
export interface Taetigkeit {
id: number;
uuid: string;
nummer: string;
name: string;
aktiv: boolean;
bereichId: number;
/** Ueberschrift des Bereichs, z. B. "100 Werkstatt (Mobil sichtbar)" */
bereich: string;
/** Spalte "Maschinen zuordnen" */
maschineZuordnen: boolean;
abwesenheit: boolean;
notizErzwingen: boolean;
}