# Workforce Planner – Zielarchitektur ## 1. Architekturdiagramm ``` ┌─────────────────────────────────────────────────────────────────┐ │ CLIENTS │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ Desktop App │ │ Web App │ │ MedWork Plugin │ │ │ │ (tkinter) │ │(React/Vue/…) │ │ (Integration) │ │ │ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │ │ │ │ │ │ │ └──────────────────┼─────────────────────┘ │ │ │ │ │ HTTPS / REST API │ └────────────────────────────┼────────────────────────────────────┘ │ ┌────────────────────────────┼────────────────────────────────────┐ │ API GATEWAY │ │ │ │ ┌─────────────────────────┴────────────────────────────────┐ │ │ │ FastAPI Server │ │ │ │ │ │ │ │ ┌─────────┐ ┌────────────┐ ┌──────────┐ ┌────────┐ │ │ │ │ │ Auth │ │ CORS │ │ Audit │ │ Rate │ │ │ │ │ │Middleware│ │ Middleware │ │Middleware │ │Limiter │ │ │ │ │ └─────────┘ └────────────┘ └──────────┘ └────────┘ │ │ │ │ │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ │ │ API Routes │ │ │ │ │ │ /employees /absences /balance /practices │ │ │ │ │ │ /auth /reports /health /audit │ │ │ │ │ └────────────────────────┬───────────────────────────┘ │ │ │ └───────────────────────────┼───────────────────────────────┘ │ └──────────────────────────────┼──────────────────────────────────┘ │ ┌──────────────────────────────┼──────────────────────────────────┐ │ SERVICE LAYER │ │ (Businesslogik) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ Employee │ │ Absence │ │ Approval │ │ │ │ Service │ │ Service │ │ Workflow │ │ │ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │ │ │ │ │ │ │ │ ┌────────────┴──────────┐ │ │ │ │ │ Business Rules │ │ │ │ │ │ • Überschneidung │ │ │ │ │ │ • Kontingentprüfung │ │ │ │ │ │ • Mindestbesetzung │ │ │ │ │ └───────────────────────┘ │ │ │ │ │ │ └─────────┼────────────────────────────────────────┼──────────────┘ │ │ ┌─────────┼────────────────────────────────────────┼──────────────┐ │ │ REPOSITORY LAYER │ │ │ │ (Datenzugriff) │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ Employee │ │ Absence │ │ Balance │ │ │ │ Repository │ │ Repository │ │ Repository │ │ │ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │ └─────────┼─────────────────┼─────────────────────┼──────────────┘ │ │ │ └─────────────────┼──────────────────────┘ │ ┌──────┴──────┐ │ SQLAlchemy │ │ ORM │ └──────┬──────┘ │ ┌────────────┴────────────┐ │ │ ┌──────┴──────┐ ┌───────┴──────┐ │ SQLite │ │ PostgreSQL │ │ (Dev) │ │ (Produktion)│ └─────────────┘ └──────────────┘ ``` ## 2. Komponentenbeschreibung ### Client Layer | Komponente | Beschreibung | |---|---| | **Desktop App** | Bestehende tkinter-App. Wird umgebaut: statt lokaler JSON-Dateien ruft sie die REST API auf. Gleiche Benutzeranmeldung wie Web. | | **Web App** | Modernes Web-Frontend (React/Vue/Svelte). Nutzt dieselbe API. Responsive für Tablet/Desktop. | | **MedWork Plugin** | Adapter der MedWork-Daten (Patienten, Termine) mit dem Arbeitsplan verknüpft. Eigener API-Endpunkt. | ### API Layer | Komponente | Beschreibung | |---|---| | **FastAPI Server** | Zentraler Einstiegspunkt. Stellt REST-Endpunkte bereit. Auto-generierte OpenAPI-Doku. | | **Auth Middleware** | JWT-basierte Authentifizierung. Gleiche Tokens für Desktop + Web. | | **Audit Middleware** | Protokolliert jede schreibende Aktion (wer, was, wann, alte/neue Werte). | | **CORS Middleware** | Erlaubt Cross-Origin-Zugriffe für Web-Client. | ### Service Layer (Businesslogik) | Komponente | Beschreibung | |---|---| | **EmployeeService** | Mitarbeiter anlegen/bearbeiten/deaktivieren. E-Mail-Duplikat-Check. | | **AbsenceService** | Abwesenheiten erstellen mit Regelprüfung. Kontingent berechnen. | | **Business Rules** | `check_no_overlap` – keine überlappenden Einträge. `check_balance` – genug Ferientage? `check_min_staffing` – Mindestbesetzung gewährleistet? | | **Approval Workflow** | Pending → Approved/Rejected Workflow mit Genehmiger-Zuweisung. | ### Repository Layer | Komponente | Beschreibung | |---|---| | **EmployeeRepository** | CRUD für Mitarbeiter. Einziger Code der direkt SQL ausführt. | | **AbsenceRepository** | CRUD + Spezialabfragen (Überschneidungen, Abwesende pro Tag). | | **BalanceRepository** | Ferientage-Kontingent pro Mitarbeiter/Jahr. | ### Data Layer | Komponente | Beschreibung | |---|---| | **SQLAlchemy ORM** | Abstrahiert Datenbank. Identischer Code für SQLite und PostgreSQL. | | **Practice (Mandant)** | Multi-Tenant: jede Praxis hat eigene Mitarbeiter/Abwesenheiten. | | **AuditLog** | Unveränderliches Änderungsprotokoll mit JSON-Diff. | ## 3. Technologie-Stack | Schicht | Technologie | Begründung | |---|---|---| | **Backend** | Python 3.11+ | Bestehende Codebasis, grosses Ökosystem | | **API Framework** | FastAPI | Async, Auto-Docs, Pydantic-Integration, modern | | **ORM** | SQLAlchemy 2.0 | Industriestandard, DB-agnostisch | | **Validierung** | Pydantic v2 | Schemas für API + Clients, schnell | | **Auth** | JWT (python-jose) | Stateless, Desktop + Web kompatibel | | **Passwörter** | passlib + bcrypt | Industriestandard | | **DB (Dev)** | SQLite | Kein Setup nötig, lokales Testen | | **DB (Prod)** | PostgreSQL 16 | ACID, JSON-Support, skalierbar | | **Desktop** | tkinter (→ später Qt) | Bestehend, HTTP-Client wird ergänzt | | **Web** | React oder Vue.js | SPA, nutzt dieselbe API | | **Deployment** | Docker + Docker Compose | Einfach, reproduzierbar | | **Reverse Proxy** | nginx / Caddy | HTTPS, Load Balancing | ## 4. Datenfluss ### Abwesenheit eintragen (von jedem Client) ``` Client (Desktop/Web) │ ▼ POST /api/v1/absences { employee_id, category, start_date, end_date, reason } │ ▼ Auth Middleware ──── JWT prüfen ──── 401 wenn ungültig │ ▼ AbsenceService.create_absence() │ ├─→ EmployeeRepository.get_by_id() → Mitarbeiter existiert? ├─→ rules.check_no_overlap() → Überschneidung? ├─→ rules.check_balance() → Genug Ferientage? ├─→ rules.check_min_staffing() → Mindestbesetzung OK? │ ├─→ AbsenceRepository.create() → DB INSERT ├─→ AuditLog.log_action() → Protokollierung ├─→ db.commit() │ ▼ Response: 201 Created { id, employee_id, category, status, business_days, ... } │ ▼ Client aktualisiert Kalenderansicht ``` ### Login-Flow (Desktop + Web identisch) ``` Client │ ▼ POST /api/v1/auth/login { email, password } │ ▼ verify_password(plain, hashed) │ ├─→ Falsch: 401 Unauthorized ├─→ Richtig: create_access_token(employee_id, role) │ ▼ Response: { access_token, token_type, employee } │ ▼ Client speichert Token → sendet bei jedem Request als Authorization: Bearer ``` ### Multi-Tenant Datenfluss ``` Praxis A (ID: praxis_a) Praxis B (ID: praxis_b) │ │ ▼ ▼ Mitarbeiter mit Mitarbeiter mit practice_id = praxis_a practice_id = praxis_b │ │ ▼ ▼ Abwesenheiten nur Abwesenheiten nur für Praxis A sichtbar für Praxis B sichtbar Admin-Benutzer mit role=ADMIN kann praxisübergreifend zugreifen. ``` ## 5. Deployment-Konzept ### Entwicklung (lokal) ``` ┌─────────────────────────────────────────┐ │ Entwickler-PC │ │ │ │ uvicorn workforce_planner.api.app:app │ │ │ │ │ └──→ SQLite (workforce_planner.db)│ │ │ │ Desktop App (tkinter) → localhost:8000 │ └─────────────────────────────────────────┘ ``` ### Produktion (Docker) ``` ┌──────────────────────────────────────────────────────┐ │ Server / VPS / Cloud │ │ │ │ ┌────────────┐ │ │ │ nginx │ ◄── HTTPS (Let's Encrypt) │ │ │ :443/:80 │ │ │ └─────┬──────┘ │ │ │ │ │ ┌─────┴──────┐ ┌──────────────────────────────┐ │ │ │ FastAPI │ │ PostgreSQL │ │ │ │ :8000 ├──►│ :5432 │ │ │ │ (2 Worker) │ │ Volume: /data/postgres │ │ │ └────────────┘ └──────────────────────────────┘ │ │ │ │ docker-compose.yml orchestriert alles │ └──────────────────────────────────────────────────────┘ Desktop Clients ──────► server.praxis.ch:443/api/v1/ Web Clients ──────► server.praxis.ch:443/ ``` ### docker-compose.yml (Ziel) ```yaml services: api: build: . ports: ["8000:8000"] environment: WP_DATABASE_URL: postgresql://wp:secret@db:5432/workforce WP_SECRET_KEY: ${SECRET_KEY} depends_on: [db] db: image: postgres:16-alpine volumes: [pgdata:/var/lib/postgresql/data] environment: POSTGRES_DB: workforce POSTGRES_USER: wp POSTGRES_PASSWORD: secret nginx: image: nginx:alpine ports: ["443:443", "80:80"] volumes: [./nginx.conf:/etc/nginx/conf.d/default.conf] depends_on: [api] volumes: pgdata: ``` ## Aktueller Stand | Komponente | Status | |---|---| | Datenmodelle (Employee, Absence, BalanceAccount, Practice, AuditLog) | ✅ Fertig | | Enums (AbsenceCategory, EmployeeRole, AbsenceStatus) | ✅ Fertig | | Pydantic Schemas (Create/Update/Read) | ✅ Fertig | | Database Setup (SQLite/PostgreSQL) | ✅ Fertig | | Repository Layer (Employee, Absence, Balance) | ✅ Fertig | | Service Layer + Business Rules | ✅ Fertig | | FastAPI Routen (Employees, Absences, Balance) | ✅ Fertig | | JWT Auth + Rollen | ✅ Fertig | | Audit Logging | ✅ Fertig | | Multi-Tenant (Practice) | ✅ Modell fertig | | Desktop-Client Umstellung auf API | ⬜ Nächster Schritt | | Web-Frontend | ⬜ Geplant | | MedWork-Integration | ⬜ Geplant | | Docker Deployment | ⬜ Geplant |