diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..c7f0898 Binary files /dev/null and b/.coverage differ diff --git a/app/templates/base.html b/app/templates/base.html deleted file mode 100644 index 27dad78..0000000 --- a/app/templates/base.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - LeoCRM - - - - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} - {% block content %}{% endblock %} -
- - diff --git a/app/templates/company_form.html b/app/templates/company_form.html deleted file mode 100644 index acf5e22..0000000 --- a/app/templates/company_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

{% if company %}Firma bearbeiten{% else %}Neue Firma{% endif %}

-
- - - - - - - - - - - - - - Abbrechen -
-{% endblock %} diff --git a/app/templates/contact_form.html b/app/templates/contact_form.html deleted file mode 100644 index 68bb1c7..0000000 --- a/app/templates/contact_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

{% if contact %}Kontakt bearbeiten{% else %}Neuer Kontakt{% endif %} – {{ company.name }}

-
- - - - - - - - - - - - - - Abbrechen -
-{% endblock %} diff --git a/app/templates/contact_list.html b/app/templates/contact_list.html deleted file mode 100644 index 4a31326..0000000 --- a/app/templates/contact_list.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

{{ company.name }} – Kontaktpersonen

-Neuer Kontakt -Zurück -{% if contacts %} - - - - - - - - - - - - {% for c in contacts %} - - - - - - - - {% endfor %} - -
NamePositionEmailTelefonAktionen
{{ c.first_name }} {{ c.last_name }}{{ c.position or '-' }}{{ c.email or '-' }}{{ c.phone or '-' }} - Edit -
- -
-
-{% else %} -

Keine Kontaktpersonen erfasst.

-{% endif %} -{% endblock %} diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html deleted file mode 100644 index 3062976..0000000 --- a/app/templates/dashboard.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Dashboard

-Neue Firma -{% if companies %} - - - - - - - - - - - {% for c in companies %} - - - - - - - {% endfor %} - -
NameTelefonEmailAktionen
{{ c.name }}{{ c.phone or '-' }}{{ c.email or '-' }} - Edit -
- -
-
-{% else %} -

Noch keine Firmen erfasst.

-{% endif %} -{% endblock %} diff --git a/app/templates/login.html b/app/templates/login.html deleted file mode 100644 index 359c936..0000000 --- a/app/templates/login.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Login

-
- - - - - -
-

Noch kein Konto? Registrieren

-{% endblock %} diff --git a/app/templates/register.html b/app/templates/register.html deleted file mode 100644 index 7491a52..0000000 --- a/app/templates/register.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} -{% block content %} -

Registrieren

-
- - - - - -
-

Bereits registriert? Anmelden

