Files
AgrarmonitorConnector/docs/zeitbuchung-analyse.md
T
bjoernpoettkerandClaude Opus 5.5 d703e46125 Arbeitszeit: Zeitbuchungen anlegen, aendern und loeschen
createZeitbuchung, updateZeitbuchung und deleteZeitbuchung buchen
Taetigkeitsbloecke und Pausen ueber module/arbeitszeit/api/. Die erste
Buchung eines Tages legt per nachtragen.php die Schicht an, weitere
Buchungen kommen in die vorhandene Schicht, die bei Bedarf verlaengert
wird. Die Kostenstelle wird an die Notiz gehaengt. Unbekannte
Taetigkeiten werden vorher abgelehnt, weil Agrarmonitor sie ohne Fehler
speichert. Ablehnungen kommen als ZeitbuchungError mit Grund.

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

13 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. Entscheidungen (14.10.2026)

  • Kostenstelle: wird als Zeile Kostenstelle: … an die Notiz gehängt.
  • Pausen: werden als Agrarmonitor-Pause übertragen (editPauseForm.php mit shiftID legt die Pause an, updatePause.php mit neu: 1 speichert sie, deletePause.php {pauseID, employeeID, date} löscht). Eine Pause braucht eine vorhandene Schicht.
  • Überschneidungen: Das Buchen in Agrarmonitor selbst wird für die Mitarbeiter abgeschaltet; der Connector prüft keine Überschneidungen.
  • Umsetzung: createZeitbuchung, updateZeitbuchung, deleteZeitbuchung mit ref = verteilung:<mitarbeiterId>:<datum>:<id> bzw. pause:….
  • Notiz ändern: Notizen löscht module/notiz/api/delete.php {noteID} (Antwort true). Beim Ändern wird die neue Notiz gespeichert und danach die alte gelöscht.

Echttest (14.10.2026, Mitarbeiter 3309, Tag 11.10.2026)

Über den Connector (eigenes, freigeschaltetes Gerät) angelegt: Block 08:00–09:00 mit Notiz und Kostenstelle (per Nachtragen), Pause 09:00–09:15, Block 09:15–10:00 mit Maschine 5033; danach Block 1 auf 08:55 und Pause auf 09:10 geändert, Notiz ersetzt; alles wieder gelöscht, der Tag ist danach leer. Ergebnis wie erwartet.

Fund: Agrarmonitor speichert eine unbekannte taetigkeit_id (999999) ohne Fehler – es entsteht eine Verteilung ohne Tätigkeit. Der Connector prüft die Tätigkeit deshalb gegen die Auswahl im Buchungsformular (nachtragen-form.php bzw. editVerteilungForm.php) und wirft sonst ZeitbuchungError (grund: 'taetigkeit nicht buchbar'). Maschinen-IDs werden nicht geprüft, weil das Formular nur eine Teilmenge der Maschinen anbietet.

5. Offene Fragen

  1. Pause als erste Buchung des Tages: Ohne Schicht kann keine Pause gebucht werden (ZeitbuchungError). Die App überträgt Blöcke beim Beenden; eine Pause vor dem ersten Tätigkeitsblock landet dann bei needs_manual.
  2. Über Mitternacht: Der Connector trägt bis 23:59 nach und korrigiert Verteilung und Schicht danach auf die echten Zeiten – im Mock getestet, im echten System nicht.
  3. 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).
  4. Tätigkeitsauswahl: Das Buchungsformular bietet nur aktive Tätigkeiten an; Abwesenheiten (Bereich 900) und „800 Pause“ sollten in der App vermutlich ausgeblendet werden.
  5. Verfasser der Notiz: verfasser_id ist der Agrarmonitor-Benutzer des Connectors, nicht der Mitarbeiter.