Files
AgrarmonitorConnector/docs/zeitbuchung-analyse.md
T
bjoernpoettkerandClaude Opus 5.5 d2c5db7e22 Arbeitszeit: Analyse der Zeitbuchung in Agrarmonitor
Fundstellen fuer Taetigkeiten, Maschinen und Kostenstellen sowie die
Buchungsendpunkte unter module/arbeitszeit/api/ (Nachtragen, Verteilung
anlegen/aendern/loeschen, Schicht) mit Feldern, Antworten und offenen
Fragen fuer die Poettker-App.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 11:11:23 +02:00

12 KiB
Raw Blame History

Zeitbuchung in Agrarmonitor – Analyse

Stand: 2026-10-14 · Grundlage: Live-Erkundung in admin7.agrarmonitor.de (Web-Oberfläche, eingeloggt im Browser) und das aktuelle Frontend-Bundle assets/Admin.min.js. Die Kopie Fahrzeugübersicht/public/js/main.js ist veraltet (alte Endpunkte ajax-v3/arbeitszeit.php?id=… liefern heute 404).

Schreibende Requests wurden nur für den Testmitarbeiter 3309 abgesetzt (Freigabe durch Björn) und danach vollständig wieder gelöscht.

1. Stammdaten

Alle Livesearch-Endpunkte liefern ein HTML-Fragment. Paging: Der generische Livesearch-Client liest .pagination[data-pages] aus der Antwort; fehlt das Element oder ist data-pages ≤ 1, gibt es nur eine Seite. Bei Tätigkeiten und Maschinen gibt es aktuell kein Paging (alle Zeilen auf Seite 1, seite=2 liefert dieselben Zeilen).

Tätigkeiten (Tätigkeitsarten)

  • Seite: /arbeitszeit/taetigkeiten („Arbeiten auf dem Betrieb verwalten“)
  • Livesearch: GET /module/arbeitszeit/taetigkeiten-livesearch.php?seite=1&taetigkeiten_aktiv=1
    • taetigkeiten_aktiv: 0 = nur aktive (Standard der Seite), 1 = aktive und inaktive, 2 = nur inaktive
    • aktuell 54 Tätigkeiten (33 aktiv)
  • Aufbau: je „Bereich“ eine Überschrift <h2><b class="bereich" data-id="17">100 Werkstatt (Mobil sichtbar)</b></h2>, darauf folgend table.taetigkeit_table
  • Zeile: tr[data-id][data-activityuuid]
    • data-id = stabile Tätigkeits-ID (wird als taetigkeit_id gebucht)
    • Spalten: 0 Nummer · 1 Bezeichnung · 2 Maschinen zuordnen (Ja/Nein) · 3 Abwesenheit · 4 Als Urlaub werten · 5 Notiz erzwingen · 6 Wartungsplan · 7 Erweiterte Effizienz · 8 Aktiv (td.toggle mit .fa-check = aktiv, .fa-close = inaktiv)
  • Bereiche (aktuell): 100 Werkstatt, 200 Auftragsnebenzeiten, 300 Büro, 400 Hof-/Feldarbeit, 500 Gülle Mann solo, 600 LKW, 700 Rüstzeiten, 800 Pause, 900 Abwesenheit
  • Hinweis: Bezeichnungen sind in Agrarmonitor teils abgeschnitten gespeichert (z. B. „Arbeiten, die keiner Maschine oder anderen Tätigkeit z“) – identisch in Liste und Buchungsformular.

