From d8f46abe0bf1595cf75528d543b920e9aa6d0266 Mon Sep 17 00:00:00 2001 From: leocrm-bot Date: Sun, 28 Jun 2026 12:36:01 +0200 Subject: [PATCH] chore: clean main for new system architecture - Old code archived on archive/legacy-v0 and feat/T1-auth - Main contains only requirements and analysis docs - UI Prototype: https://webspace.media-on.de/leocrm-prototype-x7k2p9/ - Ready for Phase 2: Architecture design --- .coverage | Bin 0 -> 73728 bytes app/templates/base.html | 58 - app/templates/company_form.html | 20 - app/templates/contact_form.html | 20 - app/templates/contact_list.html | 37 - app/templates/dashboard.html | 34 - app/templates/login.html | 12 - app/templates/register.html | 12 - codebase-vs-requirements.md | 540 ++++++ docs/codebase-vs-requirements.md | 540 ++++++ docs/extracted-architecture-details.md | 1006 +++++++++++ docs/quality-gate-phase1.md | 172 ++ docs/requirements-review.md | 375 ++++ docs/requirements.md | 2142 +++++++++++++++++++++++ extracted-architecture-details.md | 1006 +++++++++++ quality-gate-phase1.md | 172 ++ requirements-review.md | 375 ++++ requirements.md | 2166 +++++++++++++++++++++++- 18 files changed, 8462 insertions(+), 225 deletions(-) create mode 100644 .coverage delete mode 100644 app/templates/base.html delete mode 100644 app/templates/company_form.html delete mode 100644 app/templates/contact_form.html delete mode 100644 app/templates/contact_list.html delete mode 100644 app/templates/dashboard.html delete mode 100644 app/templates/login.html delete mode 100644 app/templates/register.html create mode 100644 codebase-vs-requirements.md create mode 100644 docs/codebase-vs-requirements.md create mode 100644 docs/extracted-architecture-details.md create mode 100644 docs/quality-gate-phase1.md create mode 100644 docs/requirements-review.md create mode 100644 docs/requirements.md create mode 100644 extracted-architecture-details.md create mode 100644 quality-gate-phase1.md create mode 100644 requirements-review.md diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..c7f089829be517abdae21e54ca354b859911f690 GIT binary patch literal 73728 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 N(kkxH^JH<~{{c0_iT?lq literal 0 HcmV?d00001 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 (`
`, ``, `