Im Außendienst gibt es nicht immer Netz

Keller, Technikräume, Dächer: Ein Techniker muss ohne Verbindung Zeiten erfassen, Notizen machen und Fotos aufnehmen können. Die mobile Flutter-App schreibt deshalb jede Aktion in eine lokale Datenbank, in eine Warteschlange (Outbox), und synchronisiert sie in Stapeln, sobald das Netz zurück ist. Die lokale Datenbank dient der Offline-Arbeit; maßgeblich bleibt der Server.

Die synchronisierten Ereignisse decken den ganzen Einsatz ab:

  • Ankunft, Beginn, Pause, Fortsetzung und Ende der Arbeit;
  • Notizen und strukturierte Messwerte;
  • verwendete oder fehlende Teile;
  • Fotos und Unterschrift des Kunden;
  • Kunde nicht anwesend, Zugang versperrt, Gefahr, erneuter Besuch nötig, Sicherung der Anlage.

Eine Kennung pro Ereignis

Jedes Ereignis erhält auf dem Telefon eine clientEventId (UUID v4), die bei einem erneuten Versuch nie neu erzeugt wird. Dasselbe Ereignis exakt erneut zu senden, bleibt ohne Wirkung, selbst nach Abschluss des Einsatzes: Die Antwort meldet es einfach als Dublette. Dieselbe Kennung mit anderem Inhalt wird abgelehnt (409).

Die App kann also nach einer verlorenen Netzwerkantwort einen ganzen Stapel erneut senden, ohne Dubletten zu riskieren. Ein Stapel umfasst höchstens 100 Ereignisse und ist transaktional: Ist eines davon ungültig, wird keines angelegt. Der Server prüft außerdem die Reihenfolge der Zeiterfassung; eine Pause ohne Beginn oder zwei Beginne hintereinander werden abgelehnt. Ein gemeldetes Teil verringert den Fahrzeugbestand nur einmal, auch wenn die Übertragung wiederholt wird.

Medien werden vor der Annahme geprüft

Vor dem Senden eines Fotos oder einer Unterschrift meldet die App eine Upload-Absicht an: MIME-Typ, Größe und SHA-256-Fingerabdruck. Der Server antwortet mit einer URL samt zufälligem Token, das fünfzehn Minuten gültig und nur einmal verwendbar ist.

Beim Empfang prüft er Token, Größe, Fingerabdruck und die tatsächliche binäre Signatur der Datei. Nur ein geprüftes Medium derselben Organisation und desselben Einsatzes kann danach von einem Einsatzereignis referenziert werden. Die Dateien landen in einem Objektspeicher (S3 oder kompatibel, etwa MinIO), nicht in PostgreSQL.

Ein Echtzeit-Stream, der dort weitermacht, wo er aufgehört hat

Die Dispositionsansichten und die App erhalten Änderungen über einen authentifizierten SSE-Stream, der pro Organisation getrennt ist. Nach einem Abbruch setzt der Client dank des Headers Last-Event-ID beim zuletzt empfangenen Ereignis fort; Ereignisse werden standardmäßig sieben Tage aufbewahrt. Ein Techniker erhält nur die Ereignisse der ihm zugewiesenen Einsätze.

Push-Benachrichtigungen (Firebase Cloud Messaging) bleiben bewusst allgemein: Sie enthalten nur Kennungen, und die App lädt die fachlichen Daten immer über die authentifizierte API nach.

Was Idempotenz verändert

Sie verlagert die Komplexität an die richtige Stelle. Die App muss nicht wissen, ob eine frühere Übertragung angekommen ist: Im Zweifel sendet sie erneut. Der Server garantiert, dass derselbe Vorgang nur einmal erfasst wird.