-{% endblock %} diff --git a/codebase-vs-requirements.md b/codebase-vs-requirements.md new file mode 100644 index 0000000..4c7e8eb --- /dev/null +++ b/codebase-vs-requirements.md @@ -0,0 +1,540 @@ +# LeoCRM — Codebase vs Requirements Analysis + +**Datum:** 2026-06-28 +**Prüfer:** Codebase Explorer (Agent Zero) +**Methode:** Read-only-Inspektion der bestehenden Codebase gegen bereinigte `requirements.md` + +--- + +## 1. Bestehende Architektur-Übersicht + +### Stack + +| Komponente | Code-Realität | Requirements | Status | +|------------|-------------|-------------|--------| +| Backend | FastAPI 0.115.6 | FastAPI | ✅ kompatibel | +| Python | 3.11+ (pyproject.toml) | 3.12 (Annahme 10) | ⚠️ Minor-Abweichung | +| Datenbank | **SQLite** (WAL mode) | **PostgreSQL 16** | ❌ KONFLIKT | +| ORM | SQLAlchemy 2.0.36 | (offen — architecture.md) | ✅ kompatibel | +| Frontend | **Jinja2 Templates** (server-side) | **React SPA** (client-side) | ❌ KONFLIKT | +| Auth | Starlette SessionMiddleware (Cookie) | Session-basiert (Cookie) | ✅ kompatibel | +| Deployment | Docker (single container) | Coolify (Docker) | ⚠️ Single-Container vs Multi-Container | +| Testing | pytest (backend only) | pytest + Vitest + Playwright | ⚠️ Backend-only | + +### Projekt-Struktur + +``` +app/ +├── main.py — FastAPI app, lifespan, middleware, router wiring +├── config.py — Pydantic Settings (env: LEOCRM_*) +├── deps.py — Auth dependencies (get_current_user, require_admin) +├── db/ +│ ├── models.py — 862 Zeilen, 15 SQLAlchemy-Modelle (alle Core, keine Plugins) +│ ├── session.py — SQLite-Engine, SessionLocal, get_db dependency +│ └── init_db.py — Table creation + demo seed (admin/admin) +├── routes/ +│ ├── api_routes.py — JSON auth endpoints (/api/auth/login, /api/auth/logout) +│ ├── html_routes.py — HTML auth endpoints (/login, /logout — Jinja2) +│ ├── company_routes.py— JSON API /api/companies (CRUD, search, export) +│ ├── contact_routes.py— JSON API /api/contacts (CRUD, search) +│ ├── dms_routes.py — JSON API /api/dms/* (folders, files, search, links, bulk) +│ ├── tag_routes.py — JSON API /api/tags (CRUD, assign, bulk-assign) +│ ├── calendar_routes.py— JSON API /api/calendars, /api/entries (CRUD, shares, subtasks, attendees, links) +│ ├── notification_routes.py — JSON API /api/notifications +│ ├── import_routes.py — JSON API /api/companies/import, /api/contacts/import (CSV) +│ ├── public_routes.py — Public share links /api/public/share/{token} +│ └── health_routes.py — /api/health +├── services/ +│ ├── auth_service.py — bcrypt password hashing, authenticate_user +│ ├── company_service.py — Company CRUD logic +│ ├── contact_service.py — Contact CRUD logic +│ ├── dms_service.py — DMS file/folder operations (26KB, größte Service-Datei) +│ ├── tag_service.py — Tag CRUD + assignment +│ ├── calendar_service.py — Calendar/entry/subtask/attendee/notification logic (21KB) +│ ├── permission_service.py— DMS permissions + share links +│ ├── import_service.py — CSV import for companies/contacts +│ └── export_service.py — CSV/XLSX export for companies +├── schemas/ — Pydantic schemas (auth, company, contact, dms, tag, calendar, common) +└── templates/ — Jinja2 HTML templates (login, register, dashboard, company_form, contact_form, contact_list, base) +``` + +### Patterns + +- **Monolith:** Single FastAPI app, alle Module fest eingebaut +- **Dual-Interface:** HTML routes (Jinja2) + JSON API routes parallel +- **Service-Layer:** Business-Logik in `services/`, Routes sind dünn +- **SQLAlchemy 2.0:** DeclarativeBase, Mapped types, mapped_column +- **Soft-Delete:** `deleted_at` auf Company, Contact, Folder, File +- **N:M Junctions:** CompanyContact, TagAssignment, FileEntityLink, EntryLink, CalendarShare +- **RBAC:** 3 Rollen (admin, editor, viewer) — hardcoded in `require_admin` dependency +- **Demo-Seed:** init_db() erstellt admin/admin + 2 Firmen + 3 Kontakte + +--- + +## 2. Konflikte: Requirements vs Code-Realität + +### K1: Multi-Tenant (F-AUTH-07, F-CORE-02) — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| tenant_id | Auf allen Core-Tabellen | **Nirgendwo vorhanden** | +| Tenant-Isolation | ORM filtert automatisch | **Keine Filterung** | +| User-Tenant-Zuordnung | User kann zu mehreren Tenants gehören | **Nicht implementiert** | +| Tenant-Switch UI | Wechsel aktiver Tenant | **Nicht vorhanden** | +| Plugin-Tabellen | Müssen tenant_id haben | **N/A (keine Plugins)** | + +**Evidence:** +- `models.py` Zeile 42: `class User(Base):` docstring sagt explizit `"Login account for LeoCRM (single-tenant)."` +- Keine `tenant_id`-Spalte auf Company, Contact, Folder, File, Tag, Calendar, CalendarEntry, Notification, Permission, ShareLink +- `deps.py`: Session speichert nur `user_id`, kein `tenant_id`-Kontext +- Keine Tenant-Modell-Klasse existiert + +**Impact:** Fundamentale Architektur-Veränderung erforderlich. Jede Tabelle braucht tenant_id, ORM-Queries müssen tenant-gefiltert sein, User-Tenant-Mapping-Tabelle nötig. + +--- + +### K2: Plugin-System (F-PLUGIN-01, F-PLUGIN-02) — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Plugin-Architektur | Core-Feature v1 | **Nicht existent** | +| DMS/Kalender/Tags/Mail | Als Plugins implementiert | **Fest im Core eingebaut** | +| Plugin-Manifest | Definiertes Format | **Nicht vorhanden** | +| Lifecycle-Hooks | install/activate/deactivate/uninstall | **Nicht vorhanden** | +| Plugin-API-Endpunkte | Plugins registrieren eigene Routes | **Nicht vorhanden** | +| Plugin-DB-Migration | Eigene Migrationen | **Nicht vorhanden** | +| Plugin-Abhängigkeiten | Deklarierbar | **Nicht vorhanden** | + +**Evidence:** +- `grep -rn 'plugin\|Plugin\|manifest\|lifecycle\|activate\|deactivate' app/` → **0 Treffer** +- DMS: `models.py` Folder/File/FileEntityLink + `dms_service.py` (26KB) + `dms_routes.py` — alles fest im Core +- Kalender: `models.py` Calendar/CalendarShare/CalendarEntry/Attendee/EntryLink/SubTask + `calendar_service.py` (21KB) + `calendar_routes.py` — fest im Core +- Tags: `models.py` Tag/TagAssignment + `tag_service.py` + `tag_routes.py` — fest im Core +- Keine Plugin-Registry, kein Plugin-Loader, kein Manifest-Format + +**Impact:** Komplette Plugin-Architektur muss neu gebaut werden. Bestehende DMS/Kalender/Tag-Module müssen in Plugins umgewandelt werden. + +--- + +### K3: Datenbank — SQLite vs PostgreSQL — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| DB-Engine | PostgreSQL 16 | **SQLite** | +| Connection-Pooling | PostgreSQL MVCC | **SQLite WAL, check_same_thread=False** | +| Concurrent Writes | Multi-User fähig | **SQLite limitiert** | + +**Evidence:** +- `config.py`: `db_path: str = Field(default=str(Path("/data/leocrm.db")))` → SQLite-Datei +- `config.py`: `database_url` property → `f"sqlite:///{self.db_path}"` +- `session.py`: SQLite-spezifische PRAGMAs (`PRAGMA foreign_keys = ON`, `PRAGMA journal_mode = WAL`) +- `session.py`: `connect_args={"check_same_thread": False}` — SQLite-only +- `pyproject.toml`: Keine `psycopg2`/`asyncpg`/`psycopg`-Dependency + +**Impact:** DB-Layer muss auf PostgreSQL umgestellt werden. Session-Engine, PRAGMAs, connect_args müssen angepasst werden. + +--- + +### K4: Frontend — Jinja2 vs React SPA — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Frontend | React SPA (client-side) | **Jinja2 Templates (server-side)** | +| i18n | DE + EN, Sprachwahl persistiert | **Nicht implementiert** | +| UI-Plugin-Framework | Plugins registrieren UI-Komponenten | **Nicht vorhanden** | + +**Evidence:** +- `app/templates/`: 7 Jinja2-HTML-Templates (login, register, dashboard, company_form, contact_form, contact_list, base) +- `html_routes.py`: Jinja2Templates, TemplateResponse +- Keine `package.json`, keine `.tsx`/`.jsx`-Dateien, kein React/Vite-Setup +- `pyproject.toml`: `jinja2==3.1.5` als Dependency +- Requirements Annahme 3: "SPA-Frontend: Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9)" + +**Impact:** Komplettes Frontend muss als React SPA neu gebaut werden. Jinja2-Templates und HTML-Routes werden obsolet. UI-Plugin-Framework (F-CORE-04) muss in React integriert werden. + +--- + +### K5: F-CORE-01 — Event Bus — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Event Bus | Core-Feature v1 | **Nicht implementiert** | +| Events emit/subscribe | Typisiert, Payload, asynchron | **Nicht vorhanden** | +| Plugin-Listener | Registrieren beim Aktivieren | **N/A** | + +**Evidence:** `grep -rn 'event.bus\|EventBus\|event_bus\|emit\|subscribe\|listener' app/` → **0 Treffer** + +--- + +### K6: F-CORE-05 — Service Container / DI — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Service Container | Core-Services über Container | **Nicht implementiert** | +| DI für Plugins | Services injiziert | **N/A** | +| Mocking für Tests | Mock-Services injizierbar | **Nur DB-Session override** | + +**Evidence:** Services werden direkt importiert (`from app.services import company_service`), nicht über Container. FastAPI `Depends()` ist das einzige DI-Muster, aber nur für Request-Scoped dependencies (DB-Session, Current-User). + +--- + +### K7: F-CORE-06 — API-First Architecture — TEILWEISE + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Alle Features über API | API-First | **Teilweise** — API routes existieren für alle Module | +| UI ist API-Client | UI nutzt API | **❌ Jinja2 rendert server-side** | +| API versioniert | z.B. /api/v1/ | **❌ Keine Versionierung** | +| OpenAPI/Swagger | Auto-gen, dokumentiert | **⚠️ FastAPI auto-gen existiert, aber nicht explizit konfiguriert** | +| Plugin-API-Endpunkte | Registrierbar | **N/A** | +| KI-Copilot nutzt API | Gleiche Endpunkte | **Nicht implementiert** | + +**Evidence:** +- API routes: `/api/companies`, `/api/contacts`, `/api/dms/*`, `/api/tags/*`, `/api/calendars`, `/api/entries`, `/api/notifications`, `/api/auth/*` +- Kein `/api/v1/` Prefix — alle routes sind unversioniert +- FastAPI generiert automatisch OpenAPI unter `/openapi.json`, aber nicht explizit konfiguriert oder dokumentiert +- HTML routes existieren parallel (`/login`, `/` dashboard) — UI ist NICHT API-Client + +--- + +### K8: F-CORE-07 — Async Job Queue — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Queue-System | Background-Jobs asynchron | **Nicht implementiert** | +| Retry-Logic | Automatische Retries | **Nicht vorhanden** | +| Dead-Letter-Queue | Bei wiederholtem Fehlschlag | **Nicht vorhanden** | +| Job-Status UI | Sichtbar im UI | **Nicht vorhanden** | + +**Evidence:** `grep -rn 'celery\|Celery\|queue\|Queue\|async_job\|background_job\|job_queue' app/` → **0 Treffer** + +--- + +### K9: F-CORE-08 — Caching-Strategie — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Cache-Backend | Sessions, Query-Cache, Plugin-Data | **Nicht implementiert** | +| Cache-Invalidierung | Event-basiert | **N/A** | +| TTL-Caching | Fallback | **Nur `@lru_cache` für Settings** | + +**Evidence:** `grep -rn 'cache\|Cache\|redis\|Redis' app/` → nur `functools.lru_cache` in `config.py` für Settings-Caching. Kein Redis, kein Query-Cache. + +--- + +### K10: F-CORE-09 — User-Profile und Preferences — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| User-Profile | Profil mit Preferences | **Nicht implementiert** | +| Sprache/Zeitzone/Theme | Umschaltbar | **Nicht vorhanden** | +| Dashboard-Konfiguration | Konfigurierbar | **Nicht vorhanden** | +| Plugin-Preferences | Eigene Felder registrierbar | **N/A** | + +**Evidence:** `User`-Modell hat nur: id, username, password_hash, role, personal_folder_id, default_calendar_id, created_at. Keine Preferences, keine Sprache, keine Zeitzone. + +--- + +### K11: F-CORE-10 — Storage-Backend — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| S3-kompatibel | Konfigurierbar | **Nicht implementiert** | +| Lokales Volume | Alternative | **Lokales Dateisystem** | +| Presigned-URLs | Download ohne Plugin-Code | **Nicht vorhanden** | +| Storage-Service | Core-Service für Plugins | **Direkter Dateizugriff** | + +**Evidence:** +- `config.py`: `dms_storage_path: str = Field(default="/data/dms")` — lokales Verzeichnis +- `dms_service.py`: Direkter Dateizugriff via `open()`, `Path`-Operationen +- Keine S3/MinIO/boto3-Integration +- `grep -rn 's3\|S3\|boto3\|storage_backend\|presigned' app/` → **0 Treffer** + +--- + +### K12: F-CORE-11 — Generic Import/Export Service — TEILWEISE + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| CSV-Import | Mit Preview, Dry-Run, Fehler-Reporting | **⚠️ Nur direkter Import ohne Preview/Dry-Run** | +| Excel-Export | Feld-Auswahl, Filterung | **⚠️ CSV + XLSX Export, aber begrenzte Feld-Auswahl** | +| Plugin-Definitionen | Registrierbar | **N/A** | + +**Evidence:** +- `import_service.py`: `import_companies_csv()`, `import_contacts_csv()` — direkter Import, kein Preview, kein Dry-Run +- `export_service.py`: `export_companies_csv()`, `export_companies_xlsx()` — Export funktioniert, aber nicht generisch/plugin-fähig +- Import/Export ist hardcoded für Companies/Contacts, nicht generisch + +--- + +### K13: F-CORE-12 — PDF/Document Generation Service — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| PDF-Generierung | Aus Templates | **Nicht implementiert** | +| Template-Engine | Variablen, Conditionals, Tabellen | **Nicht vorhanden** | +| Storage-Integration | PDFs im Storage gespeichert | **N/A** | + +**Evidence:** `grep -rn 'pdf\|PDF\|weasyprint\|reportlab\|pdfkit' app/` → nur DMS-Preview (stream existing PDFs), keine Generierung + +--- + +### K14: F-CORE-13 — Notification Service — TEILWEISE + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| In-App-Notifications | Bell-Icon, Badge-Zähler | **⚠️ DB-Modell existiert, keine UI** | +| E-Mail-Channel | Notifications per Mail | **Nicht implementiert** | +| Preferences | Pro User konfigurierbar | **Nicht vorhanden** | +| Tenant-Isolation | Pro Tenant isoliert | **N/A (single-tenant)** | +| Plugin-Notification-Typen | Registrierbar | **N/A** | + +**Evidence:** +- `models.py`: `Notification`-Modell existiert (id, user_id, type, title, body, related_entry_id, is_read, created_at) +- `notification_routes.py`: API für List/Mark-Read existiert +- Keine E-Mail-Integration, keine Preferences, kein Badge-Zähler in UI (Jinja2-Templates haben kein Notification-UI) + +--- + +### K15: F-AUTH-01 — Login mit E-Mail — KONFLIKT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Login-Feld | **E-Mail** + Passwort | **Username** + Passwort | +| Session-Cookie | HttpOnly, Secure, SameSite=Strict | SameSite=**lax**, https_only conditional | + +**Evidence:** +- `auth_service.py`: `authenticate_user(db, username, password)` — verwendet `username`, nicht `email` +- `models.py`: `User.username: Mapped[str]` — kein `email`-Feld auf User +- `deps.py`: Session speichert `user_id`, kein Tenant-Kontext +- `main.py`: `same_site="lax"` (requirements sagen Strict), `https_only=settings.is_production` (requirements sagen Secure) + +--- + +### K16: F-AUTH-03 — User-Verwaltung durch Admin — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Admin legt User an | E-Mail, Name, Rolle, Passwort | **Nicht implementiert** | +| User-Tenant-Zuordnung | User wird Tenant zugeordnet | **N/A** | +| Keine Self-Registration | Admin-only | **⚠️ Register-Template existiert** | + +**Evidence:** +- Keine User-Management-Routes (kein `/api/users`, kein Admin-User-CRUD) +- `app/templates/register.html` existiert — Self-Registration-Template (widerspricht Non-Goal #1) +- `init_db.py`: Demo-Seed erstellt nur admin/admin + +--- + +### K17: F-AUTH-05 — Passwort-Reset — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Reset-Flow | E-Mail mit Reset-Link | **Nicht implementiert** | +| Reset-Link | Gültig 24h | **Nicht vorhanden** | + +**Evidence:** Keine Reset-Routes, keine Reset-Templates, keine Token-Generierung. + +--- + +### K18: F-AUTH-07 — Multi-Tenant — FEHLT (siehe K1) + +Bereits in K1 abgedeckt. Keine Tenant-Modelle, keine User-Tenant-Mapping-Tabelle. + +--- + +### K19: DMS/Calendar/Tags als Core vs Plugin — ARCHITEKTUR-KONFLIKT + +| Modul | Requirements | Code-Realität | +|-------|-------------|---------------| +| DMS | v2-Plugin (F-FILE/F-DMS/F-LINK/F-PERM) | **Core: 3 Modelle + 26KB Service + eigene Routes** | +| Kalender | v2-Plugin (F-CAL-01..18) | **Core: 6 Modelle + 21KB Service + eigene Routes** | +| Tags | v2-Plugin (F-TAG-01..04) | **Core: 2 Modelle + 8KB Service + eigene Routes** | +| Mail | v2-Plugin (F-MAIL-01..19) | **Nicht implementiert** | + +**Evidence:** Alle Module sind direkt in `models.py`, `services/`, `routes/` integriert. Keine Plugin-Grenzen, keine Plugin-Schnittstellen. + +**Hinweis:** Requirements sagen Plugin-System ist v1-Core-Feature, aber die Module selbst sind v2-Plugins. Das bedeutet: In v1 muss das Plugin-System gebaut werden, aber DMS/Kalender/Tags können als v2-Plugins nachgezogen werden. Die bestehenden Implementierungen können als Referenz dienen, müssen aber auf Plugin-Architektur umgebaut werden. + +--- + +## 3. Kompatibel — Was bereits passt + +### ✅ Session-basierte Auth (F-AUTH-01/02, Annahme 12) +- Starlette `SessionMiddleware` mit signed Cookie +- `session_cookie="leocrm_session"`, `max_age` konfigurierbar +- Login setzt `request.session[SESSION_USER_ID_KEY] = user.id` +- Logout cleared session +- **Kompatibel** mit Requirements (Session-basiert, Cookie-basiert) + +### ✅ RBAC Grundgerüst (F-AUTH-04/06) +- 3 Rollen: admin, editor, viewer +- `require_admin` dependency prüft `user.role == "admin"` +- `get_current_user` dependency für auth-geschützte Routes +- **Kompatibel** mit Requirements (3 Rollen v1) + +### ✅ Company/Contact CRUD (F-COMP-01..06, F-CONT-01..07) +- Company: 27 Felder (Name, Adresse, Industrie, Revenue, etc.) +- Contact: 29 Felder (Name, Email, Phone, Title, etc.) +- N:M Junction: `CompanyContact` +- Soft-Delete: `deleted_at` auf beiden +- Pagination, Search, Filter, Sort in Routes +- **Kompatibel** mit Requirements + +### ✅ Data-Features (F-DATA-01..04) +- Pagination: `PageResponse` schema +- Search: Query-Parameter in company/contact routes +- Sort: Sortier-Parameter +- Soft-Delete: `deleted_at` + restore functionality +- **Kompatibel** mit Requirements + +### ✅ Health-Check (F-INFRA-01) +- `/api/health` endpoint, prüft DB, gibt Status + Version +- Nicht auth-geschützt (für Coolify/LB) +- **Kompatibel** mit Requirements + +### ✅ Import/Export Grundgerüst (F-MIG-01, F-DATA-01/02) +- CSV-Import für Companies/Contacts +- CSV + XLSX Export für Companies +- **Teilweise kompatibel** — fehlt Preview, Dry-Run, generische Service-Architektur + +### ✅ DMS-Features (als Referenz für späteres Plugin) +- Folder-Tree mit materialized path +- File-Upload, Preview (PDF), Soft-Delete, Restore +- Entity-Links (N:M zu Companies/Contacts) +- Permissions (Individual/Group/Default) +- Share-Links mit Password + Expiry +- OnlyOffice-Edit-Session +- **Vollständig implementiert** — kann als Plugin-Referenz dienen + +### ✅ Calendar-Features (als Referenz für späteres Plugin) +- Calendar CRUD, Sharing, Visibility-Toggle +- Entries: Events/Tasks/Reminders, Kanban-Status +- Subtasks, Attendees, Entry-Links +- Notifications für Reminders/Invites/Shares +- **Vollständig implementiert** — kann als Plugin-Referenz dienen + +### ✅ Tag-System (als Referenz für späteres Plugin) +- Tag CRUD (admin-only), Color, Assignment +- Bulk-Assign, Entity-Type polymorphic +- **Vollständig implementiert** — kann als Plugin-Referenz dienen + +### ✅ Testing-Setup (F-TEST-01) +- pytest mit 20+ Test-Dateien +- conftest.py mit Fixtures +- Coverage-Messung konfiguriert +- **Teilweise kompatibel** — fehlt Vitest (Frontend) und Playwright (E2E) + +--- + +## 4. F-CORE-Feature-Matrix + +| F-CORE-ID | Feature | Status im Code | Anmerkung | +|-----------|--------|---------------|----------| +| F-CORE-01 | Event Bus | ❌ Nicht implementiert | Keine Event-Infrastruktur | +| F-CORE-02 | Tenant-Isolation | ❌ Nicht implementiert | Kein tenant_id, single-tenant | +| F-CORE-03 | Plugin-DB-Migration | ❌ Nicht implementiert | Kein Plugin-System | +| F-CORE-04 | UI-Plugin-Framework | ❌ Nicht implementiert | Jinja2, keine Plugin-UI | +| F-CORE-05 | Service Container / DI | ❌ Nicht implementiert | Direkte Imports, nur FastAPI Depends | +| F-CORE-06 | API-First Architecture | ⚠️ Teilweise | API routes existieren, aber HTML parallel, keine Versionierung | +| F-CORE-07 | Async Job Queue | ❌ Nicht implementiert | Keine Queue-Infrastruktur | +| F-CORE-08 | Caching-Strategie | ❌ Nicht implementiert | Nur lru_cache für Settings | +| F-CORE-09 | User-Profile/Preferences | ❌ Nicht implementiert | User hat nur username/role | +| F-CORE-10 | Storage-Backend | ❌ Nicht implementiert | Lokales Dateisystem, kein S3 | +| F-CORE-11 | Generic Import/Export | ⚠️ Teilweise | CSV/XLSX funktioniert, nicht generisch, kein Preview/Dry-Run | +| F-CORE-12 | PDF Generation | ❌ Nicht implementiert | Keine PDF-Generierung | +| F-CORE-13 | Notification Service | ⚠️ Teilweise | DB-Modell + API existiert, keine UI, kein E-Mail-Channel | + +**Bilanz:** 0/13 vollständig implementiert, 3/13 teilweise, 10/13 fehlen komplett. + +--- + +## 5. Empfehlung: Was vor Phase 2 angepasst werden muss + +### Priorität 1 — Fundamentale Architektur (vor allem anderen) + +1. **Datenbank-Migration: SQLite → PostgreSQL** + - `config.py`: `database_url` auf PostgreSQL umstellen + - `session.py`: SQLite-PRAGMAs entfernen, PostgreSQL-Engine konfigurieren + - `pyproject.toml`: `psycopg[binary]` oder `asyncpg` hinzufügen + - `docker-compose.yml`: PostgreSQL-Service hinzufügen + +2. **Multi-Tenant-Architektur** + - Neues `Tenant`-Modell + `UserTenant`-Mapping-Tabelle + - `tenant_id`-Spalte auf ALLE Core-Tabellen (Company, Contact, Folder, File, Tag, Calendar, etc.) + - ORM-Query-Filter: automatische tenant_id-Filterung (SQLAlchemy Event oder Query-Wrapper) + - Session-Kontext: aktiver tenant_id in Session speichern + - Tenant-Switch-Endpoint + UI + +3. **Frontend-Wechsel: Jinja2 → React SPA** + - React-Projekt-Setup (Vite + React + TypeScript) + - API-Client-Layer (fetch/axios gegen /api/* Endpunkte) + - Jinja2-Templates und html_routes.py werden obsolet + - i18n-Integration (DE + EN) + - UI-Plugin-Framework vorbereiten (F-CORE-04) + +### Priorität 2 — Core-Infrastructure (F-CORE) + +4. **Service Container / DI (F-CORE-05)** + - Zentralen Service-Container implementieren + - Core-Services registrieren: DB, Cache, Event Bus, Auth, Config, Logger + - Plugin-Schnittstelle für Service-Requests definieren + +5. **Event Bus (F-CORE-01)** + - Event-Publish/Subscribe-System implementieren + - Typisierte Events mit Payload + - Asynchrone Verarbeitung (ggf. via Job Queue) + +6. **Plugin-System (F-PLUGIN-01/02)** + - Plugin-Manifest-Format definieren + - Lifecycle-Hooks: install, activate, deactivate, uninstall + - Plugin-Registry + Loader + - Plugin-API-Endpunkt-Registrierung + - Plugin-DB-Migration (F-CORE-03) + - Plugin-Abhängigkeiten + +7. **API-Versionierung (F-CORE-06)** + - `/api/v1/` Prefix für alle API-Routes + - OpenAPI/Swagger explizit konfigurieren und dokumentieren + - HTML-Routes entfernen (UI wird React SPA = API-Client) + +### Priorität 3 — Weitere Core-Infrastructure + +8. **Async Job Queue (F-CORE-07)** — Queue-System für Background-Jobs +9. **Caching (F-CORE-08)** — Redis-Anbindung, Query-Cache, Cache-Invalidierung +10. **Storage-Backend (F-CORE-10)** — S3-kompatibler Storage-Service +11. **User-Profile/Preferences (F-CORE-09)** — Profil-Erweiterung, Preferences +12. **Notification Service (F-CORE-13)** — E-Mail-Channel, Preferences, Badge-UI +13. **PDF Generation (F-CORE-12)** — Template-Engine, PDF-Generierung +14. **Generic Import/Export (F-CORE-11)** — Generischer Service, Preview, Dry-Run + +### Priorität 4 — Auth-Ergänzungen + +15. **Login auf E-Mail umstellen (F-AUTH-01)** — username → email +16. **User-Verwaltung durch Admin (F-AUTH-03)** — Admin-CRUD für User, Tenant-Zuordnung +17. **Passwort-Reset (F-AUTH-05)** — Reset-Flow mit E-Mail +18. **Register-Template entfernen** — Self-Registration ist Non-Goal +19. **Cookie-Security anpassen** — SameSite=Strict, Secure immer + +### Was beibehalten werden kann + +- **Backend-Services** (company_service, contact_service, etc.) — Business-Logik ist solide +- **Pydantic-Schemas** — Können für API-Validierung weiterverwendet werden +- **DB-Modelle** — Felder/Beziehungen sind korrekt, müssen nur tenant_id ergänzt werden +- **Test-Suite** — pytest-Tests können erweitert werden +- **DMS/Calendar/Tag-Implementierungen** — Als Referenz für spätere Plugin-Entwicklung behalten + +--- + +## 6. Zusammenfassung + +| Kategorie | Anzahl | Status | +|-----------|--------|--------| +| Kritische Konflikte | 4 | Multi-Tenant, Plugin-System, DB, Frontend | +| F-CORE fehlend | 10/13 | Event Bus, Tenant-Isolation, Plugin-Migration, UI-Plugin, Service Container, Job Queue, Caching, User-Profile, Storage, PDF | +| F-CORE teilweise | 3/13 | API-First, Import/Export, Notification | +| F-CORE vollständig | 0/13 | — | +| Auth-Konflikte | 4 | Login (username vs email), User-Verwaltung, Passwort-Reset, Cookie-Security | +| Kompatibel | 7+ | Session-Auth, RBAC, Company/Contact CRUD, Data-Features, Health, DMS/Calendar/Tags (als Referenz) | + +**Fazit:** Die bestehende Codebase ist eine funktionsfähige v0.1-Implementierung (Single-Tenant, SQLite, Jinja2), die den bereinigten v1-Requirements in 4 kritischen Bereichen nicht entspricht: Multi-Tenant, Plugin-System, PostgreSQL, React SPA. 10 von 13 F-CORE-Features fehlen komplett. Die bestehende Business-Logik (Services, Schemas, Modelle) ist jedoch solide und kann als Basis für den Umbau dienen. Der Aufwand für Phase 2 ist erheblich — es handelt sich um eine Architektur-Migration, nicht um inkrementelle Erweiterungen. diff --git a/docs/codebase-vs-requirements.md b/docs/codebase-vs-requirements.md new file mode 100644 index 0000000..4c7e8eb --- /dev/null +++ b/docs/codebase-vs-requirements.md @@ -0,0 +1,540 @@ +# LeoCRM — Codebase vs Requirements Analysis + +**Datum:** 2026-06-28 +**Prüfer:** Codebase Explorer (Agent Zero) +**Methode:** Read-only-Inspektion der bestehenden Codebase gegen bereinigte `requirements.md` + +--- + +## 1. Bestehende Architektur-Übersicht + +### Stack + +| Komponente | Code-Realität | Requirements | Status | +|------------|-------------|-------------|--------| +| Backend | FastAPI 0.115.6 | FastAPI | ✅ kompatibel | +| Python | 3.11+ (pyproject.toml) | 3.12 (Annahme 10) | ⚠️ Minor-Abweichung | +| Datenbank | **SQLite** (WAL mode) | **PostgreSQL 16** | ❌ KONFLIKT | +| ORM | SQLAlchemy 2.0.36 | (offen — architecture.md) | ✅ kompatibel | +| Frontend | **Jinja2 Templates** (server-side) | **React SPA** (client-side) | ❌ KONFLIKT | +| Auth | Starlette SessionMiddleware (Cookie) | Session-basiert (Cookie) | ✅ kompatibel | +| Deployment | Docker (single container) | Coolify (Docker) | ⚠️ Single-Container vs Multi-Container | +| Testing | pytest (backend only) | pytest + Vitest + Playwright | ⚠️ Backend-only | + +### Projekt-Struktur + +``` +app/ +├── main.py — FastAPI app, lifespan, middleware, router wiring +├── config.py — Pydantic Settings (env: LEOCRM_*) +├── deps.py — Auth dependencies (get_current_user, require_admin) +├── db/ +│ ├── models.py — 862 Zeilen, 15 SQLAlchemy-Modelle (alle Core, keine Plugins) +│ ├── session.py — SQLite-Engine, SessionLocal, get_db dependency +│ └── init_db.py — Table creation + demo seed (admin/admin) +├── routes/ +│ ├── api_routes.py — JSON auth endpoints (/api/auth/login, /api/auth/logout) +│ ├── html_routes.py — HTML auth endpoints (/login, /logout — Jinja2) +│ ├── company_routes.py— JSON API /api/companies (CRUD, search, export) +│ ├── contact_routes.py— JSON API /api/contacts (CRUD, search) +│ ├── dms_routes.py — JSON API /api/dms/* (folders, files, search, links, bulk) +│ ├── tag_routes.py — JSON API /api/tags (CRUD, assign, bulk-assign) +│ ├── calendar_routes.py— JSON API /api/calendars, /api/entries (CRUD, shares, subtasks, attendees, links) +│ ├── notification_routes.py — JSON API /api/notifications +│ ├── import_routes.py — JSON API /api/companies/import, /api/contacts/import (CSV) +│ ├── public_routes.py — Public share links /api/public/share/{token} +│ └── health_routes.py — /api/health +├── services/ +│ ├── auth_service.py — bcrypt password hashing, authenticate_user +│ ├── company_service.py — Company CRUD logic +│ ├── contact_service.py — Contact CRUD logic +│ ├── dms_service.py — DMS file/folder operations (26KB, größte Service-Datei) +│ ├── tag_service.py — Tag CRUD + assignment +│ ├── calendar_service.py — Calendar/entry/subtask/attendee/notification logic (21KB) +│ ├── permission_service.py— DMS permissions + share links +│ ├── import_service.py — CSV import for companies/contacts +│ └── export_service.py — CSV/XLSX export for companies +├── schemas/ — Pydantic schemas (auth, company, contact, dms, tag, calendar, common) +└── templates/ — Jinja2 HTML templates (login, register, dashboard, company_form, contact_form, contact_list, base) +``` + +### Patterns + +- **Monolith:** Single FastAPI app, alle Module fest eingebaut +- **Dual-Interface:** HTML routes (Jinja2) + JSON API routes parallel +- **Service-Layer:** Business-Logik in `services/`, Routes sind dünn +- **SQLAlchemy 2.0:** DeclarativeBase, Mapped types, mapped_column +- **Soft-Delete:** `deleted_at` auf Company, Contact, Folder, File +- **N:M Junctions:** CompanyContact, TagAssignment, FileEntityLink, EntryLink, CalendarShare +- **RBAC:** 3 Rollen (admin, editor, viewer) — hardcoded in `require_admin` dependency +- **Demo-Seed:** init_db() erstellt admin/admin + 2 Firmen + 3 Kontakte + +--- + +## 2. Konflikte: Requirements vs Code-Realität + +### K1: Multi-Tenant (F-AUTH-07, F-CORE-02) — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| tenant_id | Auf allen Core-Tabellen | **Nirgendwo vorhanden** | +| Tenant-Isolation | ORM filtert automatisch | **Keine Filterung** | +| User-Tenant-Zuordnung | User kann zu mehreren Tenants gehören | **Nicht implementiert** | +| Tenant-Switch UI | Wechsel aktiver Tenant | **Nicht vorhanden** | +| Plugin-Tabellen | Müssen tenant_id haben | **N/A (keine Plugins)** | + +**Evidence:** +- `models.py` Zeile 42: `class User(Base):` docstring sagt explizit `"Login account for LeoCRM (single-tenant)."` +- Keine `tenant_id`-Spalte auf Company, Contact, Folder, File, Tag, Calendar, CalendarEntry, Notification, Permission, ShareLink +- `deps.py`: Session speichert nur `user_id`, kein `tenant_id`-Kontext +- Keine Tenant-Modell-Klasse existiert + +**Impact:** Fundamentale Architektur-Veränderung erforderlich. Jede Tabelle braucht tenant_id, ORM-Queries müssen tenant-gefiltert sein, User-Tenant-Mapping-Tabelle nötig. + +--- + +### K2: Plugin-System (F-PLUGIN-01, F-PLUGIN-02) — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Plugin-Architektur | Core-Feature v1 | **Nicht existent** | +| DMS/Kalender/Tags/Mail | Als Plugins implementiert | **Fest im Core eingebaut** | +| Plugin-Manifest | Definiertes Format | **Nicht vorhanden** | +| Lifecycle-Hooks | install/activate/deactivate/uninstall | **Nicht vorhanden** | +| Plugin-API-Endpunkte | Plugins registrieren eigene Routes | **Nicht vorhanden** | +| Plugin-DB-Migration | Eigene Migrationen | **Nicht vorhanden** | +| Plugin-Abhängigkeiten | Deklarierbar | **Nicht vorhanden** | + +**Evidence:** +- `grep -rn 'plugin\|Plugin\|manifest\|lifecycle\|activate\|deactivate' app/` → **0 Treffer** +- DMS: `models.py` Folder/File/FileEntityLink + `dms_service.py` (26KB) + `dms_routes.py` — alles fest im Core +- Kalender: `models.py` Calendar/CalendarShare/CalendarEntry/Attendee/EntryLink/SubTask + `calendar_service.py` (21KB) + `calendar_routes.py` — fest im Core +- Tags: `models.py` Tag/TagAssignment + `tag_service.py` + `tag_routes.py` — fest im Core +- Keine Plugin-Registry, kein Plugin-Loader, kein Manifest-Format + +**Impact:** Komplette Plugin-Architektur muss neu gebaut werden. Bestehende DMS/Kalender/Tag-Module müssen in Plugins umgewandelt werden. + +--- + +### K3: Datenbank — SQLite vs PostgreSQL — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| DB-Engine | PostgreSQL 16 | **SQLite** | +| Connection-Pooling | PostgreSQL MVCC | **SQLite WAL, check_same_thread=False** | +| Concurrent Writes | Multi-User fähig | **SQLite limitiert** | + +**Evidence:** +- `config.py`: `db_path: str = Field(default=str(Path("/data/leocrm.db")))` → SQLite-Datei +- `config.py`: `database_url` property → `f"sqlite:///{self.db_path}"` +- `session.py`: SQLite-spezifische PRAGMAs (`PRAGMA foreign_keys = ON`, `PRAGMA journal_mode = WAL`) +- `session.py`: `connect_args={"check_same_thread": False}` — SQLite-only +- `pyproject.toml`: Keine `psycopg2`/`asyncpg`/`psycopg`-Dependency + +**Impact:** DB-Layer muss auf PostgreSQL umgestellt werden. Session-Engine, PRAGMAs, connect_args müssen angepasst werden. + +--- + +### K4: Frontend — Jinja2 vs React SPA — KRITISCH + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Frontend | React SPA (client-side) | **Jinja2 Templates (server-side)** | +| i18n | DE + EN, Sprachwahl persistiert | **Nicht implementiert** | +| UI-Plugin-Framework | Plugins registrieren UI-Komponenten | **Nicht vorhanden** | + +**Evidence:** +- `app/templates/`: 7 Jinja2-HTML-Templates (login, register, dashboard, company_form, contact_form, contact_list, base) +- `html_routes.py`: Jinja2Templates, TemplateResponse +- Keine `package.json`, keine `.tsx`/`.jsx`-Dateien, kein React/Vite-Setup +- `pyproject.toml`: `jinja2==3.1.5` als Dependency +- Requirements Annahme 3: "SPA-Frontend: Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9)" + +**Impact:** Komplettes Frontend muss als React SPA neu gebaut werden. Jinja2-Templates und HTML-Routes werden obsolet. UI-Plugin-Framework (F-CORE-04) muss in React integriert werden. + +--- + +### K5: F-CORE-01 — Event Bus — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Event Bus | Core-Feature v1 | **Nicht implementiert** | +| Events emit/subscribe | Typisiert, Payload, asynchron | **Nicht vorhanden** | +| Plugin-Listener | Registrieren beim Aktivieren | **N/A** | + +**Evidence:** `grep -rn 'event.bus\|EventBus\|event_bus\|emit\|subscribe\|listener' app/` → **0 Treffer** + +--- + +### K6: F-CORE-05 — Service Container / DI — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Service Container | Core-Services über Container | **Nicht implementiert** | +| DI für Plugins | Services injiziert | **N/A** | +| Mocking für Tests | Mock-Services injizierbar | **Nur DB-Session override** | + +**Evidence:** Services werden direkt importiert (`from app.services import company_service`), nicht über Container. FastAPI `Depends()` ist das einzige DI-Muster, aber nur für Request-Scoped dependencies (DB-Session, Current-User). + +--- + +### K7: F-CORE-06 — API-First Architecture — TEILWEISE + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Alle Features über API | API-First | **Teilweise** — API routes existieren für alle Module | +| UI ist API-Client | UI nutzt API | **❌ Jinja2 rendert server-side** | +| API versioniert | z.B. /api/v1/ | **❌ Keine Versionierung** | +| OpenAPI/Swagger | Auto-gen, dokumentiert | **⚠️ FastAPI auto-gen existiert, aber nicht explizit konfiguriert** | +| Plugin-API-Endpunkte | Registrierbar | **N/A** | +| KI-Copilot nutzt API | Gleiche Endpunkte | **Nicht implementiert** | + +**Evidence:** +- API routes: `/api/companies`, `/api/contacts`, `/api/dms/*`, `/api/tags/*`, `/api/calendars`, `/api/entries`, `/api/notifications`, `/api/auth/*` +- Kein `/api/v1/` Prefix — alle routes sind unversioniert +- FastAPI generiert automatisch OpenAPI unter `/openapi.json`, aber nicht explizit konfiguriert oder dokumentiert +- HTML routes existieren parallel (`/login`, `/` dashboard) — UI ist NICHT API-Client + +--- + +### K8: F-CORE-07 — Async Job Queue — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Queue-System | Background-Jobs asynchron | **Nicht implementiert** | +| Retry-Logic | Automatische Retries | **Nicht vorhanden** | +| Dead-Letter-Queue | Bei wiederholtem Fehlschlag | **Nicht vorhanden** | +| Job-Status UI | Sichtbar im UI | **Nicht vorhanden** | + +**Evidence:** `grep -rn 'celery\|Celery\|queue\|Queue\|async_job\|background_job\|job_queue' app/` → **0 Treffer** + +--- + +### K9: F-CORE-08 — Caching-Strategie — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Cache-Backend | Sessions, Query-Cache, Plugin-Data | **Nicht implementiert** | +| Cache-Invalidierung | Event-basiert | **N/A** | +| TTL-Caching | Fallback | **Nur `@lru_cache` für Settings** | + +**Evidence:** `grep -rn 'cache\|Cache\|redis\|Redis' app/` → nur `functools.lru_cache` in `config.py` für Settings-Caching. Kein Redis, kein Query-Cache. + +--- + +### K10: F-CORE-09 — User-Profile und Preferences — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| User-Profile | Profil mit Preferences | **Nicht implementiert** | +| Sprache/Zeitzone/Theme | Umschaltbar | **Nicht vorhanden** | +| Dashboard-Konfiguration | Konfigurierbar | **Nicht vorhanden** | +| Plugin-Preferences | Eigene Felder registrierbar | **N/A** | + +**Evidence:** `User`-Modell hat nur: id, username, password_hash, role, personal_folder_id, default_calendar_id, created_at. Keine Preferences, keine Sprache, keine Zeitzone. + +--- + +### K11: F-CORE-10 — Storage-Backend — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| S3-kompatibel | Konfigurierbar | **Nicht implementiert** | +| Lokales Volume | Alternative | **Lokales Dateisystem** | +| Presigned-URLs | Download ohne Plugin-Code | **Nicht vorhanden** | +| Storage-Service | Core-Service für Plugins | **Direkter Dateizugriff** | + +**Evidence:** +- `config.py`: `dms_storage_path: str = Field(default="/data/dms")` — lokales Verzeichnis +- `dms_service.py`: Direkter Dateizugriff via `open()`, `Path`-Operationen +- Keine S3/MinIO/boto3-Integration +- `grep -rn 's3\|S3\|boto3\|storage_backend\|presigned' app/` → **0 Treffer** + +--- + +### K12: F-CORE-11 — Generic Import/Export Service — TEILWEISE + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| CSV-Import | Mit Preview, Dry-Run, Fehler-Reporting | **⚠️ Nur direkter Import ohne Preview/Dry-Run** | +| Excel-Export | Feld-Auswahl, Filterung | **⚠️ CSV + XLSX Export, aber begrenzte Feld-Auswahl** | +| Plugin-Definitionen | Registrierbar | **N/A** | + +**Evidence:** +- `import_service.py`: `import_companies_csv()`, `import_contacts_csv()` — direkter Import, kein Preview, kein Dry-Run +- `export_service.py`: `export_companies_csv()`, `export_companies_xlsx()` — Export funktioniert, aber nicht generisch/plugin-fähig +- Import/Export ist hardcoded für Companies/Contacts, nicht generisch + +--- + +### K13: F-CORE-12 — PDF/Document Generation Service — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| PDF-Generierung | Aus Templates | **Nicht implementiert** | +| Template-Engine | Variablen, Conditionals, Tabellen | **Nicht vorhanden** | +| Storage-Integration | PDFs im Storage gespeichert | **N/A** | + +**Evidence:** `grep -rn 'pdf\|PDF\|weasyprint\|reportlab\|pdfkit' app/` → nur DMS-Preview (stream existing PDFs), keine Generierung + +--- + +### K14: F-CORE-13 — Notification Service — TEILWEISE + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| In-App-Notifications | Bell-Icon, Badge-Zähler | **⚠️ DB-Modell existiert, keine UI** | +| E-Mail-Channel | Notifications per Mail | **Nicht implementiert** | +| Preferences | Pro User konfigurierbar | **Nicht vorhanden** | +| Tenant-Isolation | Pro Tenant isoliert | **N/A (single-tenant)** | +| Plugin-Notification-Typen | Registrierbar | **N/A** | + +**Evidence:** +- `models.py`: `Notification`-Modell existiert (id, user_id, type, title, body, related_entry_id, is_read, created_at) +- `notification_routes.py`: API für List/Mark-Read existiert +- Keine E-Mail-Integration, keine Preferences, kein Badge-Zähler in UI (Jinja2-Templates haben kein Notification-UI) + +--- + +### K15: F-AUTH-01 — Login mit E-Mail — KONFLIKT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Login-Feld | **E-Mail** + Passwort | **Username** + Passwort | +| Session-Cookie | HttpOnly, Secure, SameSite=Strict | SameSite=**lax**, https_only conditional | + +**Evidence:** +- `auth_service.py`: `authenticate_user(db, username, password)` — verwendet `username`, nicht `email` +- `models.py`: `User.username: Mapped[str]` — kein `email`-Feld auf User +- `deps.py`: Session speichert `user_id`, kein Tenant-Kontext +- `main.py`: `same_site="lax"` (requirements sagen Strict), `https_only=settings.is_production` (requirements sagen Secure) + +--- + +### K16: F-AUTH-03 — User-Verwaltung durch Admin — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Admin legt User an | E-Mail, Name, Rolle, Passwort | **Nicht implementiert** | +| User-Tenant-Zuordnung | User wird Tenant zugeordnet | **N/A** | +| Keine Self-Registration | Admin-only | **⚠️ Register-Template existiert** | + +**Evidence:** +- Keine User-Management-Routes (kein `/api/users`, kein Admin-User-CRUD) +- `app/templates/register.html` existiert — Self-Registration-Template (widerspricht Non-Goal #1) +- `init_db.py`: Demo-Seed erstellt nur admin/admin + +--- + +### K17: F-AUTH-05 — Passwort-Reset — FEHLT + +| Aspekt | Requirements | Code-Realität | +|---------|-------------|---------------| +| Reset-Flow | E-Mail mit Reset-Link | **Nicht implementiert** | +| Reset-Link | Gültig 24h | **Nicht vorhanden** | + +**Evidence:** Keine Reset-Routes, keine Reset-Templates, keine Token-Generierung. + +--- + +### K18: F-AUTH-07 — Multi-Tenant — FEHLT (siehe K1) + +Bereits in K1 abgedeckt. Keine Tenant-Modelle, keine User-Tenant-Mapping-Tabelle. + +--- + +### K19: DMS/Calendar/Tags als Core vs Plugin — ARCHITEKTUR-KONFLIKT + +| Modul | Requirements | Code-Realität | +|-------|-------------|---------------| +| DMS | v2-Plugin (F-FILE/F-DMS/F-LINK/F-PERM) | **Core: 3 Modelle + 26KB Service + eigene Routes** | +| Kalender | v2-Plugin (F-CAL-01..18) | **Core: 6 Modelle + 21KB Service + eigene Routes** | +| Tags | v2-Plugin (F-TAG-01..04) | **Core: 2 Modelle + 8KB Service + eigene Routes** | +| Mail | v2-Plugin (F-MAIL-01..19) | **Nicht implementiert** | + +**Evidence:** Alle Module sind direkt in `models.py`, `services/`, `routes/` integriert. Keine Plugin-Grenzen, keine Plugin-Schnittstellen. + +**Hinweis:** Requirements sagen Plugin-System ist v1-Core-Feature, aber die Module selbst sind v2-Plugins. Das bedeutet: In v1 muss das Plugin-System gebaut werden, aber DMS/Kalender/Tags können als v2-Plugins nachgezogen werden. Die bestehenden Implementierungen können als Referenz dienen, müssen aber auf Plugin-Architektur umgebaut werden. + +--- + +## 3. Kompatibel — Was bereits passt + +### ✅ Session-basierte Auth (F-AUTH-01/02, Annahme 12) +- Starlette `SessionMiddleware` mit signed Cookie +- `session_cookie="leocrm_session"`, `max_age` konfigurierbar +- Login setzt `request.session[SESSION_USER_ID_KEY] = user.id` +- Logout cleared session +- **Kompatibel** mit Requirements (Session-basiert, Cookie-basiert) + +### ✅ RBAC Grundgerüst (F-AUTH-04/06) +- 3 Rollen: admin, editor, viewer +- `require_admin` dependency prüft `user.role == "admin"` +- `get_current_user` dependency für auth-geschützte Routes +- **Kompatibel** mit Requirements (3 Rollen v1) + +### ✅ Company/Contact CRUD (F-COMP-01..06, F-CONT-01..07) +- Company: 27 Felder (Name, Adresse, Industrie, Revenue, etc.) +- Contact: 29 Felder (Name, Email, Phone, Title, etc.) +- N:M Junction: `CompanyContact` +- Soft-Delete: `deleted_at` auf beiden +- Pagination, Search, Filter, Sort in Routes +- **Kompatibel** mit Requirements + +### ✅ Data-Features (F-DATA-01..04) +- Pagination: `PageResponse` schema +- Search: Query-Parameter in company/contact routes +- Sort: Sortier-Parameter +- Soft-Delete: `deleted_at` + restore functionality +- **Kompatibel** mit Requirements + +### ✅ Health-Check (F-INFRA-01) +- `/api/health` endpoint, prüft DB, gibt Status + Version +- Nicht auth-geschützt (für Coolify/LB) +- **Kompatibel** mit Requirements + +### ✅ Import/Export Grundgerüst (F-MIG-01, F-DATA-01/02) +- CSV-Import für Companies/Contacts +- CSV + XLSX Export für Companies +- **Teilweise kompatibel** — fehlt Preview, Dry-Run, generische Service-Architektur + +### ✅ DMS-Features (als Referenz für späteres Plugin) +- Folder-Tree mit materialized path +- File-Upload, Preview (PDF), Soft-Delete, Restore +- Entity-Links (N:M zu Companies/Contacts) +- Permissions (Individual/Group/Default) +- Share-Links mit Password + Expiry +- OnlyOffice-Edit-Session +- **Vollständig implementiert** — kann als Plugin-Referenz dienen + +### ✅ Calendar-Features (als Referenz für späteres Plugin) +- Calendar CRUD, Sharing, Visibility-Toggle +- Entries: Events/Tasks/Reminders, Kanban-Status +- Subtasks, Attendees, Entry-Links +- Notifications für Reminders/Invites/Shares +- **Vollständig implementiert** — kann als Plugin-Referenz dienen + +### ✅ Tag-System (als Referenz für späteres Plugin) +- Tag CRUD (admin-only), Color, Assignment +- Bulk-Assign, Entity-Type polymorphic +- **Vollständig implementiert** — kann als Plugin-Referenz dienen + +### ✅ Testing-Setup (F-TEST-01) +- pytest mit 20+ Test-Dateien +- conftest.py mit Fixtures +- Coverage-Messung konfiguriert +- **Teilweise kompatibel** — fehlt Vitest (Frontend) und Playwright (E2E) + +--- + +## 4. F-CORE-Feature-Matrix + +| F-CORE-ID | Feature | Status im Code | Anmerkung | +|-----------|--------|---------------|----------| +| F-CORE-01 | Event Bus | ❌ Nicht implementiert | Keine Event-Infrastruktur | +| F-CORE-02 | Tenant-Isolation | ❌ Nicht implementiert | Kein tenant_id, single-tenant | +| F-CORE-03 | Plugin-DB-Migration | ❌ Nicht implementiert | Kein Plugin-System | +| F-CORE-04 | UI-Plugin-Framework | ❌ Nicht implementiert | Jinja2, keine Plugin-UI | +| F-CORE-05 | Service Container / DI | ❌ Nicht implementiert | Direkte Imports, nur FastAPI Depends | +| F-CORE-06 | API-First Architecture | ⚠️ Teilweise | API routes existieren, aber HTML parallel, keine Versionierung | +| F-CORE-07 | Async Job Queue | ❌ Nicht implementiert | Keine Queue-Infrastruktur | +| F-CORE-08 | Caching-Strategie | ❌ Nicht implementiert | Nur lru_cache für Settings | +| F-CORE-09 | User-Profile/Preferences | ❌ Nicht implementiert | User hat nur username/role | +| F-CORE-10 | Storage-Backend | ❌ Nicht implementiert | Lokales Dateisystem, kein S3 | +| F-CORE-11 | Generic Import/Export | ⚠️ Teilweise | CSV/XLSX funktioniert, nicht generisch, kein Preview/Dry-Run | +| F-CORE-12 | PDF Generation | ❌ Nicht implementiert | Keine PDF-Generierung | +| F-CORE-13 | Notification Service | ⚠️ Teilweise | DB-Modell + API existiert, keine UI, kein E-Mail-Channel | + +**Bilanz:** 0/13 vollständig implementiert, 3/13 teilweise, 10/13 fehlen komplett. + +--- + +## 5. Empfehlung: Was vor Phase 2 angepasst werden muss + +### Priorität 1 — Fundamentale Architektur (vor allem anderen) + +1. **Datenbank-Migration: SQLite → PostgreSQL** + - `config.py`: `database_url` auf PostgreSQL umstellen + - `session.py`: SQLite-PRAGMAs entfernen, PostgreSQL-Engine konfigurieren + - `pyproject.toml`: `psycopg[binary]` oder `asyncpg` hinzufügen + - `docker-compose.yml`: PostgreSQL-Service hinzufügen + +2. **Multi-Tenant-Architektur** + - Neues `Tenant`-Modell + `UserTenant`-Mapping-Tabelle + - `tenant_id`-Spalte auf ALLE Core-Tabellen (Company, Contact, Folder, File, Tag, Calendar, etc.) + - ORM-Query-Filter: automatische tenant_id-Filterung (SQLAlchemy Event oder Query-Wrapper) + - Session-Kontext: aktiver tenant_id in Session speichern + - Tenant-Switch-Endpoint + UI + +3. **Frontend-Wechsel: Jinja2 → React SPA** + - React-Projekt-Setup (Vite + React + TypeScript) + - API-Client-Layer (fetch/axios gegen /api/* Endpunkte) + - Jinja2-Templates und html_routes.py werden obsolet + - i18n-Integration (DE + EN) + - UI-Plugin-Framework vorbereiten (F-CORE-04) + +### Priorität 2 — Core-Infrastructure (F-CORE) + +4. **Service Container / DI (F-CORE-05)** + - Zentralen Service-Container implementieren + - Core-Services registrieren: DB, Cache, Event Bus, Auth, Config, Logger + - Plugin-Schnittstelle für Service-Requests definieren + +5. **Event Bus (F-CORE-01)** + - Event-Publish/Subscribe-System implementieren + - Typisierte Events mit Payload + - Asynchrone Verarbeitung (ggf. via Job Queue) + +6. **Plugin-System (F-PLUGIN-01/02)** + - Plugin-Manifest-Format definieren + - Lifecycle-Hooks: install, activate, deactivate, uninstall + - Plugin-Registry + Loader + - Plugin-API-Endpunkt-Registrierung + - Plugin-DB-Migration (F-CORE-03) + - Plugin-Abhängigkeiten + +7. **API-Versionierung (F-CORE-06)** + - `/api/v1/` Prefix für alle API-Routes + - OpenAPI/Swagger explizit konfigurieren und dokumentieren + - HTML-Routes entfernen (UI wird React SPA = API-Client) + +### Priorität 3 — Weitere Core-Infrastructure + +8. **Async Job Queue (F-CORE-07)** — Queue-System für Background-Jobs +9. **Caching (F-CORE-08)** — Redis-Anbindung, Query-Cache, Cache-Invalidierung +10. **Storage-Backend (F-CORE-10)** — S3-kompatibler Storage-Service +11. **User-Profile/Preferences (F-CORE-09)** — Profil-Erweiterung, Preferences +12. **Notification Service (F-CORE-13)** — E-Mail-Channel, Preferences, Badge-UI +13. **PDF Generation (F-CORE-12)** — Template-Engine, PDF-Generierung +14. **Generic Import/Export (F-CORE-11)** — Generischer Service, Preview, Dry-Run + +### Priorität 4 — Auth-Ergänzungen + +15. **Login auf E-Mail umstellen (F-AUTH-01)** — username → email +16. **User-Verwaltung durch Admin (F-AUTH-03)** — Admin-CRUD für User, Tenant-Zuordnung +17. **Passwort-Reset (F-AUTH-05)** — Reset-Flow mit E-Mail +18. **Register-Template entfernen** — Self-Registration ist Non-Goal +19. **Cookie-Security anpassen** — SameSite=Strict, Secure immer + +### Was beibehalten werden kann + +- **Backend-Services** (company_service, contact_service, etc.) — Business-Logik ist solide +- **Pydantic-Schemas** — Können für API-Validierung weiterverwendet werden +- **DB-Modelle** — Felder/Beziehungen sind korrekt, müssen nur tenant_id ergänzt werden +- **Test-Suite** — pytest-Tests können erweitert werden +- **DMS/Calendar/Tag-Implementierungen** — Als Referenz für spätere Plugin-Entwicklung behalten + +--- + +## 6. Zusammenfassung + +| Kategorie | Anzahl | Status | +|-----------|--------|--------| +| Kritische Konflikte | 4 | Multi-Tenant, Plugin-System, DB, Frontend | +| F-CORE fehlend | 10/13 | Event Bus, Tenant-Isolation, Plugin-Migration, UI-Plugin, Service Container, Job Queue, Caching, User-Profile, Storage, PDF | +| F-CORE teilweise | 3/13 | API-First, Import/Export, Notification | +| F-CORE vollständig | 0/13 | — | +| Auth-Konflikte | 4 | Login (username vs email), User-Verwaltung, Passwort-Reset, Cookie-Security | +| Kompatibel | 7+ | Session-Auth, RBAC, Company/Contact CRUD, Data-Features, Health, DMS/Calendar/Tags (als Referenz) | + +**Fazit:** Die bestehende Codebase ist eine funktionsfähige v0.1-Implementierung (Single-Tenant, SQLite, Jinja2), die den bereinigten v1-Requirements in 4 kritischen Bereichen nicht entspricht: Multi-Tenant, Plugin-System, PostgreSQL, React SPA. 10 von 13 F-CORE-Features fehlen komplett. Die bestehende Business-Logik (Services, Schemas, Modelle) ist jedoch solide und kann als Basis für den Umbau dienen. Der Aufwand für Phase 2 ist erheblich — es handelt sich um eine Architektur-Migration, nicht um inkrementelle Erweiterungen. diff --git a/docs/extracted-architecture-details.md b/docs/extracted-architecture-details.md new file mode 100644 index 0000000..621a0e5 --- /dev/null +++ b/docs/extracted-architecture-details.md @@ -0,0 +1,1006 @@ +# LeoCRM — Extracted Architecture Details + +**Zweck:** Implementierungs-Details, die aus der bereinigten `requirements.md` entfernt wurden. Für den Solution Architect als Referenz. +**Quelle:** Original `requirements.md` (git HEAD, 1956 Zeilen) vs. bereinigte Version (2105 Zeilen) +**Datum:** 2026-06-28 + +--- + +## 1. Tech-Stack (vollständige Spezifikation) + +### Original Tech-Stack-Tabelle (entfernt) + +| Komponente | Entscheidung | Bemerkung | +|------------|-------------|----------| +| Backend | FastAPI + SQLAlchemy 2.0 | Python 3.12 | +| Datenbank | PostgreSQL (empfohlen) | Siehe DB-Empfehlung unten | +| Frontend | React SPA | Client-side rendering, i18n (bestätigt durch Prototyp) | +| Deployment | Coolify (Docker) | Bestätigt | +| Linting | ruff + black | Python-Standard | +| Testing | pytest + Vitest/Jest | Backend + Frontend | + +### DB-Empfehlung: PostgreSQL statt SQLite + +Ursprünglich war SQLite spezifiziert. Bei 200.000 Kontakten + mehreren Usern + Concurrent Writes ist SQLite limitiert: + +- **SQLite:** File-Level Locking, nur 1 Writer gleichzeitig, ~15 Updates/Sekunde bei Concurrent Writes (StackOverflow Benchmark) +- **PostgreSQL:** True Concurrent Writers, ~1.500 Updates/Sekunde, MVCC-Architektur (tableone.dev Benchmark) +- **Fazit:** Bei 200k Datensätzen + Multi-User + gleichzeitige Schreibzugriffe → PostgreSQL + +### v0.1 Historischer Tech-Stack (archiviert) +- ~~Backend: FastAPI (Python 3.11+)~~ → Python 3.12 +- ~~DB: SQLite (Datei `leocrm.db`)~~ → PostgreSQL 16 +- ~~Templates: Jinja2 (HTML)~~ → React 18 SPA +- ~~Server: Uvicorn / Gunicorn~~ → Uvicorn (async) +- ~~Deployment: 1 Container Docker Compose~~ → Multi-Container (Backend + Frontend + PostgreSQL + OnlyOffice + Worker) + +--- + +## 2. Domain Knowledge (entfernte Referenzen) + +**Domain:** Customer Relationship Management (CRM) + +Standard-Entitäten in CRMs (Referenz: Zoho CRM, HubSpot, Salesforce): +- **Accounts (Firmen):** Name, Adresse, Industrie, Employees, Revenue, Owner, Phone, Website, Billing/Shipping Address +- **Contacts (Kontaktpersonen):** Name, Email, Phone, Mobile, Title, Department, Mailing Address, Reports To, Description +- **Beziehungen:** N:M (ein Kontakt kann mehreren Firmen zugeordnet sein) + +Quellen: +- Zoho CRM Standard Fields Accounts: https://help.zoho.com/portal/en/kb/crm/sales-force-automation/accounts/articles/standard-fields-accounts +- Zoho CRM Standard Fields Contacts: https://help.zoho.com/portal/en/kb/crm/sales-force-automation/contacts/articles/standard-fields-contacts +- Encore Business Solutions — 30 CRM Custom Account Fields +- Bitrix24 Standard fields in CRM + +--- + +## 3. HTTP-Endpunkt-Spezifikationen + +### F-AUTH-01: Login +- **Endpoint:** POST `/api/auth/login` +- **Akzeptiert:** E-Mail + Passwort +- **Response:** Session-Cookie (HttpOnly+Secure+SameSite=Strict) oder 401 + +### F-AUTH-02: Logout +- **Clientseitig:** Token wird entfernt +- **Server:** Token-Blacklist optional für v1 + +### F-AUTH-03: User-Verwaltung +- **Endpoint:** POST `/api/users` +- **Auth:** Admin-Token erforderlich +- **Response:** 201 (erstellt) oder 403 (ohne Admin-Token) + +### F-AUTH-04: RBAC +- **Jeder API-Endpoint prüft Rolle:** falsche Rolle → 403; korrekte Rolle → 200/201 +- **Beispiele:** POST `/api/users` → 403 (editor); POST `/api/companies` → 403 (viewer), GET `/api/companies` → 200 (viewer) + +### F-AUTH-05: Passwort-Reset +- **Request:** POST `/api/auth/password-reset/request` → sendet E-Mail +- **Confirm:** POST `/api/auth/password-reset/confirm` mit Token + neuem Passwort → aktualisiert Passwort + +### F-COMP-01: Firma anlegen +- **Endpoint:** POST `/api/companies` +- **Response:** 201 Created (valide Daten); 422 Validation Error (ohne Name) + +### F-COMP-02: Firma anzeigen +- **Endpoint:** GET `/api/companies/{id}` +- **Response:** 200 mit Firmendaten + Kontakte-Array; 404 (nicht-existent) + +### F-COMP-03: Firma bearbeiten +- **Endpoint:** PUT/PATCH `/api/companies/{id}` +- **Response:** 200 mit aktualisierten Daten; 422 (Validierungsfehler) + +### F-COMP-04: Firma löschen (Soft-Delete) +- **Endpoint:** DELETE `/api/companies/{id}?cascade=true|false` +- **Response:** 200; Firma wird als `deleted_at = NOW()` markiert +- **Cascade:** bei cascade=true auch Kontakte soft-deleted + +### F-COMP-05: Firmen-Liste mit Pagination +- **Endpoint:** GET `/api/companies?page=2&page_size=25&sort_by=name&sort_order=desc` +- **Response:** 200 mit `{items, total, page, page_size}` + +### F-COMP-06: Firmen-Suche & Filter +- **Endpoint:** GET `/api/companies?search=Tech&industry=IT&country=Germany` +- **Response:** 200 mit gefilterten Ergebnissen + +### F-COMP-07: Audit-Log +- **Tabelle:** `audit_log` +- **Mechanismus:** Middleware/Decorator loggt schreibende Aktionen +- **Endpoint:** GET `/api/audit-log` (Admin, paginiert) + +### F-COMP-08: DSGVO / Right to be Forgotten +- **Endpoint:** DELETE `/api/contacts/{id}?gdpr=true` → harte Löschung +- **Tabelle:** separate `deletion_log` Tabelle (unveränderlich) + +### F-CONT-01: Kontaktperson anlegen +- **Endpoint:** POST `/api/contacts` +- **Response:** 201 (valide Daten); 422 (ohne last_name) +- **N:M:** mit company_ids → N:M-Verknüpfungen erstellt + +### F-CONT-02: Kontaktperson anzeigen +- **Endpoint:** GET `/api/contacts/{id}` +- **Response:** 200 mit Kontaktdaten + Firmen-Array; 404 + +### F-CONT-03: Kontaktperson bearbeiten +- **Endpoint:** PUT/PATCH `/api/contacts/{id}` +- **Response:** 200; Änderung an company_ids → N:M-Tabelle aktualisiert + +### F-CONT-04: Kontaktperson löschen (Soft-Delete) +- **Endpoint:** DELETE `/api/contacts/{id}` +- **Response:** 200; `deleted_at` gesetzt; N:M-Einträge gelöscht; Firmen unberührt + +### F-CONT-05: Kontakt-Liste mit Pagination +- **Endpoint:** GET `/api/contacts?page=1&page_size=25&sort_by=last_name&sort_order=asc` +- **Response:** 200 mit paginierten Ergebnissen +- **Performance:** Response-Zeit <500ms bei 200k Datensätzen (mit DB-Index) + +### F-CONT-06: Kontakt-Suche & Filter +- **Endpoint:** GET `/api/contacts?search=Müller&company_id=5` +- **Response:** 200 mit gefilterten Ergebnissen + +### F-CONT-07: N:M Firmen-Kontakt-Zuordnung +- **Tabelle:** `company_contacts` (N:M-Verknüpfungstabelle) +- **Zuordnung:** POST `/api/companies/{id}/contacts/{contact_id}` +- **Entfernung:** DELETE `/api/companies/{id}/contacts/{contact_id}` + +### F-DATA-01: CSV-Export +- **Endpoint:** GET `/api/companies/export?format=csv&filters=...` +- **Response:** 200 mit `Content-Type: text/csv`, Datei-Download + +### F-DATA-02: Excel-Export +- **Endpoint:** GET `/api/companies/export?format=xlsx&filters=...` +- **Response:** 200 mit `Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` + +### F-DATA-03: Daten-Validierung +- **Technologie:** Pydantic-Schemas für alle Entities +- **Response:** ungültige Eingaben → 422 mit detailiertem Fehler-Objekt + +### F-DATA-06: ARIA-Rollen auf DataTable +- **ARIA-Rollen:** `role="table"`, `role="row"`, `role="columnheader"`, `role="cell"` +- **Sortierung:** `aria-sort="ascending|descending|none"` +- **Label:** beschreibendes `aria-label` +- **Testing:** NVDA und axe DevTools + +### F-UI-01: Responsive Design +- **Breakpoints:** Mobile-First; Touch-Targets min 44px +- **Testing:** Chrome DevTools (375px, 768px, 1920px) + +### F-UI-02: Internationalisierung +- **Library:** react-i18next +- **Locale-Files:** JSON-Locale-Files für DE/EN +- **Persistenz:** Sprachwahl persistiert in User-Settings + +### F-UI-03: Error-Handling & Toast-Notifications +- **Komponente:** Toast-Komponente (success/error/warning/info) +- **Routen:** dedizierte 404/500-Routen im Frontend + +### F-UI-04: Loading-States +- **Komponenten:** Loading-State-Komponenten; Skeleton-Loader +- **State:** API-Loading-State im Frontend-Store; Button-Disabled-State während Pending + +### F-UI-05: Empty-States +- **Komponente:** Empty-State-Komponenten mit Icon + Text + CTA + +### F-UI-06: Confirmation-Dialogs +- **Komponente:** Modal-Dialog-Komponente +- **Regel:** alle DELETE-Operationen erfordern Bestätigung + +### F-SEC-01: CSRF-Schutz +- **Implementierung:** SameSite=Strict Cookies + Origin-Header-Validierung +- **Kein Double-Submit-Token** +- **Middleware:** Origin-Header-Validierung-Middleware +- **Exempt:** nur GET/HEAD/OPTIONS + +### F-SEC-02: XSS-Schutz & Input-Sanitization +- **Frontend:** Output-Encoding +- **Backend:** Pydantic-Validierung strippt gefährliche Eingaben +- **Header:** CSP-Header gesetzt + +### F-SEC-03: Session-Timeout +- **Timeout:** 8h +- **Interceptor:** 401-Interceptor im Frontend → Redirect zur Login-Seite +- **Refresh:** Refresh-Token optional für v1 + +### F-INFRA-01: Health-Check Endpoint +- **Endpoint:** GET `/api/health` +- **Response:** 200 `{status: "healthy", db: "connected"}` oder 503 `{status: "unhealthy", db: "disconnected"}` +- **Auth:** kein Auth erforderlich +- **Coolify:** nutzt Endpoint für Health-Check + +### F-INFRA-02: Backup & Restore +- **Backup:** Docker-Volume-Backup konfiguriert +- **Doku:** Restore-Dokumentation in README + +### F-INFRA-03: Logging +- **Format:** Python `logging` mit JSON-Formatter +- **Konfiguration:** Log-Level konfigurierbar via Env-Var +- **Inhalt:** Method, Path, Status, Duration; Error-Logs mit Stacktrace + +### F-INFRA-04: Monitoring & Alerting +- **Monitoring:** Coolify-Built-in-Monitoring nutzt `/api/health` +- **Optional:** externes Monitoring-Tool + +### F-MIG-01: CSV-Import +- **Endpoint:** POST `/api/import` mit CSV-Datei + Entity-Type +- **Response:** Import-Job mit success/skipped/failed counts + +### F-INT-01: E-Mail-Integration (Passwort-Reset) +- **Config:** SMTP via Env-Vars (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS) +- **Templates:** E-Mail-Templates für Reset + +### F-INT-02: API-Keys / Token-Auth +- **Auth:** Session-Auth-Middleware auf allen Endpoints außer `/api/health` und `/api/auth/*` +- **Optional:** API-Key für externe Integrationen (post-MVP) + +### F-TEST-01: Testing-Strategie +- **Backend:** pytest + httpx für API-Tests; Coverage >80% +- **Frontend:** Vitest für Component-Tests +- **E2E:** Playwright für Critical Paths +- **CI:** Coverage-Report in CI; bei Fehlschlag wird Build blockiert + +### F-ENV-01: Environments & Secrets +- **Env-Vars:** `LEOCRM_SECRET_KEY` (Session-Secret, min 32 Zeichen) +- **Datei:** `.env.example` dokumentiert alle Vars +- **Secrets:** keine Secrets im Git-Repo +- **Coolify:** Env-Vars konfiguriert + +### F-DOC-01: Dokumentation +- **README:** `README.md` mit Setup-Anleitung +- **API-Doku:** `/docs` (Swagger/OpenAPI) auto-gen via FastAPI +- **Admin-Doku:** `docs/admin-guide.md` für Deploy/Backup/Restore + +### F-PERF-01: Performance +- **Response-Zeit:** <500ms für List-Endpunkte bei 200k Datensätzen +- **DB-Indizes:** last_name, first_name, email, company.name, company.industry, company.country +- **Pagination:** verhindert Full-Table-Scan +- **Export:** >50k Datensätze als Background-Job oder Streaming-Response + +### F-A11Y-01: Accessibility (WCAG 2.1 AA) +- **CSS-Utility:** `.sr-only` CSS-Klasse (visual-hidden pattern) +- **Kontrast:** >4.5:1 +- **Testing:** axe DevTools + +### F-A11Y-02: prefers-reduced-motion +- **CSS:** `@media (prefers-reduced-motion: reduce)` Block in globalen Styles +- **Properties:** alle `transition` und `animation` auf `none` oder `0.01ms` +- **Testing:** Chrome DevTools (Rendering → Emulate CSS prefers-reduced-motion: reduce) + +### F-A11Y-03: 44px Touch-Targets +- **CSS-Regel:** `min-height: 44px; min-width: 44px;` für alle interaktiven Elemente +- **Alternative:** `::after` Pseudo-Element mit 44px Touch-Bereich +- **Testing:** Chrome DevTools (375px Breite, Touch-Emulation) + +### F-SCHED-01: Background-Jobs +- **Queue:** Background-Task-Queue (Celery, ARQ, oder FastAPI BackgroundTasks für v1) +- **Status:** Job-Status-Endpoint +- **Export-Limit:** >50k Datensätze als Background-Job, <50k als direkter Download + +--- + +## 4. DB-Schema-Definitionen (Feld-Tabellen) + +### F-COMP-01: Company-Felder + +| Feld | Typ | Pflicht | Bemerkung | +|------|-----|---------|-----------| +| name | String(100) | ✅ | Firmenname | +| account_number | String(40) | ❌ | Interne Referenznummer | +| industry | Picklist | ❌ | IT, Finance, Manufacturing, etc. | +| account_type | Picklist | ❌ | Customer, Partner, Prospect, etc. | +| ownership | Picklist | ❌ | Public, Private, Government, etc. | +| employees | Integer | ❌ | Anzahl Mitarbeiter | +| annual_revenue | Decimal | ❌ | Jahresumsatz | +| phone | String(30) | ❌ | Haupttelefon | +| fax | String(30) | ❌ | Fax | +| email | Email | ❌ | Allgemeine E-Mail | +| website | URL | ❌ | Website | +| rating | Picklist | ❌ | Hot, Warm, Cold | +| parent_account_id | FK→Company | ❌ | Muttergesellschaft | +| billing_street | String(250) | ❌ | Rechnungsadresse Strasse | +| billing_city | String(100) | ❌ | Rechnungsadresse Stadt | +| billing_state | String(100) | ❌ | Rechnungsadresse Bundesland | +| billing_postal_code | String(20) | ❌ | Rechnungsadresse PLZ | +| billing_country | String(100) | ❌ | Rechnungsadresse Land | +| shipping_street | String(250) | ❌ | Besuchsadresse Strasse | +| shipping_city | String(100) | ❌ | Besuchsadresse Stadt | +| shipping_state | String(100) | ❌ | Besuchsadresse Bundesland | +| shipping_postal_code | String(20) | ❌ | Besuchsadresse PLZ | +| shipping_country | String(100) | ❌ | Besuchsadresse Land | +| description | Text(32000) | ❌ | Notizfeld | +| sic_code | String(10) | ❌ | Standard Industrial Classification | +| ticker_symbol | String(30) | ❌ | Börsenkürzel | +| account_site | String(80) | ❌ | Standort-Name (z.B. Headquarters) | + +### F-CONT-01: Contact-Felder + +| Feld | Typ | Pflicht | Bemerkung | +|------|-----|---------|-----------| +| first_name | String(50) | ❌ | Vorname | +| last_name | String(50) | ✅ | Nachname | +| salutation | Picklist | ❌ | Herr, Frau, Dr., etc. | +| email | Email | ❌ | Haupt-E-Mail | +| secondary_email | Email | ❌ | Zweit-E-Mail | +| phone | String(30) | ❌ | Bürotelefon | +| mobile | String(30) | ❌ | Mobiltelefon | +| home_phone | String(30) | ❌ | Privattelefon | +| fax | String(30) | ❌ | Fax | +| title | String(100) | ❌ | Jobtitel (CEO, Manager, etc.) | +| department | String(100) | ❌ | Abteilung | +| reports_to | FK→Contact | ❌ | Vorgesetzter | +| date_of_birth | Date | ❌ | Geburtsdatum | +| assistant | String(50) | ❌ | Assistent-Name | +| assistant_phone | String(30) | ❌ | Assistent-Telefon | +| mailing_street | String(250) | ❌ | Postadresse Strasse | +| mailing_city | String(100) | ❌ | Postadresse Stadt | +| mailing_state | String(100) | ❌ | Postadresse Bundesland | +| mailing_postal_code | String(20) | ❌ | Postadresse PLZ | +| mailing_country | String(100) | ❌ | Postadresse Land | +| other_street | String(250) | ❌ | Andere Adresse Strasse | +| other_city | String(100) | ❌ | Andere Adresse Stadt | +| other_state | String(100) | ❌ | Andere Adresse Bundesland | +| other_postal_code | String(20) | ❌ | Andere Adresse PLZ | +| other_country | String(100) | ❌ | Andere Adresse Land | +| skype_id | String(50) | ❌ | Skype | +| linkedin | URL | ❌ | LinkedIn-Profil | +| twitter | String(50) | ❌ | Twitter-Handle | +| description | Text(32000) | ❌ | Notizfeld | +| company_ids | [FK→Company] | ❌ | N:M-Zuordnung (0..n Firmen) | + +### Implizite DB-Tabellen (aus Akzeptanzkriterien extrahiert) + +- `company_contacts` — N:M-Verknüpfungstabelle (company_id ↔ contact_id) +- `audit_log` — Tabelle für Audit-Log-Einträge (user, action, entity, entity_id, timestamp) +- `deletion_log` — Unveränderliche Tabelle für DSGVO-Löschungen +- `users` — User-Tabelle (email, name, role, password_hash) +- `companies` — Firmen-Tabelle (siehe Feld-Tabelle oben, + `deleted_at` TIMESTAMP für Soft-Delete) +- `contacts` — Kontakt-Tabelle (siehe Feld-Tabelle oben, + `deleted_at` TIMESTAMP für Soft-Delete) + +--- + +## 5. Nicht-funktionale Anforderungen (vollständige Tabelle) + +| ID | Kategorie | Anforderung | Metrik | +|----|-----------|-------------|--------| +| NF-01 | Performance | API-Response <500ms bei 200k Datensätzen | 95th Percentile <500ms | +| NF-02 | Skalierung | DB-Connection-Pooling (SQLAlchemy Pool size=10) | Keine Connection-Erschöpfung bei 10 concurrent Users | +| NF-03 | Sicherheit | Alle Passwörter bcrypt-gehashed (cost=12) | Keine Plain-Text-Passwörter in DB | +| NF-04 | Sicherheit | Session-Secret via Env-Var, min 32 Zeichen | App startet nicht ohne Secret | +| NF-05 | Verfügbarkeit | Health-Check für Coolify Auto-Restart | Auto-Restart bei 503 | +| NF-06 | Wartbarkeit | Code-Struktur: `api/`, `models/`, `schemas/`, `services/`, `tests/` | Klare Trennung | +| NF-07 | Wartbarkeit | Linting: ruff + black, pre-commit-hook | CI blockt bei Lint-Fehlern | +| NF-08 | Testbarkeit | pytest Coverage >80% Backend | Coverage-Report in CI | +| NF-09 | i18n | DE + EN, Sprachwahl persistiert | Alle UI-Texte übersetzt | +| NF-10 | Accessibility | WCAG 2.1 AA | axe-Check: 0 Violations | +| NF-11 | Logging | Strukturierte JSON-Logs | `docker logs` zeigt JSON-Einträge | +| NF-12 | Deployment | Docker-Container auf Coolify | `docker-compose.yml` + `Dockerfile` | + +--- + +## 6. Annahmen (entfernte Details) + +1. **Multi-Tenant (Multi-Company):** v1 ist Multi-Tenant (Multi-Company) — mehrere Organisationen, mehrere User. +2. **PostgreSQL statt SQLite:** Bei 200k Kontakten + Multi-User + Concurrent Writes wird PostgreSQL empfohlen. SQLite limitiert auf ~15 concurrent updates/sec vs PostgreSQL ~1500/sec. +3. **SPA-Frontend:** Client-side rendering mit React SPA (bestätigt durch genehmigten Prototyp leocrm-prototype-x7k2p9, v7). +4. **Max 10 concurrent Users:** v1 ist für kleine Teams, nicht für Enterprise. +5. **E-Mail-Versand:** SMTP via externem Provider (Mailtrap für Dev, Production-SMTP für Prod). +6. **Soft-Delete als Default:** Firmen und Kontakte werden soft-deleted (recoverable), außer DSGVO-Löschung (hard delete). +7. **Rollensystem v1:** 3 Rollen: admin, editor, viewer. +8. **Export-Limit:** Export von >50k Datensätzen als Background-Job, <50k als direkter Download. +9. **i18n-Default:** Deutsch ist Default-Sprache, Englisch ist zweites Locale. +10. **Python 3.12:** Aktuelle stabile Python-Version. + +--- + +## 7. Technologie-Wahlen (Alle Module) + +### Core-Stack +| Technologie | Verwendung | +|-------------|-----------| +| FastAPI | Backend-Framework | +| SQLAlchemy 2.0 | ORM | +| PostgreSQL 16 | Datenbank | +| React 18 SPA | Frontend | +| Uvicorn (async) | ASGI-Server | +| ruff + black | Linting | +| pytest + httpx | Backend-Tests | +| Vitest | Frontend-Tests | +| Playwright | E2E-Tests | +| Pydantic | Input-Validierung | +| bcrypt (cost=12) | Passwort-Hashing | +| Celery / ARQ / FastAPI BackgroundTasks | Background-Jobs | +| react-i18next | i18n-Frontend | +| Docker Compose | Multi-Container-Deployment | +| Coolify | Deployment-Plattform | + +### DMS-Modul +| Technologie | Verwendung | +|-------------|-----------| +| PDF.js | PDF-Preview im Browser | +| OnlyOffice Document Server | Online-Bearbeitung (separater Container) | + +### Mail-Modul +| Technologie | Verwendung | +|-------------|-----------| +| IMAP4rev1 (RFC 3501) | Mail-Empfang | +| IMAP IDLE (RFC 2177) | Real-time Push für neue Mails | +| SMTP Submission (RFC 6409) | Mail-Versand | +| DOMPurify | HTML-Sanitization (XSS-Schutz) | +| python-gnupg | PGP-Verschlüsselung (OpenPGP, RFC 4880) | +| PostgreSQL Full-Text Search (tsvector) | Volltext-Suche über Mails | +| AES-256 | Passwort-Verschlüsselung für IMAP/SMTP-Credentials | + +--- + +## 8. Farbcodes (Hex-Werte) + +### Kalender-Modul (F-CAL-06) + +| Eintrags-Typ | Subtyp | Farbe (Hex) | +|-------------|-------|-------------| +| appointment | normal | `#3B82F6` (blau) | +| task | normal | `#F59E0B` (gelb) | +| * | follow_up | `#F97316` (orange) | +| * | private | `#9CA3AF` (grau) | + +### Tag-System (F-TAG-02) +- Tags können mit Farbe versehen werden, z.B. `#FF0000` für „VIP" + +--- + +## 9. Protokoll-Details (Mail-Modul) + +### IMAP +- **Protokoll:** IMAP4rev1 (RFC 3501) +- **IDLE:** RFC 2177 für Push, Fallback: Polling alle 5 Min +- **Port:** 993 (SSL/TLS) +- **Auth:** PLAIN/LOGIN +- **MOVE:** RFC 6851 (Fallback: COPY + STORE \\Deleted + EXPUNGE) +- **Flags:** `\\Seen`, `\\Flagged`, `\\Answered`, `\\Draft`, `$Spam` +- **Kompatibilität:** Getestet mit Dovecot, Courier IMAP + +### SMTP +- **Protokoll:** SMTP Submission (RFC 6409) +- **Port 587:** mit STARTTLS +- **Port 465:** mit SSL +- **Auth:** PLAIN/LOGIN mit Benutzername+Passwort + +### PGP +- **Standard:** OpenPGP (RFC 4880) +- **Library:** python-gnupg +- **Private Key:** verschlüsselt gespeichert (AES-256 + Passphrase) +- **Passphrase:** nicht gespeichert, nur im Session-Cache +- **Public Key:** pro Kontakt speicherbar +- **S/MIME:** post-MVP + +### Attachment-Storage +- Anhänge werden lokal gespeichert (Dateisystem oder S3) +- Nicht im IMAP-Server belassen (Caching) +- Mail-Body wird in PostgreSQL gespeichert +- Anhänge bei Bedarf vom IMAP-Server nachgeladen +- Max: 25 MB pro Anhang, 50 MB pro Mail + +### Passwort-Speicherung +- IMAP/SMTP-Passwörter AES-256 verschlüsselt +- Key via Env-Var `MAIL_ENCRYPTION_KEY` + +### Ordner-Mapping +- INBOX → Posteingang +- Sent → Postausgang +- Drafts → Entwürfe +- Spam/Junk → Spam + +### Threading +- Basierend auf `References`- und `In-Reply-To`-Headern (RFC 5322) + +### HTML-Rendering +- Sanitization mit DOMPurify (XSS-Schutz) +- Inline-Styles erlaubt, Scripts/IFrames entfernt +- Plain-Text-Fallback + +--- + +## 10. RFC-Referenzen (alle) + +| RFC | Titel | Verwendung | +|-----|-------|-----------| +| RFC 3501 | IMAP4rev1 | Mail-Empfang | +| RFC 2177 | IMAP IDLE | Real-time Push | +| RFC 6851 | IMAP MOVE | Mail verschieben | +| RFC 6409 | SMTP Submission | Mail-Versand | +| RFC 4880 | OpenPGP | PGP-Verschlüsselung | +| RFC 5322 | Internet Message Format | Threading (References/In-Reply-To) | +| RFC 5545 | iCalendar | Recurrence Rules (RRULE) für wiederkehrende Termine | +| RFC 6047 | iMIP | (Non-Goal: nicht in v2-mail) | +| RFC 8620 | JMAP | (Non-Goal: nicht unterstützt) | + +--- + +## 11. DMS-Modul: HTTP-Endpunkte & Architektur-Details + +### F-DMS-01: Ordner-Struktur +- **Create:** POST `/api/dms/folders` → 201 +- **Rename/Move:** PATCH `/api/dms/folders/{id}` → 200 +- **Delete:** DELETE `/api/dms/folders/{id}` → 200 (Soft-Delete) +- **Zirkuläre Verschiebung:** serverseitig verhindert → 422 + +### F-DMS-02: Datei-Upload +- **Endpoint:** POST `/api/dms/files/upload` (multipart) → 201 mit Datei-Metadaten +- **Max-Size:** 413 bei Überschreitung (Default 100 MB) +- **Status:** WebSocket oder Polling + +### F-DMS-03: Datei-Operationen +- **Rename:** PATCH `/api/dms/files/{id}` → 200 +- **Move:** PATCH `/api/dms/files/{id}` mit `folder_id` → 200 +- **Delete:** DELETE `/api/dms/files/{id}` → 200 (Soft-Delete) +- **Restore:** POST `/api/dms/files/{id}/restore` → 200 + +### F-DMS-04: PDF-Preview +- **Endpoint:** GET `/api/dms/files/{id}/preview` → 200 mit `Content-Type: application/pdf` +- **Frontend:** PDF.js +- **Non-PDF:** 415 Unsupported Media Type + +### F-DMS-05: OnlyOffice Integration +- **Server:** OnlyOffice Document Server als separater Container (Coolify) +- **Endpoint:** POST `/api/dms/files/{id}/edit-session` → 200 mit OnlyOffice-URL +- **Formate:** DOCX, XLSX, PPTX + +### F-DMS-06: Datei-Metadaten +- **Endpoint:** GET `/api/dms/files/{id}` → 200 mit `{name, size, mime_type, uploaded_at, modified_at, uploaded_by}` +- **Größe:** serverseitig in Bytes, clientseitig formatiert + +### F-DMS-07: Datei-Suche +- **Endpoint:** GET `/api/dms/search?q=vertrag&folder_id={id}` → 200 mit `{items: [{type, name, path, ...}]}` +- **Global:** ohne `folder_id` → gesamte DMS + +### F-LINK-01: Dateien mit Firmen verknüpfen +- **Endpoint:** POST `/api/dms/files/{id}/link` mit `{entity_type: "company", entity_id: N}` → 201 +- **Delete:** DELETE `/api/dms/files/{id}/link?entity_type=company&entity_id=N` → 204 +- **Unique Constraint:** Duplikate serverseitig verhindert + +### F-LINK-02: Dateien mit Kontakten verknüpfen +- **Endpoint:** POST `/api/dms/files/{id}/link` mit `{entity_type: "contact", entity_id: N}` → 201 + +### F-LINK-03: Verknüpfte Dateien in Firmen-Detail +- **Response:** GET `/api/companies/{id}` enthält `linked_files: [{id, name, size, mime_type, modified_at}]` + +### F-LINK-04: Verknüpfte Dateien in Kontakt-Detail +- **Response:** GET `/api/contacts/{id}` enthält `linked_files: [{id, name, size, mime_type, modified_at}]` + +### F-LINK-05: Reverse-Verknüpfung +- **Response:** GET `/api/dms/files/{id}` enthält `linked_entities: [{entity_type, entity_id, entity_name}]` +- **Delete:** DELETE `/api/dms/files/{id}/link?entity_type=company&entity_id=N` → 204 + +### F-LINK-06: Mehrfach-Verknüpfung +- **Bulk:** POST `/api/dms/files/bulk-link` mit `{file_ids: [...], entity_type: "company", entity_id: N}` → 201 +- **Ordner:** POST `/api/dms/folders/{id}/link` + +### F-TAG-01: Tags anwenden +- **Assign:** POST `/api/tags/assign` mit `{entity_type, entity_id, tag_id}` → 201 +- **Remove:** DELETE `/api/tags/assign?entity_type=...&entity_id=...&tag_id=...` → 204 +- **Unique Constraint:** (entity_type, entity_id, tag_id) + +### F-TAG-02: Tag-Verwaltung +- **Create:** POST `/api/tags` (Admin) → 201 +- **Update:** PATCH `/api/tags/{id}` → 200 +- **Delete:** DELETE `/api/tags/{id}` → 204 mit Cascade Delete auf `tag_assignments` +- **Non-Admin:** → 403 + +### F-TAG-03: Tag-Filterung +- **Endpoint:** GET `/api/companies?tag_ids=1,2&tag_mode=and` → 200 +- **Modi:** `tag_mode=or` → OR-Verknüpfung +- **Gleiche Parameter** für `/api/contacts`, `/api/dms/files` + +### F-TAG-04: Tag-Cloud +- **Endpoint:** GET `/api/tags?with_counts=true` → 200 mit `[{id, name, color, count}]` + +### F-PERM-01: Persönlicher Root-Ordner +- **User-Creation:** automatisch `personal_folder_id` gesetzt +- **Ownership-Check:** GET `/api/dms/folders/{id}` prüft Ownership → 403 bei fremdem Ordner (außer Admin) + +### F-PERM-02: Gemeinsame Root-Ordner +- **Create:** POST `/api/dms/folders` mit `shared_with_group_id` → 201 +- **Permissions:** `folder_permissions` Tabelle (folder_id, group_id, permission: read|write) + +### F-PERM-03: Datei/Ordner mit Usern teilen +- **Share:** POST `/api/dms/files/{id}/share` mit `{user_id, permission: "read|write"}` → 201 +- **Remove:** DELETE `/api/dms/files/{id}/share?user_id=N` → 204 +- **Shared-with-me:** GET `/api/dms/shared-with-me` → 200 + +### F-PERM-04: Datei/Ordner mit Gruppen teilen +- **Share:** POST `/api/dms/files/{id}/share` mit `{group_id, permission: "read|write"}` → 201 +- **Resolution:** Individual > Group > Default (Deny vor Allow) +- **Remove:** DELETE `/api/dms/files/{id}/share?group_id=N` → 204 + +### F-PERM-05: Share-Links +- **Create:** POST `/api/dms/files/{id}/share-link` mit `{password?, expires_at?, download_only?}` → 201 mit `{url}` +- **Public:** GET `/api/public/share/{token}` → 200 (oder 401 bei Passwort, 410 bei abgelaufen) +- **Public-Endpoint:** braucht keine Auth + +### F-PERM-06: Berechtigungs-Anzeige +- **Endpoint:** GET `/api/dms/files/{id}/permissions` → 200 mit `{owner: {id, name}, shares: [{user_id?, group_id?, name, permission}], share_links: [{id, url, has_password, expires_at, is_active}]}` + +### F-FILEUI-01: Datei-Browser +- **Komponenten:** `FileBrowser` mit `SidebarTree` + `MainView` (Grid/List Toggle) +- **State-Management:** für aktiven Ordner + +### F-FILEUI-02: Breadcrumb-Navigation +- **Komponente:** `Breadcrumb` mit klickbaren Segmenten +- **Pfad:** aus `folder.path` (Materialized Path oder rekursive Abfrage) generiert + +### F-FILEUI-03: Kontext-Menü +- **Komponente:** `ContextMenu` mit dynamischen Items basierend auf `entity_type` (file/folder) und `permission` (read/write) +- **Mobile:** Long-Press statt Rechtsklick + +### F-FILEUI-04: Bulk-Aktionen +- **Move:** POST `/api/dms/files/bulk-move` mit `{file_ids: [...], target_folder_id}` → 200 +- **Delete:** POST `/api/dms/files/bulk-delete` → 200 +- **Tag:** POST `/api/tags/bulk-assign` → 201 + +### F-FILEUI-05: Upload-Progress +- **Komponente:** `UploadProgress` mit pro-Datei Progress +- **Upload:** via `XMLHttpRequest` (für Progress-Events) oder WebSocket + +### F-FILEUI-06: Drag & Drop +- **Frontend:** HTML5 Drag & Drop API +- **Drop-Target:** validiert Permission clientseitig (grün = erlaubt, rot = verboten) +- **Server:** PATCH `/api/dms/files/{id}` mit `folder_id` → Permission-Check → 200 oder 403 + +--- + +## 12. Kalender-Modul: HTTP-Endpunkte & Architektur-Details + +### Architektur-Hinweis + +Ein Kalender-Eintrag (`CalendarEntry`) hat einen `entry_type`: +- **`appointment`** — Termin mit Start/Ende (Datum+Zeit), optional Ganztägig, Ort, Teilnehmer +- **`task`** — Aufgabe mit Fälligkeitsdatum (due_date), Priorität, Status, Sub-Tasks, Zuständiger — keine feste Uhrzeit + +Beide Typen teilen sich die gleichen Verknüpfungs-, Erinnerungs-, Wiederholungs- und Anzeige-Mechanismen. + +### F-CAL-01: Kalender-Ansichten +- **Frontend:** `CalendarView` Komponente mit `mode: month|week|day` +- **Endpoint:** GET `/api/calendar/entries?from=2026-06-01&to=2026-06-30` → 200 mit Entry-Array + +### F-CAL-02: Kanban-Zeitraum-Ansicht +- **Frontend:** `KanbanCalendar` Komponente mit `period: this_week|next_2_weeks|this_month|custom` +- **Endpoint:** GET `/api/calendar/kanban?period=this_week` → 200 mit `{columns: [{date, appointments: [...], tasks: [...]}]}` + +### F-CAL-03: Kalender-Eintrag erstellen +- **Endpoint:** POST `/api/calendar/entries` → 201 +- **Fields appointment:** `entry_type`, `title`, `description?`, `start_at`, `end_at`, `all_day?`, `location?`, `attendees?` +- **Fields task:** `entry_type`, `title`, `description?`, `due_date?`, `priority: "high|medium|low"`, `status?: "open"` +- **Validierung:** end_at > start_at (außer all_day=true); title nicht leer + +### F-CAL-04: Einträge mit Entitäten verknüpfen +- **Link:** POST `/api/calendar/entries/{id}/link` mit `{entity_type: "company|contact|deal", entity_id: N}` → 201 +- **Response companies:** GET `/api/companies/{id}` enthält `upcoming_events: [...]` und `open_tasks: [...]` + +### F-CAL-05: Drag & Drop im Kalender +- **Frontend:** HTML5 Drag & Drop auf Kalender-Zellen (appointments) und Kanban-Spalten (tasks) +- **Update:** PATCH `/api/calendar/entries/{id}` mit neuem `start_at`/`end_at` (appointment) oder `status` (task) → 200 +- **Optimistic Update** mit Rollback bei Fehler + +### F-CAL-06: Farbcodierung +- **Fields:** `entry_type: appointment|task` und `subtype: normal|follow_up|private` +- **Color Map:** `{appointment+normal: "#3B82F6", task+normal: "#F59E0B", *+follow_up: "#F97316", *+private: "#9CA3AF"}` +- **Update:** PATCH `/api/calendar/entries/{id}` mit `subtype` → 200 + +### F-CAL-07: Erinnerungen/Alerts +- **Field:** `reminder: {value: N, unit: "minutes|hours|days", channel: "in_app|email"}` +- **Background-Job:** prüft fällige Erinnerungen (Cron, minütlich) +- **Notification:** POST `/api/notifications` bei Fälligkeit +- **Überfällig-Check:** täglicher Cron-Job + +### F-CAL-08: Wiederkehrende Einträge +- **Field:** `recurrence: {pattern: "daily|weekly|monthly|yearly|custom", custom_rule?, end_date?, exceptions?: [dates]}` +- **Appointments:** Server generiert Instanzen via RRULE (RFC 5545) +- **Tasks:** bei Status-Wechsel auf `done` → POST `/api/calendar/entries` mit neuem `due_date` +- **Status `cancelled`:** keine Generierung + +### F-CAL-09: Kalender-Feeds (ICS) +- **Export:** GET `/api/calendar/{calendar_id}/ics-feed?token={user_token}` → 200 mit `Content-Type: text/calendar` (RFC 5545 konform) +- **Import:** POST `/api/calendar/import` (multipart ICS, optional calendar_id) → 201 mit `{imported: N, skipped: M}` +- **Auth:** Token-basierte Auth für Feed-URL + +### F-CAL-10: Ressourcen-Booking +- **Create Resource:** POST `/api/resources` (Admin) → 201 +- **Book:** POST `/api/calendar/entries/{id}/book-resource` mit `{resource_id}` → 201 oder 409 bei Konflikt +- **Bookings:** GET `/api/resources/{id}/bookings?from=...&to=...` → 200 + +### F-CAL-11: Mehrere Kalender +- **Create:** POST `/api/calendars` mit `{name, color, type: "personal|team|project|company"}` → 201 +- **Auto-Create:** bei User-Anlage automatisch POST `/api/calendars` mit `{name: "Mein Kalender", type: "personal"}` +- **List:** GET `/api/calendars` → 200 mit sichtbaren Kalendern +- **Delete:** DELETE `/api/calendars/{id}` → 204 (Cascade auf Einträge) + +### F-CAL-12: Kalender abonnieren +- **Frontend:** `CalendarSidebar` mit Toggle-Switches pro Kalender +- **Endpoint:** GET `/api/calendar/entries?calendar_ids=1,3,5` → 200 +- **State:** persistiert pro User (`user_calendar_visibility` Tabelle) + +### F-CAL-13: Kalender teilen +- **Share:** POST `/api/calendars/{id}/share` mit `{user_id?, group_id?, permission: "read|write"}` → 201 +- **Remove:** DELETE `/api/calendars/{id}/share?user_id=N` → 204 +- **Permissions:** GET `/api/calendars/{id}/permissions` → 200 mit `{owner, shares}` + +### F-CAL-14: Default-Kalender +- **Setting:** `default_calendar_id` (User-Setting) +- **Update:** PATCH `/api/users/me/settings` mit `{default_calendar_id: N}` → 200 +- **Fallback:** persönlicher Kalender bei keinem Setting + +### F-CAL-15: Aufgaben-Zuweisung +- **Field:** `assigned_to: user_id` (nur für entry_type=task) +- **Update:** PATCH `/api/calendar/entries/{id}` mit `assigned_to` → 200, sendet Notification +- **Query:** GET `/api/calendar/entries?entry_type=task&assigned_to={user_id}` → 200 + +### F-CAL-16: Sub-Tasks +- **Create:** POST `/api/calendar/entries/{id}/subtasks` mit `{title}` → 201 (nur für entry_type=task) +- **Update:** PATCH `/api/calendar/entries/{id}/subtasks/{sub_id}` mit `{completed: true}` → 200 +- **Response:** GET `/api/calendar/entries/{id}` enthält `subtasks: [{id, title, completed}]` und `progress: {completed, total}` + +### F-CAL-17: Aufgaben-Filter & Liste +- **Query:** GET `/api/calendar/entries?entry_type=task&assigned_to={id}&priority=high&status=open&due_filter=overdue&page=1&page_size=25&sort_by=due_date&sort_order=asc` → 200 mit `{items, total, page, page_size}` +- **Export:** GET `/api/calendar/entries/export?format=csv&entry_type=task&status=open` → 200 mit CSV-Download +- **Frontend:** `TaskKanban` (4 Spalten) und `TaskList` (DataTable) Komponenten + +### F-CAL-18: Bulk-Aktionen +- **Endpoint:** POST `/api/calendar/entries/bulk` mit `{entry_ids: [...], action: "status|assign|due_date|delete", value: ...}` → 200 mit `{updated: N}` + +--- + +## 13. Mail-Modul: HTTP-Endpunkte & Architektur-Details + +### F-MAIL-01: Standard-Ordner +- **Endpoint:** GET `/api/mail/folders` → 200 mit `{folders: [{id, name, type: "inbox|sent|drafts|spam|custom", unread_count, total_count}]}` +- **IMAP IDLE-Listener:** läuft als Background-Task, neue Mails innerhalb von 5 Sekunden +- **Bidirektionale Flag-Sync:** \\Seen, \\Flagged, \\Answered, \\Draft, $Spam + +### F-MAIL-02: E-Mail schreiben +- **Send:** POST `/api/mail/send` mit `{to: [...], cc: [...], bcc: [...], subject, body_html, in_reply_to?, attachments: [...]}` → 201 +- **Draft:** POST `/api/mail/drafts` → speichert Entwurf +- **Reply:** POST `/api/mail/{id}/reply` → erstellt Reply mit vorausgefüllten Feldern +- **Forward:** POST `/api/mail/{id}/forward` → erstellt Forward +- **HTML-Sanitization:** DOMPurify + +### F-MAIL-03: Volltext-Suche +- **Endpoint:** GET `/api/mail/search?q=angebot&folder=inbox&date_from=...&date_to=...` → 200 mit `{items: [{id, subject, from, date, snippet, folder}], total}` +- **Index:** PostgreSQL tsvector-Index über `mail_body_tsv` und `mail_subject_tsv` +- **Performance:** <500ms bei 10.000 Mails + +### F-MAIL-04: Anhänge +- **Send:** POST `/api/mail/send` mit multipart-Form-Data für Anhänge +- **Download:** GET `/api/mail/{id}/attachments/{att_id}` → 200 +- **Content-Disposition:** `attachment` für Downloads, `inline` für inline-Bilder +- **Max:** 25 MB pro Anhang, 50 MB pro Mail + +### F-MAIL-05: Threading +- **Endpoint:** GET `/api/mail/threads?folder=inbox` → 200 mit `{items: [{thread_id, subject, participants, message_count, last_date, messages: [{id, from, date, snippet}]}]}` +- **Basis:** `In-Reply-To`/`References`-Header (RFC 5322) +- **Frontend:** `ThreadView` Komponente + +### F-MAIL-06: Vorlagen/Templates +- **Create:** POST `/api/mail/templates` mit `{name, subject, body_html, shared: false}` → 201 +- **List:** GET `/api/mail/templates?scope=me|shared` → 200 +- **Compose:** POST `/api/mail/compose?template_id=N&contact_id=M&company_id=K` → 200 mit aufgelösten Platzhaltern +- **Syntax:** `{{entity.field}}` + +### F-MAIL-07: Filter/Regeln +- **Create:** POST `/api/mail/rules` mit `{name, conditions: [{field, operator, value}], actions: [{type, value}]}` → 201 +- **Evaluation:** Background-Worker nach IMAP-IDLE-Trigger +- **List:** GET `/api/mail/rules` → 200 +- **Delete:** DELETE `/api/mail/rules/{id}` → 204 + +### F-MAIL-08: Abwesenheitsnotiz +- **Endpoint:** POST `/api/mail/vacation` mit `{active: true, subject, body, start_date, end_date}` → 201 +- **Background-Worker:** prüft eingehende Mails, `vacation_sent_log` Tabelle (einmal pro Absender) +- **No-Reply-Erkennung:** Absender enthält „noreply", „no-reply", „donotreply" + +### F-MAIL-09: Labels/Flags +- **Flag:** PATCH `/api/mail/{id}/flags` mit `{starred: true}` → 200, setzt IMAP \\Flagged +- **Label:** POST `/api/mail/{id}/labels` mit `{label_id: N}` → 201 +- **Filter:** GET `/api/mail?label=vertrieb` → 200 +- **Label-Verwaltung:** POST `/api/mail/labels` mit `{name, color}` + +### F-MAIL-10: Kontakt-Verknüpfung +- **Contact-Mails:** GET `/api/contacts/{id}/emails` → 200 mit `{items: [{id, subject, from, date, direction: "in|out", thread_id}]}` +- **Company-Mails:** GET `/api/companies/{id}/emails` → 200 +- **Auto-Verknüpfung:** Background-Job matcht Absender/Empfänger gegen `contacts.email`-Feld +- **Manual:** POST `/api/mail/{id}/link` mit `{contact_id, company_id}` + +### F-MAIL-11: Kalender-Integration +- **Endpoint:** POST `/api/mail/{id}/create-event` mit `{date, time, duration, calendar_id?, title?}` → 201 +- **Defaults:** Titel = Mail-Betreff, Beschreibung = Mail-Body (plain text), Teilnehmer = Mail-Absender +- **Link:** `calendar_entry.source_mail_id = mail.id` + +### F-MAIL-12: PGP-Verschlüsselung +- **Import Private Key:** POST `/api/mail/pgp/keys` mit `{private_key, passphrase}` → 201 (verschlüsselt gespeichert) +- **Import Public Key:** POST `/api/contacts/{id}/pgp-key` mit `{public_key}` → 201 +- **Encrypt Send:** POST `/api/mail/send` mit `{encrypt: true}` → python-gnupg Verschlüsselung +- **Decrypt:** GET `/api/mail/{id}` → erkennt PGP-Block, entschlüsselt bei vorhandener Passphrase (Session-Cache) + +### F-MAIL-13: Signaturen +- **Create:** POST `/api/mail/signatures` mit `{name, body_html}` → 201 +- **Assign Default:** PATCH `/api/mail/accounts/{id}` mit `{default_signature_id: N}` → 200 +- **Compose:** POST `/api/mail/compose?account_id=N` → Response enthält `signature`-Feld + +### F-MAIL-14: Mehrere Postfächer +- **Create:** POST `/api/mail/accounts` mit `{name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl, username, password}` → 201 (Passwort verschlüsselt) +- **List:** GET `/api/mail/accounts` → 200 +- **Default:** PATCH `/api/users/me/settings` mit `{default_mail_account_id: N}` + +### F-MAIL-15: Geteilte Postfächer +- **Create:** POST `/api/mail/accounts` mit `{type: "shared", name: "info@firma.de", ...}` → 201 +- **Assign Users:** POST `/api/mail/accounts/{id}/users` mit `{user_ids: [1, 2, 3]}` → 200 +- **List:** GET `/api/mail/accounts/shared` → 200 +- **Seen-By:** `mail_seen_by` Tabelle (mail_id, user_id, seen_at) + +### F-MAIL-16: Stellvertretung +- **Delegate:** POST `/api/mail/accounts/{id}/delegates` mit `{delegate_user_id, permission: "read|full"}` → 201 +- **Remove:** DELETE `/api/mail/accounts/{id}/delegates?user_id=N` → 204 +- **List:** GET `/api/mail/accounts?include_delegated=true` → 200 mit `delegated: true` Markierung + +### F-MAIL-17: Sende-Berechtigungen +- **Grant:** POST `/api/mail/accounts/{id}/send-permissions` mit `{user_ids: [...]}` → 200 +- **List:** GET `/api/mail/accounts/{id}/send-permissions` → 200 +- **Send-Check:** POST `/api/mail/send` mit `{from_account_id: N}` → prüft, 403 wenn nicht + +### F-MAIL-18: Postfach-Konfiguration +- **Create:** POST `/api/mail/accounts` mit `{imap_host, imap_port, imap_ssl: true, smtp_host, smtp_port, smtp_starttls: true, username, password}` +- **Validierung:** IMAP-Login + SMTP-Login, 422 bei Fehler +- **Verschlüsselung:** AES-256, Key via Env-Var `MAIL_ENCRYPTION_KEY` +- **Maskierung:** GET `/api/mail/accounts/{id}` → Passwort-Feld ist `***` + +### F-MAIL-19: Mail-Ordner verwalten +- **Create:** POST `/api/mail/folders` mit `{name, parent_id?}` → 201, führt IMAP CREATE aus +- **Rename:** PATCH `/api/mail/folders/{id}` mit `{name}` → 200, führt IMAP RENAME aus +- **Delete:** DELETE `/api/mail/folders/{id}` → 204, führt IMAP DELETE aus + +--- + +## 14. Mail-Modul: Technische Constraints (vollständige Tabelle) + +| Constraint | Beschreibung | +|-----------|-------------| +| IMAP-Protokoll | IMAP4rev1 (RFC 3501) — Server muss IMAP IDLE (RFC 2177) für Push unterstützen | +| SMTP-Protokoll | SMTP Submission (RFC 6409) — Port 587 mit STARTTLS oder Port 465 mit SSL | +| Auth | SMTP: PLAIN/LOGIN mit Benutzername+Passwort; IMAP: PLAIN/LOGIN | +| Attachment-Storage | Anhänge lokal gespeichert (DMS-Integration), nicht im IMAP-Server belassen (Caching) | +| IMAP IDLE | Real-time Push; Fallback: Polling alle 5 Min | +| Volltext-Suche | PostgreSQL Full-Text Search (tsvector) über Mail-Body + Anhang-Namen (OCR optional post-MVP) | +| HTML-Rendering | Sanitization mit DOMPurify (XSS-Schutz); Plain-Text-Fallback | +| Verschlüsselung | PGP (OpenPGP, RFC 4880) via python-gnupg; S/MIME post-MVP | +| Mail-Sync | Bidirektionale Sync: IMAP-Flags (Seen/Flagged) synchronisiert | +| Ordner-Mapping | INBOX→Posteingang, Sent→Postausgang, Drafts→Entwürfe, Spam/Junk→Spam | +| Passwort-Speicherung | IMAP/SMTP-Passwörter AES-256 verschlüsselt, Key via Env-Var MAIL_ENCRYPTION_KEY | +| Max Anhang-Größe | 25 MB pro Anhang, 50 MB pro Mail | + +--- + +## 15. Mail-Modul: Annahmen + +1. **IMAP-Server-Kompatibilität:** IMAP4rev1 (RFC 3501), getestet mit Dovecot, Courier IMAP. +2. **SMTP-Auth:** SMTP-Server muss Authentifizierung unterstützen (PLAIN/LOGIN). Kein Open-Relay. +3. **IMAP IDLE:** Server muss RFC 2177 unterstützen. Fallback: Polling alle 5 Minuten. +4. **IMAP MOVE:** Server muss RFC 6851 unterstützen. Fallback: COPY + STORE \\Deleted + EXPUNGE. +5. **Attachment-Storage:** Lokal (Dateisystem oder S3), Caching in PostgreSQL. +6. **Maximale Anhang-Größe:** 25 MB pro Anhang, 50 MB pro Mail. +7. **Passwort-Speicherung:** AES-256 verschlüsselt, Key via `MAIL_ENCRYPTION_KEY`. +8. **HTML-Sanitization:** DOMPurify, Inline-Styles erlaubt, Scripts/IFrames entfernt. +9. **Multi-Tenant (Multi-Company):** Mail-Modul ist Multi-Tenant (Multi-Company). +10. **PGP-Key-Verwaltung:** Private Keys verschlüsselt (AES-256 + Passphrase). Passphrase nicht gespeichert, nur Session-Cache. + +--- + +## 16. Implizite DB-Tabellen (aus allen Akzeptanzkriterien extrahiert) + +| Tabelle | Modul | Zweck | +|---------|-------|-------| +| `users` | Core | User (email, name, role, password_hash, default_calendar_id, default_mail_account_id) | +| `companies` | Core | Firmen (siehe Feld-Tabelle + deleted_at, created_at) | +| `contacts` | Core | Kontakte (siehe Feld-Tabelle + deleted_at, created_at) | +| `company_contacts` | Core | N:M-Verknüpfung (company_id, contact_id) | +| `audit_log` | Core | Audit (user, action, entity, entity_id, timestamp) | +| `deletion_log` | Core | Unveränderliche DSGVO-Lösch-Logs | +| `dms_folders` | DMS | Ordner (id, name, parent_id, owner_id, deleted_at) | +| `dms_files` | DMS | Dateien (id, name, folder_id, size, mime_type, uploaded_at, modified_at, uploaded_by, deleted_at) | +| `file_links` | DMS | Verknüpfung (file_id, entity_type, entity_id) — Unique Constraint | +| `tags` | DMS | Tags (id, name, color) | +| `tag_assignments` | DMS | Zuordnung (entity_type, entity_id, tag_id) — Unique Constraint | +| `folder_permissions` | DMS | (folder_id, group_id, permission: read|write) | +| `file_shares` | DMS | (file_id, user_id?, group_id?, permission) | +| `share_links` | DMS | (file_id, token, password?, expires_at?, download_only?) | +| `calendar_entries` | Kalender | (id, entry_type, title, description, start_at?, end_at?, due_date?, all_day?, location?, priority?, status?, assigned_to?, calendar_id, subtype, reminder, recurrence) | +| `calendars` | Kalender | (id, name, color, type, owner_id) | +| `calendar_entry_links` | Kalender | (entry_id, entity_type, entity_id) | +| `calendar_shares` | Kalender | (calendar_id, user_id?, group_id?, permission) | +| `user_calendar_visibility` | Kalender | (user_id, calendar_id, visible) | +| `subtasks` | Kalender | (entry_id, title, completed) | +| `resources` | Kalender | (id, name, type) — später | +| `resource_bookings` | Kalender | (resource_id, entry_id, from, to) — später | +| `mail_accounts` | Mail | (id, user_id, name, imap_host, imap_port, imap_ssl, smtp_host, smtp_port, smtp_ssl/starttls, username, password_encrypted, type) | +| `mails` | Mail | (id, account_id, folder_id, subject, body_html, body_text, from, to, cc, bcc, date, in_reply_to, references, thread_id, seen, flagged, has_attachments) | +| `mail_attachments` | Mail | (mail_id, filename, mime_type, size, dms_file_id) | +| `mail_folders` | Mail | (id, account_id, name, type, parent_id, unread_count, total_count) | +| `mail_labels` | Mail | (id, name, color) | +| `mail_label_assignments` | Mail | (mail_id, label_id) | +| `mail_rules` | Mail | (id, account_id, name, conditions, actions, priority) | +| `mail_templates` | Mail | (id, user_id, name, subject, body_html, shared) | +| `mail_signatures` | Mail | (id, user_id, name, body_html) | +| `vacation_sent_log` | Mail | (account_id, sender_address, sent_at) | +| `mail_seen_by` | Mail | (mail_id, user_id, seen_at) | +| `mail_account_delegates` | Mail | (account_id, delegate_user_id, permission) | +| `mail_account_send_permissions` | Mail | (account_id, user_id) | +| `pgp_keys` | Mail | (user_id, private_key_encrypted, public_key) | +| `contact_pgp_keys` | Mail | (contact_id, public_key) | +| `notifications` | Core | (id, user_id, type, title, body, created_at, read_at) | + +--- + +## 17. Historische v0.1 Endpoints (archiviert) + +| Endpoint | Historisch | Aktuell | +|----------|-----------|--------| +| POST `/login` | F-1 | POST `/api/auth/login` | +| POST `/logout` | F-2 | POST `/api/auth/logout` | +| GET `/companies` | F-5 | GET `/api/companies` | +| POST `/companies` | F-5 | POST `/api/companies` | +| DELETE `/companies/{id}` | F-8 | DELETE `/api/companies/{id}` | +| POST `/contacts` | F-10 | POST `/api/contacts` | +| GET `/api/health` | F-15 | GET `/api/health` | + +### v0.1 Demo-Seed +- Beim ersten Start: 1 Admin (`admin`/`admin`), 2 Firmen, 3 Kontakte +- Frische DB → Seed-Daten vorhanden +- DB bereits befüllt → Seed überspringt + +--- + +## 18. Non-Goals (alle Module, vollständig) + +### v1 Non-Goals +1. Self-Registration (nur Admin legt User an) +2. ~~Multi-Tenant (Single-Tenant in v1)~~ — Multi-Tenant (Multi-Company) ist v1-Feature +3. Sales Pipeline / Deals +4. Kampagnen-Management +5. Mobile App (Native) — nur Responsive Web-UI +6. Offline-Support +7. AI-Features (Lead-Scoring, Auto-Enrichment) +8. Real-time Collaboration (Google Docs-style) +9. Webhooks für externe Systeme +10. Multi-Currency +11. Advanced Analytics/Dashboards (BI) +12. SSO/OAuth (nur E-Mail/Passwort) +13. Custom Fields (Standard-Felder fest definiert) +14. Two-Factor Auth (post-MVP) +15. PWA + +### v2 DMS Non-Goals +1. Versionierung (History, Diff) +2. Volltext-Suche in Dokumenten (kein OCR, kein Volltext-Index) +3. Eigene Office-Suite (nur OnlyOffice) +4. E-Mail-Attachment aus DMS +5. Externes Sync (WebDAV/Nextcloud) +6. Watermarking + +### v2 Kalender Non-Goals +1. Ressourcen-Booking (nur optional markiert) +2. Time-Tracking +3. Task-Templates +4. Automatisierte Task-Erstellung aus Triggern +5. Gantt-Diagramm +6. Öffentliche Kalender-Sync (außer ICS) + +### v2 Mail Non-Goals +1. Google/Microsoft API (nur direkte IMAP/SMTP) +2. S/MIME (nur PGP in v2-mail) +3. Mail-Server-Hosting (nur Client) +4. Mailinglisten-Management +5. Newsletter-Tool +6. Mail-to-Ticket +7. OCR für Anhänge +8. Kalender-Einladungen per Mail (iMIP, RFC 6047) +9. JMAP (RFC 8620) +10. Push-Benachrichtigungen (Web Push) — nur In-App + +--- + +**Ende der extrahierten Architektur-Details.** diff --git a/docs/quality-gate-phase1.md b/docs/quality-gate-phase1.md new file mode 100644 index 0000000..f874bf8 --- /dev/null +++ b/docs/quality-gate-phase1.md @@ -0,0 +1,172 @@ +# Quality Gate Review — Phase 1 → Phase 2 (Re-Review) + +**Datum:** 2026-06-28 +**Dateien:** requirements.md (2142 Zeilen), extracted-architecture-details.md (1006 Zeilen) +**Vorherige Findings:** 6 Fixes angewendet + +--- + +## Prüfkriterien & Ergebnisse + +### 1. Vollständigkeit: 143 Features (73 Core + 70 Plugin) +**Status: ✅ PASS** + +Verifikation: +- Active feature headings (exkl. historisch): 143 +- `[v1]`-Features (Core): 73 +- `[v2-Plugin]`-Features (Plugin): 70 +- `[v1-Plugin]`-Features: 0 (alle konvertiert) +- Summary-Tabelle: 73 Core + 70 Plugin = 143 +- DISCOVERY_CHECK_FINAL: `features_with_ids=143/143` + +### 2. Konsistenz: Plugin vs Core Trennung +**Status: ✅ PASS** + +Verifikation: +- F-FILE-01–04: alle `[v2-Plugin]` (vorher `[v1-Plugin]`) +- F-DMS-01–07: alle `[v2-Plugin]` +- F-LINK-01–06: alle `[v2-Plugin]` +- F-TAG-01–04: alle `[v2-Plugin]` +- F-PERM-01–06: alle `[v2-Plugin]` +- F-FILEUI-01–06: alle `[v2-Plugin]` +- F-CAL-01–18: alle `[v2-Plugin]` (F-CAL-10 = `[v2-Plugin — später]`) +- F-MAIL-01–19: alle `[v2-Plugin]` +- F-PLUGIN-01/02: `[v1]` (Plugin-System ist Core) +- F-CORE-04 (UI-Plugin-Framework): `[v1]` (Core-Infrastruktur) +- Summary-Header: `### Core-Features (v1)` und `### Plugin-Features (v2-Plugin)` + +### 3. Keine Implementierungs-Details in requirements.md +**Status: ✅ PASS** + +Verifikation: +- Keine HTML-Tags (`
`, ``, `