Maschinen

  • Seite: /maschinen
  • Livesearch: GET /module/maschinen/livesearch.php?suchstring=&maschinen_aktiv=1&maschinengruppe=0&seite=1
    • maschinen_aktiv: 0 = nur aktive (Standard der Seite), 1 = aktive und inaktive, 2 = nur inaktive
    • maschinengruppe: 0 = alle Kategorien, -98 = alle Zugmaschinen, -99 = alle Anbaumaschinen, sonst Kategorie-ID
  • Aufbau: je Kategorie eine Überschrift <h2>Schlepper</h2> und table.maschinen_table#maschinen_table_{kategorieId}
  • Zeile: tr[data-id], Spalten: 0 Nummer · 1 Bezeichnung · 2 Kennzeichen · 3 Zähler · 4 Aktiv (.fa-check/.fa-close). Leere Werte werden als - angezeigt.
  • Eine Maschine kann in mehreren Kategorien stehen: 382 Zeilen, aber nur 263 verschiedene Maschinen (99 Maschinen mehrfach).
  • getMaschinen(gruppenId) sendet bereits maschinen_aktiv: 1, liefert also – anders als angenommen – aktive und inaktive Maschinen einer Kategorie. Paging ist für Maschinen nicht relevant (eine Seite).
  • Das Buchungsformular bietet nur eine Teilmenge an (147 Maschinen im Verteilungs-Formular, 243 im Nachtragen-Formular). Welche Maschinen dort fehlen (inaktive? nicht mobil sichtbare?), ist nicht geprüft.

Kostenstellen

  • Es gibt keine Kostenstellen-Stammdaten (keine Seite, keine Livesearch, kein Auswahlfeld).
  • „Kostenstelle“ ist nur ein Freitextfeld (max. 10 Zeichen) an Maschine (/maschinen/detail/{id}, Feld kostenstelle), Mitarbeiter und Artikel. Es wird beim DATEV-Export verwendet (Einstellung „Kostenstellen exportieren“).
  • Das Modul „Betriebszweige“ (/betriebszweige) ist in diesem Mandanten nicht freigeschaltet („Diese Seite existiert nicht“).
  • Eine Arbeitszeit-Buchung hat kein Kostenstellen-Feld. Am nächsten kommt das Feld Projekt (projekt_id, 16 Projekte unter /projekte).

Mitarbeiter

  • getMitarbeiter() nutzt module/mitarbeiter/livesearch.php mit mitarbeiter_aktiv=1 (= aktive und inaktive) und itemsperpage=100000 – reicht für die Zuordnung am_mitarbeiter_id.

2. Buchungsschnittstelle

Gefunden: ja (Web-Oberfläche, JSON-Endpunkte unter module/arbeitszeit/api/). Die Token-API api.agrarmonitor.de/v1 ist nicht öffentlich dokumentiert (Weiterleitung auf agrarmonitor.de/schnittstellen, „Sprechen Sie uns an“); der Connector nutzt dort nur /kunden.

Datenmodell in Agrarmonitor

  • Schicht (Anwesenheit): ein Zeitraum pro Mitarbeiter und Tag (schicht_id), mehrere pro Tag möglich.
  • Verteilung: ein Tätigkeitsblock innerhalb einer Schicht (verteilung_id), mit Tätigkeit, optional Maschine und Projekt, Notizen und Artikel-Ausgängen.
  • Pause: eigener Datensatz in der Schicht (pause_id).
  • Zeit ohne Verteilung erscheint in der Tagesansicht als „Nicht zugeordnet“.
  • Tagesansicht: GET /arbeitszeit/detail/{mitarbeiterId}/{YYYY-MM-DD}; je Schicht table.verteilung_table#verteilung_table_{schichtId}[data-schicht], Zeilen tr.betrieb[id={verteilungId}] (Spalten: Zeit „HH:MM - HH:MM“, Arbeit, Kunde, Notiz, Kontrolle, Dauer). Auftrags-Zeiten stehen als tr.auftrag daneben.

Endpunkte (getestet mit Mitarbeiter 3309 am 14.10.2026)

Alle POST mit Content-Type: application/json, sofern nicht anders angegeben. Zeitformat in Formularen: dd.mm.yy (HH:MM) (z. B. 14.10.26 (08:00)), Ortszeit Europe/Berlin, minutengenau.

