Files
AgrarmonitorConnector/docs/zeitbuchung-analyse.md
T
bjoernpoettkerandClaude Opus 5.5 659fdb5293 Arbeitszeit: Arbeitstag beginnen, beenden, aendern und loeschen
beginneArbeitstag legt per saveSchicht.php eine laufende Schicht fuer
beliebige Mitarbeiter an (der Menuepunkt "Arbeitstag beginnen" wirkt nur
fuer den angemeldeten Benutzer). Damit kann auch eine Pause die erste
Buchung des Tages sein. Zeitbuchungen lassen laufende Schichten offen
und ziehen nur den Beginn bei Bedarf vor.

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

147 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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}` | `{"action":"link","target":"arbeitszeit"}`; Schicht samt Inhalt gelöscht |
| **Arbeitstag beginnen** (laufende Schicht) | `module/auftraege/api/saveSchicht.php` `{vorgang_id: "-1", mitarbeiter_id, formdata: {timestamp_start, timestamp_ende: ""}}` | neue `schicht_id` als Text; Schicht läuft (`timestamp_ende` `0000-00-00 00:00:00`, `beendet: 0`) |
| Menü „Arbeitstag beginnen/beenden“ | `module/arbeitszeit/api/bueroAnundAbmelden.php` `{employeeID}` | `true` – wirkt nur für den angemeldeten Benutzer (Mitarbeiter 6); mit anderer `employeeID` passiert nichts |
| 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.
- **Arbeitstag:** Beim Tagesbeginn legt `beginneArbeitstag` eine laufende Schicht an (`saveSchicht.php`, für beliebige Mitarbeiter). Damit kann auch eine Pause die erste Buchung sein. `updateSchicht.php` mit leerem `timestampEnd` lässt eine Schicht laufen, mit Ende wird sie beendet (`beendet: 1`); Tätigkeiten und Pausen in einer laufenden Schicht beenden sie nicht (Echttest 14.10.2026, Mitarbeiter 3309).
## 5. Offene Fragen
1. **Ü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.
2. **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`).
3. **Tätigkeitsauswahl:** Das Buchungsformular bietet nur aktive Tätigkeiten an; Abwesenheiten (Bereich 900) und „800 Pause“ sollten in der App vermutlich ausgeblendet werden.
4. **Verfasser der Notiz:** `verfasser_id` ist der Agrarmonitor-Benutzer des Connectors, nicht der Mitarbeiter.