From d2c5db7e22b8a614ac57a249e8d70a8ca1215b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20P=C3=B6ttker?= Date: Wed, 14 Oct 2026 11:11:23 +0200 Subject: [PATCH] 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 --- docs/zeitbuchung-analyse.md | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 docs/zeitbuchung-analyse.md diff --git a/docs/zeitbuchung-analyse.md b/docs/zeitbuchung-analyse.md new file mode 100644 index 0000000..c0901d1 --- /dev/null +++ b/docs/zeitbuchung-analyse.md @@ -0,0 +1,131 @@ +# 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 `

100 Werkstatt (Mobil sichtbar)

`, 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 `

Schlepper

` 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×tamp_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.