Zweck Request Antwort
Tag nachtragen (legt Schicht + Verteilung an) POST module/arbeitszeit/api/nachtragen.php, form-urlencoded (jQuery-Kodierung): form[…], tage[]=YYYY-MM-DD, anzahl_datum=1, planungAusMitarbeiterId=0 {"action":"link","target":"arbeitszeit/plan"} – keine ID
Formular einer bestehenden Verteilung laden (lesend) module/arbeitszeit/api/editVerteilungForm.php {employeeID, date, distributionID} HTML form#verteilung_form[data-id] + form#notiz-form
Neue Verteilung in bestehender Schicht editVerteilungForm.php {employeeID, date, shiftID} HTML-Formular; legt sofort eine leere Verteilung an, ihre ID steht in #verteilung_form[data-id]. Das UI löscht sie bei „Abbrechen“ wieder.
Verteilung speichern/ändern module/arbeitszeit/api/updateVerteilung.php {mitarbeiterId, verteilungId, verteilungForm, notizForm} 200 leerer Body; Validierungsfehler 422 mit JSON, z. B. {"verteilungForm":"ganzertag must be present"}
Verteilung löschen module/arbeitszeit/api/deleteVerteilung.php {distributionID, employeeID, date} 200 null; war es die letzte Verteilung: {"action":"link","target":"arbeitszeit"} und die Schicht wird mitgelöscht (ist_deleted: 1)
Schicht lesen GET module/arbeitszeit/api/getSchicht.php?schicht_id= JSON (timestamp_start, timestamp_ende als YYYY-MM-DD HH:MM:SS, ist_deleted, …)
Schicht ändern module/arbeitszeit/api/updateSchicht.php {shiftID, timestampStart, timestampEnd} (Format dd.mm.yy (HH:MM)) 200 true
Schicht löschen module/arbeitszeit/api/deleteSchicht.php {shiftID, employeeID, date} nicht getestet
Plausibilität prüfen GET module/arbeitszeit/api/checkPlausibelInSchicht.php?timestamp_start&timestamp_ende&mitarbeiterId [] oder {timestamp_start:{code,typ,msg}, …} – nicht getestet

Weitere vorhandene Endpunkte (nicht getestet): updatePause.php/editPauseForm.php/deletePause.php, addNotiz.php ({verteilungId, notizfeld, verfasser_id} → {id,…}), startTaetigkeit.php/startPause.php/beendePause.php/beende-schicht.php (Stempeluhr des eingeloggten Benutzers).

Felder

verteilungForm (alle Werte als String):

Feld Bedeutung
timestamp_start, timestamp_ende dd.mm.yy (HH:MM)
taetigkeit_id Tätigkeits-ID
alte_taetigkeit_id bisherige Tätigkeit (0 bei neuer Verteilung)
maschine_id Maschinen-ID, 0 = keine
projekt_id Projekt-ID, 0 = keines
ganzertag Pflicht (1 oder 0.5; nur bei Abwesenheiten fachlich relevant)

notizForm: notizfeld (Text; nicht leer → wird als neue Notiz an die Verteilung gehängt), verfasser_id (Mitarbeiter-ID des eingeloggten Benutzers, steht im Formular).

nachtragen.php → form[…]: mitarbeitertyp=1, mitarbeiter_id, taetigkeit_id, maschine_id, projekt_id, arbeitszeit_start/arbeitszeit_ende (HH:MM), pause_start/pause_ende (leer = keine Pause), notiz, uebernehmen=1 („In Arbeitszeit mit gewählter Dauer übernehmen“; 2 = ohne Dauer, 0 = nur Planung), kategorie_id=0, arbeitzeit_sollstunden=''.

Beobachtetes Verhalten

  • Nachtragen legt eine Schicht exakt über den angegebenen Zeitraum und darin eine Verteilung an; die Notiz wird als Notizeintrag an der Verteilung gespeichert.
  • Eine neue Verteilung über editVerteilungForm+updateVerteilung verlängert die Schicht nicht (Zugeordnet 1,50 h bei Anwesend 0,75 h). Die Schicht muss per updateSchicht.php angepasst werden.
  • updateVerteilung und deleteVerteilung verkürzen die Schicht dagegen teils automatisch auf die verbleibenden Verteilungen.
  • Ändern und Löschen sind möglich; die Referenz ist die verteilung_id (Zahl).

