Schulprojekt Modul 335 – Mobile Applikation
Klasse: IFZM-2426 009 Rudy-Martin-Fionn-Jannik
- Projektidee
- Technologie-Stack
- Rollen & Zugänge
- Gesamtarchitektur
- Projektstruktur
- Services im Detail
- Frontend-Apps
- Mobile App (Flutter)
- Datenbanken
- Docker & lokale Umgebung
- Port-Übersicht
- Arbeitsweise im Team (Kanban)
- Konventionen & Coding-Standards
| App | Screenshot 1 | Screenshot 2 | Screenshot 3 |
|---|---|---|---|
HR Web :3002hr.mueller / password |
![]() |
![]() |
![]() |
Schichtleiter Web :3003sl.huber / password |
![]() |
||
Admin Web :3001admin / password |
![]() |
Rapport-Bilder (
/rapports): HR wählt einen Mitarbeiter aus dem Dropdown — die App lädt alle via Mobile App hochgeladenen Fotos aus MongoDB und zeigt sie mit Datum, Auftrags-ID und Notiz an. Bild wird per Klick auf "Bild laden" via API Gateway mit JWT-Auth abgerufen.
| Login | Check-in/out | Kalender |
|---|---|---|
![]() |
![]() |
![]() |
| Absenzen | Rapport-Upload |
|---|---|
![]() |
![]() |
Kalender: Zeigt nur veröffentlichte Schichten. Der Schichtleiter muss den Arbeitsplan unter
/planningzuerst veröffentlichen (Status → PUBLISHED), danach erscheinen die Schichten im Mitarbeiter-Kalender.
Rapport-Upload: Foto wird direkt aus der Kamera aufgenommen und viaPOST /api/media/upload(Multipart) durch den API Gateway an den Report/Media Service weitergeleitet, wo es in MongoDB gespeichert wird.
Das Bild ist als Binary-Dokument in der Collection
media_reportsder Datenbankworkforce-mediagespeichert. Einsehbar via Mongo Express unter http://localhost:8081 oder im HR-Web unter/rapports.
| Bereich | Typ | Kurzbeschreibung |
|---|---|---|
| Planning Service | Bugfix | CORS-Doppelheader behoben |
| HR-Web | Bugfix | Stundenfreigabe mit Promise.allSettled stabilisiert |
| SL-Web | Bugfix | Arbeitsplanung: Silent-Fehler sichtbar + Auto-Retry |
| Mobile App | Bugfix | Bild-Upload: Gateway-Buffer, Content-Type, Timeout |
| Report-Media Service | Bugfix | authenticationEntryPoint → 401 statt 403 |
| HR-Web | Feature | Neue Seite /rapports mit Lazy-Image-Loading |
| Docker / CI | Stabilität | Health-Checks und Timeouts angepasst |
| Dokumentation | Docs | 11 Screenshots, Vorschau-Galerie, Flutter-Anleitung |
Problem: HR-Stundenfreigabe und SL-Arbeitsplanung lieferten einen doppelten Access-Control-Allow-Origin-Header — der Browser blockierte daraufhin alle Requests an /api/planning/**.
Ursache: Der Planning Service setzte den CORS-Header auf zwei Wegen gleichzeitig:
- Eigene
corsConfigurationSource-Bean inSecurityConfig @CrossOrigin-Annotation direkt auf dem Controller
Im System gilt: CORS wird ausschliesslich vom API Gateway verwaltet — alle anderen Services müssen cors.disable() setzen.
graph LR
Browser -->|"preflight / request"| GW
subgraph GW["API Gateway :8000\n(einzige CORS-Quelle)"]
CORS["Access-Control-Allow-Origin\nAccess-Control-Allow-Methods\nAccess-Control-Allow-Headers"]
end
GW --> PL["Planning Service\nvorher: cors.disable() FEHLT ❌\nnachher: cors.disable() ✅"]
GW --> AU["Auth Service\ncors.disable() ✅"]
GW --> US["User-Role Service\ncors.disable() ✅"]
GW --> AB["Absence Service\ncors.disable() ✅"]
GW --> RM["Report-Media Service\ncors.disable() ✅"]
style GW fill:#bbf7d0,stroke:#15803d,color:#14532d
style PL fill:#fecaca,stroke:#dc2626,color:#7f1d1d
Fix:
corsConfigurationSource-Bean + Import ausSecurityConfig.javaentfernt,cors.disable()eingesetzt@CrossOrigin-Annotation + Import ausPlanningController.javaentfernt- 2 Regressionstests in
PlanningControllerTestssichern, dass der Service keinen CORS-Header direkt setzt
Problem: Upload von Kamerabildern schlug aus 3 unabhängigen Gründen fehl.
sequenceDiagram
actor App as Flutter App
participant GW as API Gateway :8000
participant RS as Report-Media Service
participant DB as MongoDB
rect rgb(254,202,202)
Note over App,DB: VORHER — 3 parallele Fehlerquellen
App->>GW: POST /api/media/upload (Kamerabild ~4 MB)
Note over GW: ❌ 256 KB In-Memory-Limit → bricht ab
Note over App: ❌ Content-Type: application/octet-stream<br/>(kein Datei-Extension → Auto-detect schlägt fehl)
Note over App: ❌ Timeout: 8 s (globaler Request-Timeout)
end
rect rgb(187,247,208)
Note over App,DB: NACHHER — alle 3 Ursachen behoben
App->>GW: POST /api/media/upload<br/>Content-Type: image/jpeg ✅ Timeout: 30 s ✅
Note over GW: max-in-memory-size: 10 MB ✅
GW->>RS: weiterleiten
RS->>DB: Bild binär speichern
RS-->>App: 201 Created ✅
end
Fixes im Detail:
| # | Problem | Datei | Änderung |
|---|---|---|---|
| 1 | Gateway bricht Upload bei > 256 KB ab | api-gateway/application.yml |
spring.codec.max-in-memory-size: 10MB |
| 2 | Auto-detect Content-Type schlägt bei temp. Pfaden fehl | mobile/lib/services/api_service.dart |
Explizit image/jpeg / image/png via http_parser |
| 3 | Globaler 8s-Timeout zu knapp für grosse Bilder | mobile/lib/services/api_config.dart |
Separater uploadTimeout: 30s |
| 4 | Kein Token → 403 statt 401 (kein authenticationEntryPoint) |
report-media-service/.../SecurityConfig.java |
authenticationEntryPoint ergänzt |
Problem: Seiten zeigten Fehler oder deaktivierte Buttons, wenn Services beim ersten Seitenaufruf direkt nach dem Docker-Start noch nicht vollständig bereit waren.
graph TD
A[Seitenaufruf direkt nach docker compose up] --> B{Services bereit?}
B -->|Nein| C_old["VORHER: Promise.all\n→ ein Fehler = alles leer\nButton bleibt deaktiviert\nkeine Erklärung"]
B -->|Nein| D_new["NACHHER: Promise.allSettled\n→ Teilfehler werden isoliert\nroter Hinweis sichtbar\nAuto-Retry nach 5 s"]
B -->|Ja| E[Seite lädt normal]
D_new -->|5 s später| B
style C_old fill:#fecaca,stroke:#dc2626,color:#7f1d1d
style D_new fill:#bbf7d0,stroke:#15803d,color:#14532d
Docker Compose:
- Alle Spring-Boot-Services warten via
condition: service_healthyauf MySQL / MongoDB nc-basierte Health-Checks entfernt (BusyBox-Inkompatibilität: SIGALRM Exit-Code)user-role-serviceseeded beim Start automatisch 160h Stundenkontingent fürsl.huber→ frische Umgebung sofort nutzbar
CI-Timeouts:
| Schritt | Vorher | Nachher |
|---|---|---|
| Seed-User-Warte | 120 s | 150 s |
| API-Gateway-Warte | 90 s | 150 s |
| Job-Timeout | 15 min | 25 min |
HR-Benutzer können Rapportfotos einsehen, die Mitarbeiter via Mobile App hochgeladen haben.
graph LR
subgraph Mobile["Mobile App (Flutter)"]
MA["Mitarbeiter\nfotografiert & lädt hoch"]
end
subgraph Backend["Backend"]
GW["API Gateway :8000"]
RS["Report-Media Service :8087"]
DB[("MongoDB\nmedia_reports")]
end
subgraph HRWeb["HR-Web (React)"]
DP["/rapports\nDropdown → Mitarbeiter wählen"]
IL["Lazy Image Load\n→ Blob URL"]
BW["Bild im Browser"]
end
MA -->|"POST /api/media/upload\nJWT + multipart"| GW
GW --> RS --> DB
DP -->|"GET /api/media/employee/{id}"| GW
GW --> RS --> DB --> RS --> GW --> IL
IL -->|"URL.createObjectURL()"| BW
style GW fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
style DB fill:#fef9c3,stroke:#ca8a04,color:#713f12
Details:
- Mitarbeiter-Dropdown:
GET /api/users?role=EMPLOYEE - Rapport-Liste:
GET /api/media/employee/{id}— zeigt Datum, Auftrags-ID, Notiz, Dateigrösse - Bilder werden lazy per Klick geladen: Axios
responseType: 'blob'→URL.createObjectURL()(direkter<img src>geht nicht, da JWT-Header nötig) - Blob-URLs beim Seitenverlassen via
URL.revokeObjectURL()freigegeben
| Test-Datei | Neue Tests | Was wird geprüft |
|---|---|---|
PlanningControllerTests.java |
+2 | Service setzt keinen CORS-Header direkt |
MediaControllerTests.java |
+7 | Upload JPEG/PNG, kein Token→401, falscher Typ→400, 404, listByEmployee, Upload mit Auftrags-ID |
api-test.js |
+6 | Media: Listing HR/Admin/Employee, kein Token→401, 404, Order-List |
CI Maven -pl |
+1 Service | report-media-service neu im Backend-Test-Job |
11 Screenshots in docs/screenshots/ — Vorschau-Galerie oben im README ergänzt.
| Datei | Inhalt |
|---|---|
mobile_login.png |
Flutter Login-Screen |
mobile_checkin.png |
Check-in / Check-out |
mobile_kalender.png |
Kalenderansicht |
mobile_absenzen.png |
Absenz einreichen |
mobile_rapport.png |
Rapport mit Bild-Upload |
mobile_noSQLupload.png |
MongoDB-Nachweis des Uploads |
screenshot_admin_dashboard.png |
Admin-Dashboard |
screenshot_hr_dashboard.png |
HR-Dashboard |
screenshot_hr_stundenfreigabe.png |
HR Stundenfreigabe |
screenshot_sl_planung.png |
Schichtleiter Arbeitsplanung |
web_hr_rapports.png |
HR Rapport-Bilder Seite |
| Dokument | Beschreibung |
|---|---|
| UML- & Architekturdiagramme | Systemarchitektur, Klassendiagramm, ER-Diagramm, Sequenz- und Statusdiagramme (Mermaid) |
| User Stories – HR | User Stories mit Akzeptanzkriterien für die HR-Rolle |
| User Stories – Schichtleiter | User Stories mit Akzeptanzkriterien für die Schichtleiter-Rolle |
| User Stories – Mitarbeiter | User Stories mit Akzeptanzkriterien für die Mitarbeiter-Rolle |
| User Stories – Admin | User Stories mit Akzeptanzkriterien für die Admin-Rolle |
| Tests & CI-Pipeline | Wie Tests ausgeführt werden, was die CI-Pipeline prüft, was bei Fehlern zu tun ist |
| Testbericht | Ergebnisse des automatischen API-Testlaufs (54/54 Tests bestanden) |
| OWASP Top 10 Testplan | Sicherheits-Testplan mit OWASP-Kategorien, Befunden, Verbesserungen und offenen Punkten |
| Flipper Auth Integration | Übernommene Flipper-, HCE-, ESP32- und Auth-Service-Teile |
- Docker Desktop installiert und gestartet
- Ports 3001–3004, 8000–8009, 3307, 27017, 8080, 8081 sind frei
Im Stammverzeichnis des Projekts (Modul_335_Mobile_Applikation/) ausführen:
Erster Start oder nach einem Reset:
docker compose down -v
docker compose up --build -dFolgestarts (kein Code geändert, Daten sollen erhalten bleiben):
docker compose up -dErster Build: dauert 3–5 Minuten (Maven-Dependencies, npm install).
Folgestarts: gehen in Sekunden, da Images bereits gebaut sind.
Wichtig – Demo-Accounts: Die Benutzer (admin, hr.mueller, sl.huber, emp.meier) werden nicht per SQL-Seed angelegt, sondern beim Start des user-role-service per CommandLineRunner mit korrekt generiertem BCrypt-Hash. MySQL-Healthcheck stellt sicher, dass die Rollen bereits vorhanden sind, bevor der Service startet.
Seeding prüfen:
docker compose logs user-role-service | grep SeedErwartete Ausgabe:
Seed-User angelegt: admin (ADMIN)
Seed-User angelegt: hr.mueller (HR)
Seed-User angelegt: sl.huber (SHIFT_LEAD)
Seed-User angelegt: emp.meier (EMPLOYEE)
| Anwendung | URL | Beschreibung |
|---|---|---|
| Admin-Frontend | http://localhost:3001 | Rollen, Benutzerverwaltung, Aufträge |
| HR-Frontend | http://localhost:3002 | Schichtleiter anlegen, Stunden, Rechnungen, Absenzen |
| Schichtleiter-Frontend | http://localhost:3003 | Arbeitspläne, Schichten, Kalender |
| Flipper Auth Dashboard | http://localhost:3004 | Flipper-Login/Logout-Challenges testen |
| phpMyAdmin | http://localhost:8080 | MySQL-Datenbankadmin |
| Mongo Express | http://localhost:8081 | MongoDB-Admin (kein Login nötig) |
Alle Frontends verwenden denselben Login-Endpunkt über den API-Gateway (localhost:8000). Die Demo-Accounts werden beim Start automatisch angelegt.
| Benutzername | Passwort | Rolle | Frontend |
|---|---|---|---|
admin |
password |
Admin | http://localhost:3001 |
hr.mueller |
password |
HR | http://localhost:3002 |
sl.huber |
password |
Schichtleiter | http://localhost:3003 |
emp.meier |
password |
Mitarbeiter | Flutter Mobile App |
Jedes Frontend prüft nach dem Login die Rolle im JWT.
adminkann sich z.B. nicht im HR-Frontend einloggen — falsche Rolle wird verweigert.
Admin → http://localhost:3001
| Seite | Funktion |
|---|---|
| Rollen | Benutzer aus der DB anzeigen, Rolle ändern, deaktivieren/aktivieren |
| Aufträge | Aufträge über den Order Service erstellen, bearbeiten, zuweisen und Status ändern |
| HR / Mitarbeiter | Übersicht (lokal) |
| Seite | URL | Funktion |
|---|---|---|
| Benutzerverwaltung | /users |
Schichtleiter/Mitarbeiter anlegen, bearbeiten, deaktivieren |
| Stundenübersicht | /time |
Gesamtstunden, Monatsdetail und Pausenverstösse prüfen |
| Stundenfreigabe | /hour-budgets |
Monatliche HR-Stundenkontingente für Schichtleiter festlegen |
| Rechnungen | /invoices |
Rechnungen erstellen, versenden, als bezahlt markieren |
| Lohnauszüge | /payroll |
Monatslohn aus Stunden, Rate, Zuschlägen und Abzügen berechnen |
| Absenzen & Ferien | /absences |
Ferienanträge genehmigen/ablehnen, Absenzen erfassen |
Schichtleiter → http://localhost:3003
| Seite | URL | Funktion |
|---|---|---|
| Arbeitsplanung | /planning |
Arbeitspläne mit HR-Stundenfreigabe erstellen, Schichten hinzufügen, veröffentlichen |
| Aufträge | /orders |
Zugewiesene Aufträge aus dem Order Service einsehen und Status ändern |
| Arbeitszeiten | /time |
Gesamtstunden, Monatsdetails und Pausenverstösse der Mitarbeiter einsehen |
Flutter Mobile App (Login: emp.meier / password)
| Screen | Funktion |
|---|---|
| Check-in/out | POST /api/time/checkin, POST /api/time/checkout; Pausenminuten werden beim Check-out mitgegeben |
| Kalender | GET /api/planning/calendar/{employeeId} – zeigt veröffentlichte Schichten des laufenden Monats |
| Absenzen | POST /api/absences, GET /api/absences/employee/{employeeId} |
| Rapport | POST /api/media/upload mit optionaler Auftrags-ID; Bild wird in MongoDB gespeichert |
Wenn der Docker-Stack läuft, können die API-Tests aus dem tests-Ordner gestartet werden:
cd tests
node api-test.jsDie Tests verwenden den lokalen API-Gateway unter http://localhost:8000.
POST http://localhost:8000/api/auth/login
Content-Type: application/json
{
"username": "hr.mueller",
"password": "password"
}
Antwort:
{
"token": "eyJ...",
"role": "HR",
"username": "hr.mueller",
"userId": 2
}Den token-Wert als Authorization: Bearer <token> Header für alle weiteren API-Anfragen verwenden.
# Status aller Container anzeigen
docker compose ps
# Logs eines bestimmten Services live verfolgen
docker compose logs -f api-gateway
docker compose logs -f user-role-service
docker compose logs -f hr-web
# Alle Container stoppen (Daten bleiben erhalten)
docker compose down
# Einzelnen Service nach Code-Änderung neu bauen und starten
docker compose up --build user-role-service -d
# Kompletter Reset – stoppt alles und löscht alle Datenbankdaten
docker compose down -v && docker compose up --build -d
down -vlöscht die MySQL- und MongoDB-Volumes. Beim nächsten Start legtinit.sqlSchema und Rollen neu an, deruser-role-serviceseeded die Demo-Accounts.
| Problem | Ursache | Lösung |
|---|---|---|
| Login schlägt mit 401 fehl | MySQL-Volume mit falsch geseedeten Usern aus alter Version | docker compose down -v && docker compose up --build -d |
Login leitet sofort zurück auf /login |
401-Response vom Login-Endpoint triggerte früher einen Hard-Redirect | Behoben in api.js aller drei Frontends (Interceptor prüft jetzt ob Request = Login-Endpoint) |
| Admin zeigt 403 und kann Mitarbeiter oder Aufträge nicht laden/speichern | Im Browser ist noch ein abgelaufener oder ungültiger JWT gespeichert | Admin-Web entfernt bei 401/403 den alten Loginzustand und leitet zur erneuten Anmeldung weiter; User- und Order-Service antworten bei ungültigen Tokens korrekt mit 401 |
| Leere Seite ohne Login-Formular | vite.config.js fehlte → JSX wurde nicht verarbeitet |
Behoben, vite.config.js ist vorhanden |
| CORS-Fehler 403 | Gateway hatte kein globalcors, Services gaben doppelte CORS-Header |
Behoben: Gateway verwaltet CORS, Services haben cors.disable() |
| Admin zeigt andere User als HR-Frontend | adminSeed.js enthielt lokale Dummy-User (Amir Suter, Lea Baumann etc.); HR- und Mitarbeiter-Formulare schrieben in localStorage statt in die DB |
Behoben in DashboardPage.jsx: saveHrUser und saveEmployee rufen jetzt POST /api/users auf; Suche und Stats verwenden echte API-Daten |
| Nach Wechsel auf neuen Rechner zeigt Admin noch alte Daten | Browser-localStorage vom alten Rechner enthält veralteten Admin-State |
DevTools → Application → Local Storage → planifywork-admin-state-v1 löschen, Seite neu laden |
CORS-Fehler „Es darf nur eine CORS-Kopfzeile verwendet werden" auf /api/planning/** |
Planning Service hatte corsConfigurationSource-Bean in SecurityConfig und @CrossOrigin auf dem Controller gleichzeitig; CORS wird aber ausschliesslich vom API Gateway gesetzt |
Beide Service-seitigen CORS-Konfigurationen entfernt, cors.disable() eingesetzt; gilt als Konvention für alle Services — niemals @CrossOrigin oder eigene CorsConfigurationSource in einem Service hinzufügen |
| Bild-Upload aus Mobile App schlägt fehl (kein 200, kein sinnvoller Fehler) | Spring Cloud Gateway hat ein Standard-In-Memory-Buffer-Limit von 256 KB; Kamerabilder sind grösser und werden vom Gateway abgebrochen, bevor der Report-Service sie sieht | spring.codec.max-in-memory-size: 10MB in api-gateway/application.yml setzen; Limit muss mindestens so gross sein wie spring.servlet.multipart.max-file-size im Report-Service |
Das Workforce Management System ist eine verteilte Applikation zur Verwaltung von Mitarbeitern, Arbeitszeiten, Aufträgen, Schichten und Rechnungen.
Das System besteht aus:
- 3 React-Webapplikationen für Admin, HR und Schichtleiter (Desktop)
- 1 Flutter-Mobile-App für Mitarbeiter (iOS / Android)
- 8 Spring-Boot-Microservices als Backend
- MySQL für strukturierte Daten
- MongoDB für Bild-Uploads und Mediendaten
- Docker Compose zur lokalen Ausführung aller Komponenten
| Bereich | Technologie | Version |
|---|---|---|
| Frontend Web | React + Vite | React 18 |
| Mobile | Flutter | SDK 3.3+ |
| Backend | Spring Boot | 3.3 |
| API Gateway | Spring Cloud Gateway | 2023.0 |
| Authentifizierung | JWT (jjwt) | 0.12.5 |
| Datenbank 1 | MySQL | 8.0 |
| Datenbank 2 | MongoDB | 7.0 |
| Container | Docker + Docker Compose | – |
| DB-Admin | phpMyAdmin + Mongo Express | – |
| HTTP Client | Axios (React) / http (Flutter) | – |
| State (Flutter) | Provider | 6.1 |
| Routing (React) | React Router DOM | v6 |
| Rolle | Zugang | JWT-Rolle | Test-Benutzer | Passwort |
|---|---|---|---|---|
| Admin | Admin Web – http://localhost:3001 | ADMIN |
admin |
password |
| HR | HR Web – http://localhost:3002 | HR |
hr.mueller |
password |
| Schichtleiter | Schichtleiter Web – http://localhost:3003 | SHIFT_LEAD |
sl.huber |
password |
| Mitarbeiter | Flutter Mobile App | EMPLOYEE |
emp.meier |
password |
Jedes Frontend prüft nach dem Login die Rolle im JWT-Token. Stimmt die Rolle nicht überein, wird der Zugang verweigert.
Die Testbenutzer werden beim ersten Start des user-role-service automatisch in der Datenbank angelegt (via CommandLineRunner in UserRoleServiceApplication.java). Voraussetzung: Die Rollen müssen in der roles-Tabelle vorhanden sein (wird via database/mysql/init.sql beim ersten Start von MySQL erledigt).
graph TB
subgraph Clients["Clients"]
AW["React Admin Web\n:3001"]
HW["React HR Web\n:3002"]
SW["React Schichtleiter Web\n:3003"]
MA["Flutter Mobile App\nAndroid / iOS"]
end
subgraph GWLayer["API Gateway"]
GW["Spring Cloud Gateway :8000\nJWT-Prüfung · CORS · Routing"]
end
subgraph SVC["Backend Microservices (Spring Boot 3.3)"]
AS["Auth Service\n:8001"]
URS["User & Role Service\n:8002"]
OS["Order Service\n:8003"]
PS["Planning Service\n:8004"]
TS["Time Service\n:8005"]
AVS["Absence Service\n:8006"]
BS["Billing Service\n:8007"]
RMS["Report/Media Service\n:8008"]
end
subgraph DBLayer["Datenbanken"]
MYSQL[("MySQL :3307\nworkforce DB\nBenutzer · Aufträge · Schichten\nZeiten · Absenzen · Rechnungen")]
MONGO[("MongoDB :27017\nworkforce-media DB\nBilder · Rapport-Metadaten")]
end
AW & HW & SW & MA --> GW
GW --> AS & URS & OS & PS & TS & AVS & BS & RMS
AS & URS & OS & PS & TS & AVS & BS --> MYSQL
RMS --> MONGO
Detaillierte Diagramme (ER, Klassendiagramm, Sequenz-, Statusdiagramme) → docs/diagrams.md
Modul_335_Mobile_Applikation/
│
├── docker-compose.yml # Startet alle Container
├── .gitignore
│
├── backend/ # Spring Boot Microservices
│ ├── api-gateway/ # Zentraler Einstiegspunkt, JWT-Prüfung
│ ├── auth-service/ # Login, Logout, JWT erstellen
│ ├── user-role-service/ # Benutzerverwaltung, Rollen
│ ├── order-service/ # Auftragsmanagement
│ ├── planning-service/ # Arbeitsplanung, Schichten
│ ├── time-service/ # Check-in/out, Arbeitszeitberechnung
│ ├── absence-vacation-service/ # Absenzen und Ferien
│ ├── billing-service/ # Rechnungen
│ └── report-media-service/ # Bild-Upload (MongoDB)
│
├── frontend/ # React Webapplikationen
│ ├── admin-web/ # Admin-Oberfläche
│ ├── hr-web/ # HR-Oberfläche
│ └── shiftlead-web/ # Schichtleiter-Oberfläche
│
├── mobile/ # Flutter Mobile App
│ └── lib/
│ ├── main.dart
│ ├── screens/ # UI-Screens
│ └── services/ # API- und Auth-Logik
│
└── database/
├── mysql/init.sql # MySQL Schema + Seed-Daten
└── mongodb/init.js # MongoDB Collection-Setup
Jeder Spring-Boot-Service hat dieselbe interne Struktur:
<service-name>/
├── Dockerfile
├── pom.xml
└── src/main/
├── java/com/workforce/<name>/
│ ├── <Name>Application.java # Einstiegspunkt
│ ├── controller/ # REST-Endpoints (@RestController)
│ ├── service/ # Geschäftslogik
│ ├── model/ # Entities / Datenmodelle
│ ├── repository/ # Datenbankzugriff (JPA / MongoDB)
│ └── config/ # Security, JWT, CORS etc.
└── resources/
└── application.yml # Port, DB-Verbindung, JWT-Secret
Jede React-App hat dieselbe interne Struktur:
<app-name>/
├── Dockerfile
├── nginx.conf
├── package.json
├── index.html
└── src/
├── main.jsx # React-Einstiegspunkt
├── App.jsx # Router-Setup, Protected Routes
├── pages/ # Seitenkomponenten (LoginPage, Dashboard...)
├── components/ # Wiederverwendbare UI-Komponenten
└── services/
└── api.js # Axios-Instanz mit JWT-Interceptor
Aufgabe: Einziger Einstiegspunkt für alle Frontends. Leitet HTTP-Anfragen anhand des URL-Pfades an den passenden Microservice weiter.
Routing-Tabelle:
| Pfad-Prefix | Ziel-Service |
|---|---|
/api/auth/** |
auth-service :8001 |
/api/users/** |
user-role-service :8002 |
/api/orders/** |
order-service :8003 |
/api/planning/** |
planning-service :8004 |
/api/time/** |
time-service :8005 |
/api/absences/** |
absence-vacation-service :8006 |
/api/billing/** |
billing-service :8007 |
/api/media/** |
report-media-service :8008 |
/api/flipper-auth/** |
flipper-auth-service :8009 |
Hinweis zur Integration:
- Das Gateway leitet alle
/api/**-Requests an die passenden Microservices weiter. - JWT-Header werden durchgereicht; die rollenbasierte Prüfung passiert in den jeweiligen Services per Spring Security.
- CORS ist für Admin, HR, Schichtleiter und Flipper-Web konfiguriert.
- Rate Limiting bleibt optional.
Aufgabe: Authentifizierung. Erstellt JWT-Tokens nach erfolgreichem Login.
Bereits implementiert:
POST /api/auth/login→ gibt JWT-Token + Rolle zurückGET /api/auth/validate→ prüft ob ein Token gültig istUser- undRole-Entity mit JPAUserRepository(findByUsername, findByEmail)JwtUtil(Token erstellen, validieren, Rolle/Username extrahieren)- Spring Security Konfiguration (stateless, BCrypt)
Noch zu implementieren:
POST /api/auth/logout(Token-Blacklist oder Frontend-seitig)- Passwort ändern
- Erster Admin-Benutzer per Seed-Script
Aufgabe: Verwaltung aller Benutzer und ihrer Rollen.
Noch zu implementieren:
GET /api/users– alle Benutzer auflisten (Admin)POST /api/users– neuen Benutzer anlegenPUT /api/users/{id}– Benutzer bearbeitenDELETE /api/users/{id}– Benutzer deaktivierenGET /api/users/{id}– Benutzerdetails- Entities:
User,Role - Repository, Service, Controller
Aufgabe: Auftragsmanagement. Admin erstellt Aufträge, Schichtleiter empfangen sie, Mitarbeiter können Auftragsdaten herunterladen.
Implementiert:
GET /api/orders– Aufträge auflisten, optional mit?shiftLeadId=und?status=filternGET /api/orders/{id}– Auftragsdetail anzeigenPOST /api/orders– Auftrag erstellen (Admin)PUT /api/orders/{id}– Auftrag bearbeiten (Admin)PUT /api/orders/{id}/assign– Schichtleiter/Mitarbeiter zuweisen (Admin)PUT /api/orders/{id}/status– Status ändern (Admin/Schichtleiter)GET /api/orders/{id}/download– Auftragsdaten als JSON abrufen- Entities:
WorkOrder,OrderEmployee - Status-Enum:
OPEN,IN_PROGRESS,DONE
Beispiel: Auftrag erstellen
POST http://localhost:8000/api/orders
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"title": "Umbau Eingang A",
"description": "Material prüfen und Rapportbilder hochladen",
"company": "Demo AG",
"location": "Zürich",
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"requiredRole": "Montage",
"assignedShiftLeadId": 3,
"createdBy": 1,
"status": "OPEN",
"employeeIds": [4]
}Aufgabe: HR gibt monatliche Stundenkontingente pro Schichtleiter frei. Schichtleiter erstellen darauf basierend Arbeitspläne für ihr Team. Mitarbeiter sehen veröffentlichte Schichten im Mobile-Kalender.
Implementiert:
POST /api/planning/hour-budgets– HR-Stundenkontingent pro Schichtleiter und Monat erstellen/aktualisierenGET /api/planning/hour-budgets– HR-Stundenkontingente auflisten, optional mit?shiftLeadId=filternPOST /api/planning/workplans– Arbeitsplan erstellen und HR-Stundenkontingent automatisch übernehmenGET /api/planning/workplans– Arbeitspläne auflisten, optional mit?shiftLeadId=filternGET /api/planning/workplans/{id}– Arbeitsplan inkl. Schichten und Stundenübersicht anzeigenPUT /api/planning/workplans/{id}– Arbeitsplan-Entwurf bearbeitenPOST /api/planning/workplans/{id}/shifts– Schicht hinzufügen, optional mitorderIdPUT /api/planning/workplans/{id}/publish– Arbeitsplan veröffentlichenGET /api/planning/calendar/{employeeId}– veröffentlichte Kalenderschichten eines Mitarbeiters anzeigen- Entities:
HourBudget,WorkPlan,Shift,WorkPlanStatus
Stundenlogik:
approvedHourswird aus der HR-Stundenfreigabe übernommen und nicht mehr vom Schichtleiter eingegeben.plannedHourswird aus allen Schichten eines Arbeitsplans berechnet.remainingHourszeigt die Differenz zwischen freigegebenen und geplanten Stunden.overLimitwirdtrue, wenn mehr als das HR-Kontingent geplant wurde.underPlannedwirdtrue, wenn weniger als 95 % des HR-Kontingents geplant wurden.
Beispiel: HR-Stundenfreigabe erstellen
POST http://localhost:8000/api/planning/hour-budgets
Authorization: Bearer <hr-token>
Content-Type: application/json
{
"shiftLeadId": 3,
"year": 2026,
"month": 6,
"approvedHours": 1000,
"createdBy": 2,
"notes": "Sommermonat Juni"
}Beispiel: Arbeitsplan erstellen
POST http://localhost:8000/api/planning/workplans
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "Monatsplan Juni",
"shiftLeadId": 3,
"startDate": "2026-06-01",
"endDate": "2026-06-30"
}Beispiel: Schicht hinzufügen
POST http://localhost:8000/api/planning/workplans/1/shifts
Authorization: Bearer <token>
Content-Type: application/json
{
"employeeId": 4,
"orderId": null,
"shiftDate": "2026-06-03",
"startTime": "08:00",
"endTime": "17:00",
"notes": "Tagesschicht"
}Aufgabe: Check-in / Check-out erfassen, Arbeitsstunden berechnen, Auswertungen bereitstellen.
Implementiert:
POST /api/time/checkin– Check-in speichernPOST /api/time/checkout– Check-out speichern und Netto-Arbeitszeit berechnenGET /api/time/current/{employeeId}– aktuell offener Check-in eines MitarbeitersGET /api/time/latest/{employeeId}– letzter Zeiteintrag eines MitarbeitersGET /api/time/today/{employeeId}– heutiger Zeiteintrag eines MitarbeitersGET /api/time/month/{employeeId}?month=&year=– Monatsauswertung pro MitarbeiterGET /api/time/total?from=&to=– Gesamtstunden aller Mitarbeiter im ZeitraumGET /api/time/total/{employeeId}?from=&to=– Gesamtstunden eines Mitarbeiters im ZeitraumGET /api/time/break-violations?from=&to=&employeeId=– Pausenverstösse auswerten- Rollen: HR/Admin für Auswertungen, Schichtleiter für Team-Übersicht, Mitarbeiter für eigenen Check-in/out
- Entities:
TimeEntry - Berechnung: Netto-Stunden aus Check-in, Check-out und Pausenzeit
- Pausenregel: mehr als 6 Stunden Brutto-Arbeitszeit → mindestens 30 Minuten Pause; mehr als 9 Stunden → mindestens 45 Minuten Pause
Aufgabe: Ferienanfragen und Absenzen verwalten.
Implementiert:
POST /api/absences– Absenz/Ferienanfrage einreichen (Mitarbeiter, HR, Admin)GET /api/absences/employee/{employeeId}– eigene Absenzen/Ferienanfragen für die Mobile App ladenGET /api/absences– Absenzen nach Mitarbeiter und/oder Typ filternGET /api/absences/pending– offene Anfragen (HR)PUT /api/absences/{id}/approve– genehmigen (HR)PUT /api/absences/{id}/reject– ablehnen (HR)PUT /api/absences/{id}– Abwesenheit bearbeiten (HR/Admin)DELETE /api/absences/{id}– Abwesenheit löschen (HR/Admin)- Entities:
Absence - Type-Enum:
VACATION,SICK,OTHER - Status-Enum:
PENDING,APPROVED,REJECTED
Aufgabe: HR erstellt Rechnungen und monatliche Lohnauszüge basierend auf erfassten Arbeitsstunden.
Implementiert Rechnungen:
POST /api/billing/invoices– Rechnung erstellenGET /api/billing/invoices– alle RechnungenGET /api/billing/invoices/{id}– RechnungsdetailPUT /api/billing/invoices/{id}/send– Rechnung versendenPUT /api/billing/invoices/{id}/pay– Rechnung als bezahlt markieren- Entities:
Invoice,InvoicePosition - Status-Enum:
DRAFT,SENT,PAID
Implementiert Lohnauszüge:
POST /api/billing/payroll-statements– Lohnauszug aus Monatsstunden, Stundenrate, Zuschlägen und Abzügen erstellen oder neu berechnenGET /api/billing/payroll-statements– Lohnauszüge auflisten, optional mit?status=filternGET /api/billing/payroll-statements/{id}– Lohnauszug anzeigenPUT /api/billing/payroll-statements/{id}/approve– Lohnauszug freigebenPUT /api/billing/payroll-statements/{id}/pay– Lohnauszug als bezahlt markieren- Entities:
PayrollStatement,PayrollStatus - Status-Enum:
DRAFT,APPROVED,PAID - Stundendaten kommen aus
time_entriesdes Time Service
Aufgabe: Bild-Uploads aus der Mobile App empfangen und in MongoDB speichern.
Implementiert:
POST /api/media/upload– Bild aus der Mobile App per Multipart hochladenGET /api/media/{id}– Bilddatei aus MongoDB abrufenGET /api/media/order/{orderId}– alle Rapportbilder eines Auftrags auflistenGET /api/media/employee/{employeeId}– alle Rapportbilder eines Mitarbeiters auflisten- MongoDB-Document:
MediaReport(employeeId, orderId, rapportId, filename, contentType, fileSize, storagePath, uploadedAt, metadata, data) - Bilddaten werden direkt in MongoDB gespeichert; maximale Upload-Grösse: 10 MB
Beispiel: Rapportbild hochladen
POST http://localhost:8000/api/media/upload
Authorization: Bearer <employee-token>
Content-Type: multipart/form-data
file=<bild.jpg>
employeeId=4
orderId=1
note=Rapportfoto Eingang A- Vite als Build-Tool
- React Router v6 für clientseitiges Routing
- Axios mit JWT-Interceptor (
src/services/api.js)- Setzt automatisch den
Authorization: Bearer <token>Header - Leitet bei 401 automatisch auf
/loginweiter
- Setzt automatisch den
- Protected Routes – nicht eingeloggte Nutzer werden auf
/loginumgeleitet - Login prüft die JWT-Rolle, falsche Rolle = Zugang verweigert
Implementiert:
- Login mit Rollenprüfung
ADMIN - Dashboard mit vollständiger Navigation (Übersicht, Aufträge, HR, Firmenkonzepte, Lohn und Stunden, Mitarbeiter, Rollen, Berichte, Suche, Audit-Log)
- Rollen-Tab: alle Benutzer aus der DB anzeigen, Rolle ändern, deaktivieren/aktivieren (
GET /api/users,PUT /api/users/:id) - HR-Tab: HR-Benutzer aus DB anzeigen, anlegen, bearbeiten und deaktivieren/aktivieren (
GET /api/users?role=HR,POST /api/users,PUT /api/users/:id) - Mitarbeiter-Tab: Mitarbeiter aus DB anzeigen, anlegen, bearbeiten und deaktivieren/aktivieren (
GET /api/users?role=EMPLOYEE,POST /api/users,PUT /api/users/:id) - Aufträge: werden über den Order Service gespeichert (
GET/POST/PUT /api/orders) - Firmenkonzepte, Stunden-/Lohnregeln, Berichte, Audit-Log: weiterhin lokal im Browser
- Suche und Übersichts-Statistiken verwenden echte DB-Daten für Benutzer/Mitarbeiter/Aufträge
Hinweis: Firmenkonzepte, Stunden- und Lohnregeln werden aktuell noch im
localStoragedes Browsers gespeichert. Aufträge sind jetzt backendgestützt und bleiben in MySQL erhalten.
Hinweis Bearbeiten-Formular: Beim Bearbeiten eines bestehenden HR- oder Mitarbeiter-Benutzers werden Benutzername und Passwort ausgeblendet, da der
PUT /api/users/:idEndpunkt diese Felder nicht akzeptiert (Benutzername ist eindeutig und unveränderlich; Passwortänderung ist nicht implementiert).
Noch zu implementieren:
- Firmendaten-Seite (
/company) - Stundenübersicht aus dem Time Service laden
Implementiert:
- Login mit Rollenprüfung
HR - Benutzerverwaltung (
/users): Schichtleiter/Mitarbeiter anlegen, bearbeiten, deaktivieren - Stundenübersicht (
/time): Gesamtstunden, Monatsdetail und Pausenverstösse prüfen - Stundenfreigabe (
/hour-budgets): Monatskontingente pro Schichtleiter freigeben - Rechnungen (
/invoices): Erstellen, versenden, als bezahlt markieren (DRAFT → SENT → PAID) - Lohnauszüge (
/payroll): Monatslohn aus Stunden, Stundenrate, Zuschlägen und Abzügen berechnen (DRAFT → APPROVED → PAID) - Absenzen & Ferien (
/absences): Ferienanfragen genehmigen/ablehnen, Absenzen erfassen und verwalten
Noch zu implementieren:
- Abwesenheitskalender (
/absences/calendar) — Backend-Endpunkt vorhanden, Frontend-Widget fehlt (US-HR-10)
Implementiert:
- Login mit Rollenprüfung
SHIFT_LEADund Speicherung vonuserId - Dashboard mit Kacheln für Planung, Aufträge und Notizen
- Arbeitsplan-Erstellung (
/planning) mit automatisch übernommener HR-Stundenfreigabe - Schichten hinzufügen inklusive Mitarbeiter-Auswahl, optionaler Auftrag-ID und Notiz
- Stundenübersicht mit HR-Kontingent, geplanten Stunden, Reststunden und Warnungen
- Arbeitszeiten-Seite (
/time) lädt Gesamtstunden, Monatsdetails und Pausenverstösse aus dem Time Service - Arbeitsplan veröffentlichen, damit Mitarbeiter die Schichten im Mobile-Kalender sehen
Implementiert zusätzlich:
- Auftrags-Ansicht (
/orders) lädt zugewiesene Aufträge aus dem Order Service - Schichtleiter kann den Auftragsstatus auf
OPEN,IN_PROGRESSoderDONEsetzen - Arbeitsplanung bietet zugewiesene Aufträge als Auswahl für neue Schichten an
Vorbereitet:
- Notizen-Übersicht (
/notes) als Platzhalter; Schichtnotizen werden bereits im Arbeitsplan gespeichert
Verzeichnis: mobile/
Rolle: Mitarbeiter (emp.meier / password)
Technologie: Flutter SDK 3.3+, Provider 6.1, http, SharedPreferences, image_picker
Die Mobile App verbindet sich mit demselben API Gateway (:8000) und verwendet dasselbe JWT wie die drei Web-Frontends. Sie ist ausschliesslich für die Rolle EMPLOYEE gedacht.
- Flutter SDK ≥ 3.3 (flutter.dev/docs/get-started/install)
- Android Studio mit installiertem Android Emulator (API 33+) oder ein physisches Android-Gerät
- Docker-Stack läuft (
docker compose up -d) – die App spricht gegen den Backend-Stack
⚠️ Wichtig: Die URL muss vor dem ersten Start angepasst werden. Ohne korrekte URL kann die App keine Verbindung zum Backend aufbauen.
Die Basis-URL für alle API-Anfragen steht zentral in einer Datei:
mobile/lib/services/api_config.dart
class ApiConfig {
// Android Emulator: 10.0.2.2 = localhost des Host-Rechners
// Echtes Gerät im selben WLAN: lokale IP des Host-Rechners (z.B. 192.168.1.x)
static const String baseUrl = 'http://10.0.2.2:8000';
static const Duration requestTimeout = Duration(seconds: 8);
}| Situation | Wert für baseUrl |
|---|---|
| Android-Emulator (Standard) | http://10.0.2.2:8000 |
| Echtes Gerät im selben WLAN | http://<lokale-IP-des-Rechners>:8000 |
| iOS-Simulator | http://localhost:8000 |
Die lokale IP des Rechners findet man unter Windows mit
ipconfig(z.B.192.168.1.42), unter macOS/Linux mitifconfigoderip a.
Wichtig: Nach einer URL-Änderung muss die App neu gebaut werden (flutter run).
cd mobile
flutter pub get # Dependencies installieren (einmalig)
flutter run # App im verbundenen Emulator / Gerät startenAlternativer Start direkt im Android Studio:
- Emulator über Device Manager starten
- In der Run-Konfiguration das
mobile/-Verzeichnis auswählen - Run drücken
| Feld | Wert |
|---|---|
| Benutzername | emp.meier |
| Passwort | password |
Die App ruft POST /api/auth/login auf denselben API Gateway auf wie die Web-Frontends. Nach erfolgreichem Login speichert sie token, role, username und userId in den SharedPreferences des Geräts. Das JWT ist 24 Stunden gültig.
Die App prüft die Rolle nicht beim Login – technisch kann jeder Systembenutzer die Mobile App verwenden. Im Normalbetrieb ist sie für die Rolle
EMPLOYEEvorgesehen.
Ermöglicht dem Mitarbeiter, die Arbeitszeit direkt aus der App zu erfassen.
| Aktion | Endpoint | Payload |
|---|---|---|
| Status laden | GET /api/time/current/{userId} |
– |
| Check-in | POST /api/time/checkin |
{ "employeeId": 4 } |
| Check-out | POST /api/time/checkout |
{ "employeeId": 4, "breakMinutes": 30 } |
- Beim Check-out wird die Pausenzeit in Minuten abgezogen
- Die Netto-Arbeitsstunden werden vom Time Service berechnet und zurückgegeben
- Ist der Mitarbeiter bereits eingecheckt, zeigt die App den aktuellen Check-in-Zeitpunkt
Zeigt die veröffentlichten Schichten des Mitarbeiters für den laufenden Monat.
| Aktion | Endpoint |
|---|---|
| Schichten laden | GET /api/planning/calendar/{userId}?from=YYYY-MM-DD&to=YYYY-MM-DD |
Schichten sind nur sichtbar, wenn der Schichtleiter den Arbeitsplan veröffentlicht hat (Status
PUBLISHED). Entwürfe (DRAFT) erscheinen nicht.
Mitarbeiter kann eigene Ferienanträge und Absenzen einreichen und den Status einsehen.
| Aktion | Endpoint | Payload |
|---|---|---|
| Absenzen laden | GET /api/absences/employee/{userId} |
– |
| Absenz einreichen | POST /api/absences |
{ "employeeId": 4, "type": "VACATION", "startDate": "...", "endDate": "...", "reason": "..." } |
Typen: VACATION (Ferien), SICK (Krank), OTHER (Sonstiges)
Status: PENDING → APPROVED / REJECTED (wird von HR gesetzt)
Mitarbeiter fotografiert den Arbeitsort und lädt das Bild mit optionaler Auftrags-ID hoch.
| Aktion | Endpoint |
|---|---|
| Bild hochladen | POST /api/media/upload (Multipart) |
Felder: file (Bilddatei), employeeId, orderId (optional), note (optional)
Das Bild wird in MongoDB (workforce-media.media_reports) gespeichert. Max. 10 MB.
mobile/lib/
├── main.dart # App-Einstieg, Provider-Setup, Login/Home-Weiche
├── services/
│ ├── api_config.dart # Zentrale URL-Konfiguration (hier bei Bedarf anpassen)
│ ├── api_service.dart # GET, POST, Multipart-Upload mit JWT-Header
│ └── auth_service.dart # Login, Logout, JWT/UserId/Rolle (SharedPreferences)
└── screens/
├── login_screen.dart # Login-UI
├── home_screen.dart # Bottom-Navigation (4 Tabs)
├── checkin_screen.dart # Check-in / Check-out → Time Service
├── calendar_screen.dart # Monatskalender → Planning Service
├── absence_screen.dart # Absenzen einreichen und anzeigen → Absence Service
└── report_screen.dart # Kamera → Bild-Upload → Report/Media Service
Alle API-Aufrufe laufen ausschliesslich über ApiService. Kein Screen ruft http direkt auf. Der AuthService verwaltet den Login-Zustand und stellt den JWT-Header für alle Requests bereit.
| Problem | Ursache | Lösung |
|---|---|---|
Backend nicht erreichbar |
Falsche baseUrl in api_config.dart |
URL auf 10.0.2.2:8000 (Emulator) oder lokale IP (echtes Gerät) setzen |
Nicht autorisiert (401) |
Token abgelaufen (24h) oder nie gesetzt | Ausloggen und neu einloggen |
| Keine Schichten im Kalender | Schichtleiter hat Plan noch nicht veröffentlicht | Im Schichtleiter-Web Plan veröffentlichen (Status → PUBLISHED) |
| Kamera öffnet sich nicht | image_picker Berechtigungen fehlen |
Android: Kameraberechtigung in App-Einstellungen erteilen |
| Upload schlägt mit 413 fehl | Bild grösser als 10 MB | Kleinere Auflösung / niedrigere Bildqualität wählen |
- Auftragsdaten herunterladen (
GET /api/orders/{id}/download) - Eigenes Benutzerprofil anzeigen
Wird verwendet für alle strukturierten Daten.
Datenbank: workforce
Benutzer: workforce / workforce
Intern verwenden die Backend-Services weiterhin mysql-db:3306. Auf dem Windows-Host ist MySQL über localhost:3307 erreichbar, damit es keinen Konflikt mit einer lokal installierten Windows-MySQL-Instanz auf Port 3306 gibt.
Tabellen (automatisch angelegt via database/mysql/init.sql):
| Tabelle | Inhalt |
|---|---|
roles |
ADMIN, HR, SHIFT_LEAD, EMPLOYEE |
users |
Alle Benutzer mit Rollenzuweisung |
orders |
Aufträge inklusive Firma, Einsatzort, Zeitraum, Status und Schichtleiter-Zuweisung |
order_employees |
Zuordnung Mitarbeiter ↔ Auftrag |
work_plans |
Arbeitspläne inkl. HR-Stundenkontingent, Status und Veröffentlichungszeitpunkt |
shifts |
Einzelschichten mit Mitarbeiter- und optionalem Auftragsbezug |
time_entries |
Check-in/out Einträge |
absences |
Absenzen und Ferienanfragen |
invoices |
Rechnungen |
invoice_positions |
Rechnungspositionen |
phpMyAdmin: http://localhost:8080
Wird verwendet für Bild-Uploads aus der Mobile App.
Datenbank: workforce-media
Benutzer: workforce / workforce
Collection: media_reports
{
"employee_id": 42,
"order_id": 7,
"rapport_id": "uuid-...",
"filename": "bild_2024_01.jpg",
"content_type": "image/jpeg",
"file_size": 204800,
"storage_path": "mongodb://workforce-media/media_reports/uuid-...",
"uploaded_at": "2024-01-15T14:30:00Z",
"metadata": { "note": "Rapportfoto Eingang A" },
"data": "<binary image data>"
}Mongo Express: http://localhost:8081
- Docker Desktop installiert und gestartet
- Ports 3001–3004, 8000–8009, 3307, 27017, 8080, 8081 sind frei
- Node.js v18+ nur nötig, wenn Frontends im Entwicklungsmodus (ausserhalb Docker) gestartet werden
docker compose up --build -dStartet alle Container auf einmal: Datenbanken, alle 8 Backend-Services, alle 3 Frontends und die DB-Admins.
| Flag | Bedeutung |
|---|---|
--build |
Bilder neu bauen (nötig beim ersten Start und nach Code-Änderungen) |
-d |
Hintergrundmodus (Terminal bleibt frei) |
Folgestarts ohne Code-Änderungen:
docker compose up -d(kein--build, startet in Sekunden).
docker compose psmysql-db muss den Status healthy haben, bevor die Backend-Services bereit sind. Wenn ein Service unhealthy zeigt, Logs prüfen.
# Live-Log eines Services
docker compose logs -f api-gateway
docker compose logs -f user-role-service
docker compose logs -f absence-vacation-service
# Letzten 50 Zeilen ohne Live-Follow
docker compose logs --tail=50 hr-web# Ein Service
docker compose up --build user-role-service -d
# Mehrere Services gleichzeitig
docker compose up --build auth-service user-role-service absence-vacation-service -ddocker compose up mysql-db mongo-db phpmyadmin mongo-express -dSpring-Boot-Services können dann direkt aus IntelliJ gestartet werden (SPRING_PROFILES_ACTIVE=local).
# Stoppen – Daten bleiben erhalten
docker compose down
# Stoppen + alle Volumes löschen (DB-Reset)
docker compose down -vNach
docker compose down -v: beim nächstendocker compose up --build -dwerden Schema, Rollen und alle Demo-Accounts automatisch neu angelegt.
| Anwendung | URL | Login |
|---|---|---|
| Admin-Frontend | http://localhost:3001 | admin / password |
| HR-Frontend | http://localhost:3002 | hr.mueller / password |
| Schichtleiter-Frontend | http://localhost:3003 | sl.huber / password |
| Flipper Auth Dashboard | http://localhost:3004 | (kein Login) |
| phpMyAdmin | http://localhost:8080 | workforce / workforce |
| Mongo Express | http://localhost:8081 | (kein Login) |
| API Gateway | http://localhost:8000 | (direkte API-Calls) |
| Komponente | Port |
|---|---|
| API Gateway | 8000 |
| Auth Service | 8001 |
| User & Role Service | 8002 |
| Order Service | 8003 |
| Planning Service | 8004 |
| Time Service | 8005 |
| Absence/Vacation Svc. | 8006 |
| Billing Service | 8007 |
| Report/Media Service | 8008 |
| Flipper Auth Service | 8009 |
| Admin Web | 3001 |
| HR Web | 3002 |
| Schichtleiter Web | 3003 |
| Flipper Auth Web | 3004 |
| MySQL | 3307 Host → 3306 Container |
| MongoDB | 27017 |
| phpMyAdmin | 8080 |
| Mongo Express | 8081 |
Das Kanban-Board findet ihr direkt hier im GitHub-Repository unter dem Tab Projects.
main ← stabiler Stand
└── feature/<name> ← Feature-Branch für eine Aufgabe
Beispiele:
feature/time-service-checkinfeature/hr-web-invoicesfeature/flutter-calendarfeature/owasp-testplan
- Karte im Kanban-Board von To Do → In Progress verschieben
- Feature-Branch erstellen:
git checkout -b feature/<name>
- Implementieren, committen
- Branch pushen
- Pull Request auf
mainerstellen oder nach Absprache direkt inmainmergen - Kurze Gegenkontrolle durch eine andere Person (Code Review)
- Merge → Karte auf Done verschieben
<typ>(<bereich>): <kurze Beschreibung>
Beispiele:
feat(time-service): add check-in endpoint
fix(auth-service): correct JWT expiration
feat(flutter): connect calendar to planning API
feat(hr-web): implement invoice creation page
Typen: feat, fix, refactor, docs, style, test
- Packagestruktur:
com.workforce.<servicename>.<schicht> - Schichten:
controller→service→repository→model - REST-Endpoints geben immer
ResponseEntity<T>zurück - Fehlerbehandlung:
@ControllerAdvicemit sinnvollen HTTP-Status-Codes - JWT-Secret nie in den Code schreiben – nur über
application.yml/ Umgebungsvariable - Lombok (
@Data,@RequiredArgsConstructor) für Boilerplate
- Komponenten: PascalCase (
UserList.jsx) - Hooks/Funktionen: camelCase
- API-Aufrufe immer über
src/services/api.js(nicht direktaxios.get(...)) - Kein Token-Handling direkt in Komponenten – nur in
api.jsundlocalStorage
- Screens in
lib/screens/, Services inlib/services/, Modelle inlib/models/ - API-Aufrufe nur über
ApiService, nie direkthttp.get()in Widgets - State-Management mit Provider (
AuthService extends ChangeNotifier) - Keine hardcodierten URLs – Konstante in
ApiService._baseUrl
Viel Erfolg beim Ausarbeiten! Bei Fragen → Issue erstellen oder direkt im Kanban kommentieren.
Folgende drei Punkte wurden nachträglich implementiert, um offene Lücken zu schliessen:
- Config Service – neuer Backend-Microservice für Firmenkonzepte, Stundenregeln und Lohnregeln (ersetzt localStorage im Admin-Web)
- Admin-Web: Stundenübersicht vom Time Service – Echtdaten statt Dummy-Daten
- HR-Web: Abwesenheitskalender – neuer Tab mit monatlicher Kalenderansicht
Warum: Das Admin-Web speicherte Firmenkonzepte, Stundenregeln und Lohnregeln bisher im localStorage des Browsers. Das hat mehrere Nachteile: Daten gehen beim Browser-Clear verloren, sind nicht teamweit geteilt und entsprechen nicht der Microservice-Architektur des restlichen Systems.
Was wurde gemacht:
- Neuer Spring Boot 3.3 Microservice
config-serviceim Packagecom.workforce.adminconfig - Standalone-Maven-Projekt (eigene
spring-boot-starter-parent-Elterndependenz), damit die bestehenden Dockerfiles der 10 anderen Services nicht angefasst werden mussten - Port 8010, Container-Name
config-service - Gleiche Sicherheitsarchitektur wie alle anderen Services: JWT-Auth-Filter, stateless, kein CORS (Gateway regelt das)
- Berechtigungen: GET für ADMIN/HR/SHIFT_LEAD; POST/PUT/DELETE nur für ADMIN
Neue Datenbanktabellen in database/mysql/init.sql:
| Tabelle | Inhalt |
|---|---|
company_concepts |
Firmenkonzepte: Name, Beschreibung, aktiv/inaktiv |
time_rules |
Stundenregeln: max. Tages-/Wochenstunden, Pausenregel |
wage_rules |
Lohnregeln: Stundenansatz, Überstundenansatz, zugeordnetes Konzept |
API-Endpunkte:
GET /api/config/concepts → alle Konzepte
POST /api/config/concepts → Konzept erstellen (ADMIN)
PUT /api/config/concepts/{id} → Konzept bearbeiten (ADMIN)
GET /api/config/time-rules → alle Stundenregeln
POST /api/config/time-rules → Stundenregel erstellen (ADMIN)
PUT /api/config/time-rules/{id} → Stundenregel bearbeiten (ADMIN)
GET /api/config/wage-rules → alle Lohnregeln
POST /api/config/wage-rules → Lohnregel erstellen (ADMIN)
PUT /api/config/wage-rules/{id} → Lohnregel bearbeiten (ADMIN)
Geänderte Infrastruktur-Dateien:
backend/api-gateway/src/main/resources/application.yml→ neue Route/api/config/**docker-compose.yml→config-service-Eintrag, inapi-gatewaydepends_on ergänzt
Warum: Der Admin konnte bisher nur Dummy-Daten aus dem localStorage sehen. Der Time Service (:8005) liefert die echten Check-in/Check-out-Einträge aller Mitarbeiter.
Was wurde gemacht in frontend/admin-web/src/pages/DashboardPage.jsx:
- Neue State-Variablen:
apiTimeTotal,apiBreakViolations,timeFrom,timeTo - Neue Funktion
loadTimeTotal(from, to)→ ruftGET /api/time/totalundGET /api/time/break-violationsparallel auf - Im Tab "Lohn und Stunden": Stundenübersicht zeigt jetzt echte Gesamtstunden pro Mitarbeiter und Pausenverstösse aus dem Time Service
- Datumsfilter (Von/Bis) mit Laden-Button – gleiche UX wie auf der HR-Seite
Warum: In der HR-Web-Applikation gab es zwar eine Absenz-Verwaltungsseite (Genehmigen, Ablehnen, Erstellen), aber keine übersichtliche kalendarische Darstellung aller Absenzen.
Was wurde gemacht in frontend/hr-web/src/pages/AbsencesPage.jsx:
- Neuer Tab "Kalender" neben den bestehenden Tabs "Ausstehende Anträge" und "Alle Absenzen"
- Monatliche Rasteransicht (Mo–So, ISO-Wochenstart)
- Farbkodierung nach Absenztyp:
- Ferien → blau (
#dbeafe/#1e40af) - Krankheit → rot (
#fee2e2/#991b1b) - Sonstiges → grau (
#f3f4f6/#4b5563)
- Ferien → blau (
- Vor-/Zurück-Navigation zwischen Monaten, Heute-Markierung
- Lädt alle Absenzen via
GET /api/absences; das Raster filtert lokal auf den sichtbaren Monat
| Port | Service |
|---|---|
| 8000 | API Gateway |
| 8001 | Auth Service |
| 8002 | User & Role Service |
| 8003 | Order Service |
| 8004 | Planning Service |
| 8005 | Time Service |
| 8006 | Absence & Vacation Service |
| 8007 | Billing Service |
| 8008 | Report/Media Service |
| 8009 | Flipper Auth Service |
| 8010 | Config Service (neu) |
| 3001 | Admin Web |
| 3002 | HR Web |
| 3003 | Schichtleiter Web |
| 3004 | Flipper Auth Web |