3. Vorschlag: App-Block → Agrarmonitor

App Agrarmonitor
Mitarbeiter (users.am_mitarbeiter_id) mitarbeiterId / mitarbeiter_id / employeeID
Datum (Start des Arbeitstags) date der Schicht (YYYY-MM-DD)
Start, Ende (timestamptz) timestamp_start/timestamp_ende in Europe/Berlin, dd.mm.yy (HH:MM) – Sekunden gehen verloren
Tätigkeitsart (activity_types.am_id) taetigkeit_id
Maschine (machines.am_id) maschine_id (0 = keine)
Kostenstelle kein Feld – siehe offene Fragen
Notiz notizForm.notizfeld (bzw. form[notiz] beim Nachtragen)
am_ref verteilung_id

Ablauf createZeitbuchung:

  1. Tagesansicht arbeitszeit/detail/{mitarbeiterId}/{datum} laden und Schicht(en) ermitteln.
  2. Keine Schicht → nachtragen.php für genau diesen Block; danach die Tagesansicht erneut laden und die neue Verteilung über Zeit (und Tätigkeit) finden → ref.
  3. Schicht vorhanden → editVerteilungForm.php mit shiftID (liefert die neue ID) → updateVerteilung.php mit den Daten; schlägt das fehl, den Platzhalter per deleteVerteilung.php wieder entfernen. Danach die Schicht per updateSchicht.php auf [frühester Start, spätestes Ende] erweitern.

updateZeitbuchung: updateVerteilung.php mit vollständigem Formular (alte Tätigkeit aus editVerteilungForm mit distributionID), danach Schicht erweitern. Notiz nur senden, wenn sie sich geändert hat (sonst entsteht ein doppelter Notizeintrag).

deleteZeitbuchung: deleteVerteilung.php.

4. Offene Fragen

  1. Kostenstelle: Agrarmonitor hat keine Kostenstellen-Stammdaten und kein Kostenstellen-Feld an der Buchung. Optionen: (a) Kostenstelle in der App streichen, (b) stattdessen Projekte (projekt_id) verwenden, (c) Kostenstelle nur in die Notiz schreiben. getKostenstellen() habe ich deshalb in Schritt 2 nicht umgesetzt.
  2. Pausen: Die App überträgt keine Pausen. Die Lücke zwischen zwei Blöcken erscheint in Agrarmonitor dann als „Nicht zugeordnet“ innerhalb der Schicht und zählt als Anwesenheit/Arbeitszeit. Sollen Pausen als Agrarmonitor-Pause (updatePause.php) übertragen werden, oder soll jede Lücke eine eigene Schicht bekommen?
  3. Parallele Erfassung: Stempeln Mitarbeiter zusätzlich in der Agrarmonitor-App, gibt es am selben Tag schon eine Schicht mit Verteilungen; App-Blöcke würden hineingebucht und können sich überschneiden. Vorher checkPlausibelInSchicht.php prüfen und bei Überschneidung ablehnen?
  4. Über Mitternacht: Verteilungs-Zeitstempel enthalten das Datum, das Nachtragen nur HH:MM. Blöcke über Mitternacht sind ungetestet.
  5. Maschinenpflicht: Tätigkeiten mit „Maschinen zuordnen = Ja“ verlangen im UI eine Maschine (clientseitige Prüfung über data-extra). Ob der Server das ebenfalls erzwingt, ist ungetestet – die App sollte es vorher prüfen (Feld maschineZuordnen in getTaetigkeiten).
  6. Tätigkeitsauswahl: Das Buchungsformular bietet nur aktive Tätigkeiten an; Abwesenheiten (Bereich 900) und „800 Pause“ sollten in der App vermutlich ausgeblendet werden.
  7. Verfasser der Notiz: verfasser_id ist der Agrarmonitor-Benutzer des Connectors, nicht der Mitarbeiter.