Files
crm-system/docs/01-requirements.md
T

28 KiB
Raw Blame History

CRM System Requirements (v1.0 MVP)

Phase: INTAKE → REQUIREMENTS | Single-Tenant CRM | Status: Draft for User Approval Stack: FastAPI + SQLAlchemy (async) + Alembic + Pydantic + SQLite/PostgreSQL + Alpine.js + Tailwind + Docker + Coolify Auth: JWT im localStorage | Deployment: Coolify localhost standalone (uuid lw80w8scs4044gwcw084s00s4)

1. Vision & Scope

Was ist v1? v1 ist ein leichtgewichtiges, self-hosted CRM für kleine bis mittelgroße Sales-Teams (525 Vertriebler), die ihre Pipeline, Accounts, Contacts und Verkaufsaktivitäten an einem Ort bündeln wollen ohne Salesforce-Lock-in, ohne Cloud-Zwang, ohne monatliche Lizenzkosten. Kern ist die tägliche Arbeit eines Sales-Reps: Account anlegen, Contact verknüpfen, Deal durch die Pipeline ziehen, Aktivitäten (Calls/Meetings/Notes) tracken, im Dashboard den Überblick behalten. Single-Tenant bedeutet: Eine Org, ein Server, ein Datenbestand dafür aber voller Datenschutz, DSGVO-Konformität out-of-the-box und Coolify-Deployment per Knopfdruck.

Was ist v1 NICHT? v1 ist kein Enterprise-CRM mit Multi-Tenancy, komplexem Permission-Granular-RBAC oder AI-Features. v1 hat kein integriertes Email-Marketing, keine Marketing-Automation, keine Lead-Scoring-Maschine. v1 ist kein Mobile-First-System (responsive Web ja, native App nein), kein Realtime-Collaboration-Tool (kein Websocket-Editing wie in Notion), und kein vollumfängliches Reporting-Suite (KPIs ja, Custom-Reports/Exports nein). v1 verzichtet bewusst auf komplexe Workflow-Engines, Sales-Territory-Management und Product-Catalog-Features.

Annahmen:

  • Eine Org, ein Deployment, ein Datenbank-Container kein Mandanten-Trennungs-Layer
  • Erster Admin wird per Bootstrap-Registrierung angelegt (kein Invite-Only-Flow)
  • Maximale User-Anzahl: 50 (Soft-Cap, Hard-Limit erst in v2 via DB-Constraint)
  • Sales-Reps arbeiten im 1:1 mit Accounts/Contacts, kein Shared-Inbox-Pattern
  • Browser: aktuelle Evergreen-Chrome/Firefox/Safari/Edge, kein IE11
  • Internet-Anbindung an Coolify-Server ist stabil, kein Offline-Modus

Non-Goals (explizit out):

  • Multi-Tenancy / Mandantenfähigkeit (auch wenn org_id bereits in DB vorbereitet wird)
  • Email-Marketing-Automation (Newsletter, Drip-Campaigns)
  • Mobile-App (iOS/Android)
  • Lead-Scoring / AI-gestützte Next-Best-Action
  • Custom-Report-Builder / BI-Integration
  • Rechnungsstellung / Quote-to-Cash (kein CPQ)
  • Marketing-Attribution / UTM-Tracking
  • Telefonie-Integration (CTI, Twilio, etc.)
  • Slack/Teams-Integration
  • Custom-Fields-Builder (DB-Schema ist fix für v1)

2. Personas & Haupt-Use-Cases

Persona 1: Sales-Rep (Primärnutzer)

  • Use-Case: „Ich logge mich morgens ein, sehe im Dashboard meine offenen Deals und überfälligen Activities, arbeite die Calls ab, verschiebe gewonnene Deals in die Won-Stage, lege neue Accounts für Inbound-Leads an."
  • Use-Case: „Ich durchsuche meine Accounts nach Branche/Größe, öffne einen Account, sehe alle Contacts und letzten Activities, plane für nächste Woche ein Follow-up-Meeting und vergebe Tags zur Segmentierung."
  • Use-Case: „Ich öffne die Pipeline-View, sehe alle meine Deals als Kanban, ziehe einen Deal von Qualified' nach Proposal' und ergänze einen Note mit dem Verhandlungsstand."

Persona 2: Sales-Manager

  • Use-Case: „Ich logge mich ein, prüfe die Team-Pipeline im Dashboard, sehe Won-this-Month, Conversion-Rate, und filtere die Pipeline auf den Rep, der Backlog hat."
  • Use-Case: „Ich lege einen neuen Sales-Rep-User an, vergebe die Rolle, und der Rep kann sofort loslegen."
  • Use-Case: „Ich passe Org-Info an (Name, Logo-URL, Default-Currency) und sehe im Audit-Log (falls aktiviert), wer wann welche Einstellung geändert hat."

Persona 3: Admin (technisch/organisatorisch)

  • Use-Case: „Ich führe das initiale Bootstrap-Setup durch, lege den ersten Admin an, konfiguriere Org-Settings, und überwache Health/Backups in Coolify."

3. Funktionale Anforderungen (FR)

FR-1: Authentication & User Management

  • FR-1.1 Registrierung des ersten Admin-Users (Bootstrap, nur einmalig möglich wenn users-Tabelle leer)
  • FR-1.2 Login (email + password) → JWT (24h expiry, HS256, im Response-Body)
  • FR-1.3 Logout (JWT clientseitig verworfen, optional Server-seitige Token-Blacklist in v2)
  • FR-1.4 Passwort-Reset (token-basiert, per Email, Token 1h gültig, v1: Token-Endpoint-Output für Tests, SMTP-Integration out-of-scope)
  • FR-1.5 User-CRUD (nur Admin): Anlegen, Bearbeiten, Soft-Delete, Rolle zuweisen (Admin / Sales-Manager / Sales-Rep)
  • FR-1.6 /api/users/me Endpunkt für aktuellen User (für Auth-Validierung & UI-Profil)

Akzeptanzkriterien (testbar):

  • POST /api/auth/register mit gültigem Payload (email, password ≥ 8 Zeichen, name) → 201 + User-Objekt + JWT
  • POST /api/auth/register mit existierender Email → 409 + generische Fehlermeldung
  • POST /api/auth/login mit korrekten Credentials → 200 + JWT + User-Objekt
  • POST /api/auth/login mit falschen Credentials → 401 + generische Fehlermeldung (kein Email-Leak)
  • GET /api/users/me mit gültigem JWT → 200 + User-Daten (ohne password_hash)
  • GET /api/users/me ohne JWT → 401
  • GET /api/users/me mit expired JWT → 401 + Hinweis "token_expired"
  • DB-User wird in users Tabelle angelegt mit gehashtem password_hash (bcrypt oder argon2)
  • Zweiter POST /api/auth/register nach erfolgreichem ersten → 403 (Bootstrap bereits erfolgt)

FR-2: Accounts (Firmen/Kunden)

  • FR-2.1 Account anlegen (Pflicht: name; Optional: website, address (street/city/zip/country), industry, size [1-10/11-50/51-200/201-1000/1000+], owner_id default = current_user)
  • FR-2.2 Account anzeigen, bearbeiten (PATCH), soft-löschen (DELETE → deleted_at timestamp)
  • FR-2.3 Account-Liste mit Paginierung (page, page_size ≤ 100), Volltext-Suche (name), Filter (industry, size, owner_id)
  • FR-2.4 Account-Detail-Page mit allen zugehörigen Contacts, Deals, Activities, Notes (via Eager-Loading)
  • FR-2.5 Ownership-Transfer (PATCH owner_id, mit Audit-Logging in v2)

Akzeptanzkriterien:

  • POST /api/accounts mit gültigem Payload → 201 + Account-Objekt
  • POST /api/accounts mit fehlendem Pflichtfeld name → 422 (Pydantic validation error)
  • GET /api/accounts?page=1&page_size=20 → 200 + Paginierte Liste (items, total, page, page_size)
  • GET /api/accounts?industry=SaaS&size=51-200 → 200 + gefilterte Liste
  • GET /api/accounts/{id} → 200 + Account-Details mit relationen (contacts[], deals[], activities[])
  • PATCH /api/accounts/{id} mit Teil-Update → 200 + aktualisiertes Objekt
  • DELETE /api/accounts/{id} → 204 (soft-delete: deleted_at gesetzt, Datensatz bleibt in DB)
  • DB-Account in accounts Tabelle angelegt, FK zu users (owner_id) ON DELETE RESTRICT

FR-3: Contacts (Personen)

  • FR-3.1 Contact anlegen (Pflicht: first_name, last_name; Optional: email, phone, title, account_id)
  • FR-3.2 Contact bearbeiten (PATCH), soft-löschen, duplizieren (POST /api/contacts/{id}/duplicate)
  • FR-3.3 Contact-Liste mit Suche (first_name, last_name, email ILIKE), Filter (account_id, owner_id)
  • FR-3.4 Email-Uniqueness nur innerhalb einer Org (nicht global), damit mehrere Reps denselben Contact anlegen können

Akzeptanzkriterien:

  • POST /api/contacts mit gültiger account_id → 201, FK-Constraint geprüft (Account existiert)
  • POST /api/contacts mit ungültiger account_id → 404 (Account not found)
  • GET /api/contacts?q=muster&account_id=5 → 200 + gefilterte Liste
  • POST /api/contacts/{id}/duplicate → 201 + neuer Contact (Suffix "(Kopie)" im first_name, ohne Notes/Activities)

FR-4: Deals (Verkaufschancen/Pipeline)

  • FR-4.1 Deal anlegen (Pflicht: title, account_id; Optional: value, currency [default EUR], stage [default "Lead"], close_date, owner_id)
  • FR-4.2 Pipeline-View (Kanban-Endpoint: GET /api/deals/pipeline → Deals gruppiert nach stage, sortiert nach close_date ASC)
  • FR-4.3 Stage-Transition (PATCH /api/deals/{id}/stage) mit History-Tracking in deal_stage_history
  • FR-4.4 Won/Lost-Tracking: Stage "Won" oder "Lost" erfordert won_reason bzw. lost_reason (text, pflicht im Request-Body)
  • FR-4.5 Drag-and-Drop im Frontend: Optimistic Update + PATCH, bei Fehler Rollback + Toast

Akzeptanzkriterien:

  • POST /api/deals mit gültigem Payload → 201
  • PATCH /api/deals/{id}/stage mit neuer stage → 200, History-Eintrag in deal_stage_history (from_stage, to_stage, changed_by, changed_at)
  • PATCH /api/deals/{id}/stage mit stage="Won" ohne won_reason → 422
  • GET /api/deals/pipeline → 200 + JSON mit Stages als Keys, Deal-Listen als Values
  • DELETE /api/deals/{id} → 204 (soft-delete, keine Cascade-Löschung der History)

FR-5: Activities (Calls, Meetings, Emails, Notes-Lite)

  • FR-5.1 Activity anlegen (Pflicht: type [call/meeting/email/task], subject; Optional: body, due_date, account_id?, contact_id?, deal_id?)
  • FR-5.2 Activity-Liste mit Filter (type, status [open/completed/overdue], owner_id, due_date_from/to)
  • FR-5.3 Activity-Complete (PATCH /api/activities/{id}/complete) mit Outcome-Text und completed_at = now()
  • FR-5.4 Overdue-View: GET /api/activities?overdue=true → nur Activities mit due_date < now AND completed_at IS NULL

Akzeptanzkriterien:

  • POST /api/activities mit Polymorphic-FK (z.B. nur account_id) → 201
  • POST /api/activities mit unbekanntem type → 422
  • GET /api/activities?overdue=true → 200 + nur überfällige Activities
  • PATCH /api/activities/{id}/complete mit outcome → 200 + completed_at gesetzt

FR-6: Tags & Notes

  • FR-6.1 Tag-System (color [hex], name) auf Accounts/Contacts/Deals verknüpfbar
  • FR-6.2 Notes (Polymorphic, freier Text + author_id + created_at, parent_type ∈ {account, contact, deal})
  • FR-6.3 Tag-CRUD (POST /api/tags, GET /api/tags, DELETE /api/tags/{id}) und Tag-Linking (POST /api/tags/link)

Akzeptanzkriterien:

  • POST /api/tags mit gültigem Payload → 201
  • POST /api/tags/link mit (tag_id, parent_type, parent_id) → 201, Link in tag_links
  • GET /api/accounts/{id} liefert Tags inklusive der via tag_links verknüpften
  • POST /api/notes mit parent_type="deal" und parent_id=42 → 201, Note polymorph verknüpft

FR-7: Dashboard

  • FR-7.1 KPIs (GET /api/dashboard/kpis):
    • open_deals_count (Deals nicht in Won/Lost)
    • open_deals_value (Summe value aller offenen Deals)
    • won_this_month_count (Deals mit stage=Won und close_date im aktuellen Monat)
    • won_this_month_value (Summe value dieser Deals)
    • conversion_rate (Won / (Won + Lost) in den letzten 90 Tagen, 01)
  • FR-7.2 Activity-Feed (GET /api/dashboard/feed?limit=20): Letzte 20 Activities gruppiert nach date(created_at) = today und older

Akzeptanzkriterien:

  • GET /api/dashboard/kpis → 200 + JSON mit allen 5 KPIs, korrekt berechnet
  • GET /api/dashboard/feed?limit=20 → 200 + max 20 Activities, neueste zuerst
  • KPIs sind org-isoliert (kein Cross-Tenant-Leak)

FR-8: Settings (User-Profile, Org-Info)

  • FR-8.1 User-Profile bearbeiten (PATCH /api/users/me): name, avatar_url, email_notification_prefs (JSON: {deal_won, activity_due})
  • FR-8.2 Org-Info bearbeiten (PATCH /api/org) nur Admin: name, logo_url, default_currency, timezone
  • FR-8.3 Email-Change erfordert Re-Authentifizierung (Password-Bestätigung) in v1.1; v1 erlaubt direkten PATCH

Akzeptanzkriterien:

  • PATCH /api/users/me mit gültigem Payload → 200 + aktualisierter User
  • PATCH /api/org als Non-Admin → 403
  • PATCH /api/org als Admin mit gültigem Payload → 200 + aktualisierte Org

4. Nicht-funktionale Anforderungen (NFR)

  • NFR-1 Performance: p95 Response-Time < 300 ms für Standard-CRUD-Endpoints (unter Last 50 concurrent Users, SQLite/PostgreSQL lokal); Pipeline-Endpoint < 500 ms p95
  • NFR-2 Security:
    • Passwort-Hashing: argon2id (Primary) oder bcrypt (Fallback), kein MD5/SHA1
    • JWT: HS256 mit AUTH_SECRET ≥ 32 Zeichen aus Coolify Env-Vars
    • Input-Validation: Pydantic v2 für alle Request-Bodies und Query-Params
    • CORS-Whitelist: explizite Origins, kein *
    • Rate-Limiting: 60 req/min/IP für Auth-Endpoints (SlowAPI oder ähnliches)
    • SQL-Injection-Schutz: ausschließlich SQLAlchemy ORM/parameterisierte Queries
  • NFR-3 Skalierbarkeit: Horizontale Skalierung über mehrere FastAPI-Container via Coolify (stateless App), PostgreSQL als shared DB; SQLite-Limit (single-writer) in Dev-Setup dokumentiert
  • NFR-4 Maintainability:
    • Type-Hints überall (mypy strict)
    • Ruff/Black-Formatierung, pre-commit-Hook
    • pytest-Coverage ≥ 70 % (Lines + Branches)
    • OpenAPI-Spec wird automatisch generiert unter /docs und /redoc
  • NFR-5 Observability:
    • Strukturiertes Logging: JSON (python-json-logger), Felder timestamp, level, request_id, user_id, path, method, status, latency_ms
    • /health Endpoint: 200 wenn DB-Connection OK, sonst 503
    • /metrics Prometheus-compatible (optional v1.1, vorbereitet via prometheus-fastapi-instrumentator)
    • Request-ID-Middleware für Tracing
  • NFR-6 Backup & Recovery:
    • Tägliches DB-Backup via Coolify-Side (PostgreSQL-Dump, 7 Tage Retention)
    • Restore-Runbook in docs/runbook-backup-restore.md
    • Recovery-Time-Objective (RTO) ≤ 4h, Recovery-Point-Objective (RPO) ≤ 24h
  • NFR-7 Internationalization (i18n):
    • v1: Deutsch + Englisch, Strings in separater locales/ JSON-Datei
    • Frontend: einfaches i18n-Objekt, Sprache umschaltbar via LocalStorage
    • vorbereitet für v2 (3+ Sprachen, Crowdin-Integration)
  • NFR-8 Accessibility (a11y):
    • WCAG 2.1 AA: Kontraste, Keyboard-Navigation, ARIA-Labels
    • Alpine.js + Tailwind ermöglichen semantisches HTML

5. Datenmodell-Skizze

users (1) ──< (N) accounts (1) ──< (N) contacts
                                     │
                                     └──< (N) deals ──< (N) activities
                                                  │           │
                                                  │           └──< (N) notes (polymorphic)
                                                  │
                                                  └──< (N) deal_stage_history
accounts, contacts, deals ──< (N) tags (polymorphic via tag_links)

Wichtige Entities (Felder-Übersicht, vollständige DDL in Phase 2):

Entity Schlüsselfelder Indizes
users id (UUID), email (unique, citext), password_hash, name, role (enum), org_id, created_at, deleted_at UNIQUE (org_id, email) WHERE deleted_at IS NULL
orgs id (UUID), name, logo_url, default_currency, timezone, created_at
accounts id (UUID), name, website, industry, size (enum), street, city, zip, country, owner_id, org_id, created_at, deleted_at INDEX (org_id, deleted_at), INDEX (owner_id)
contacts id (UUID), first_name, last_name, email, phone, title, account_id, owner_id, org_id, created_at, deleted_at INDEX (account_id), INDEX (org_id, last_name)
deals id (UUID), title, value (Decimal), currency, stage (enum), close_date, won_reason, lost_reason, account_id, owner_id, org_id, created_at, deleted_at INDEX (org_id, stage), INDEX (owner_id, close_date)
activities id (UUID), type (enum), subject, body, due_date, completed_at, outcome, account_id?, contact_id?, deal_id?, owner_id, org_id, created_at INDEX (owner_id, completed_at), INDEX (due_date) WHERE completed_at IS NULL
notes id (UUID), body (text), author_id, parent_type, parent_id, org_id, created_at INDEX (parent_type, parent_id)
tags id (UUID), name, color, org_id, created_at UNIQUE (org_id, name)
tag_links tag_id, parent_type, parent_id, org_id, created_at UNIQUE (tag_id, parent_type, parent_id), INDEX (parent_type, parent_id)
deal_stage_history id, deal_id, from_stage, to_stage, changed_by, changed_at INDEX (deal_id, changed_at DESC)

Multi-Tenancy-Hinweis: Single-Tenant v1, aber org_id Spalte ist in allen Tabellen mit dabei und alle Queries filtern automatisch nach org_id = current_user.org_id. Dies ermöglicht eine saubere Migration zu Multi-Tenant in v2 ohne Schema-Bruch (nur Middleware-Erweiterung).

6. API-Surface-Skizze (OpenAPI-Lite)

Method Path Auth Beschreibung
POST /api/auth/register Erster Admin registrieren (Bootstrap)
POST /api/auth/login Login → JWT
POST /api/auth/refresh JWT JWT refreshen
POST /api/auth/logout JWT Logout (clientseitig)
POST /api/auth/password-reset/request Reset-Token anfordern
POST /api/auth/password-reset/confirm Reset-Token + neues Passwort
GET /api/users/me JWT Aktueller User
PATCH /api/users/me JWT Eigene Profil-Daten bearbeiten
GET /api/users JWT+Admin Alle User (paginiert)
POST /api/users JWT+Admin User anlegen
PATCH /api/users/{id} JWT+Admin User bearbeiten
DELETE /api/users/{id} JWT+Admin User löschen (soft)
GET /api/accounts JWT Accounts-Liste (paginiert, suchbar)
POST /api/accounts JWT Account anlegen
GET /api/accounts/{id} JWT Account-Detail inkl. Relationen
PATCH /api/accounts/{id} JWT Account bearbeiten
DELETE /api/accounts/{id} JWT Account löschen (soft)
GET /api/contacts JWT Contacts-Liste
POST /api/contacts JWT Contact anlegen
GET /api/contacts/{id} JWT Contact-Detail
PATCH /api/contacts/{id} JWT Contact bearbeiten
DELETE /api/contacts/{id} JWT Contact löschen (soft)
POST /api/contacts/{id}/duplicate JWT Contact duplizieren
GET /api/deals JWT Deals-Liste (filterbar)
POST /api/deals JWT Deal anlegen
GET /api/deals/pipeline JWT Kanban-View (gruppiert)
GET /api/deals/{id} JWT Deal-Detail
PATCH /api/deals/{id} JWT Deal bearbeiten
PATCH /api/deals/{id}/stage JWT Stage-Transition + History
DELETE /api/deals/{id} JWT Deal löschen (soft)
GET /api/activities JWT Activities-Liste (filterbar)
POST /api/activities JWT Activity anlegen
GET /api/activities/{id} JWT Activity-Detail
PATCH /api/activities/{id} JWT Activity bearbeiten
PATCH /api/activities/{id}/complete JWT Activity abschließen
DELETE /api/activities/{id} JWT Activity löschen (soft)
GET /api/notes JWT Notes (parent_type+parent_id)
POST /api/notes JWT Note anlegen (polymorph)
DELETE /api/notes/{id} JWT Note löschen
GET /api/tags JWT Tags-Liste
POST /api/tags JWT Tag anlegen
PATCH /api/tags/{id} JWT Tag bearbeiten (name/color)
DELETE /api/tags/{id} JWT Tag löschen (cascade zu tag_links)
POST /api/tags/link JWT Tag mit Entity verknüpfen
DELETE /api/tags/link JWT Tag-Verknüpfung löschen
GET /api/dashboard/kpis JWT KPIs (5 Werte)
GET /api/dashboard/feed JWT Activity-Feed (latest 20)
GET /api/org JWT Org-Info lesen
PATCH /api/org JWT+Admin Org-Info bearbeiten
GET /health Healthcheck (200/503)
GET /docs Swagger-UI
GET /redoc ReDoc-UI
GET /openapi.json OpenAPI-Spec (JSON)

7. UI-Skizze (Pages)

  • /login Login-Form (Email + Passwort), Redirect auf /
  • /register Erster-Admin-Registrierung (Bootstrap), nur erreichbar solange users-Tabelle leer
  • / Dashboard (KPI-Karten oben: Open Deals, Pipeline Value, Won This Month, Conversion Rate; Activity-Feed unten)
  • /accounts Account-Liste (Tabelle mit Suche, Filter-Sidebar für Industry/Size, Pagination)
  • /accounts/{id} Account-Detail (Tabs: Info | Contacts | Deals | Activities | Notes)
  • /contacts Contact-Liste (Tabelle mit Suche nach Name/Email, Filter nach Account)
  • /contacts/{id} Contact-Detail (Tabs: Info | Activities | Notes)
  • /pipeline Kanban-Board (Deals gruppiert nach Stage als Spalten, Drag-and-Drop)
  • /activities Activity-Liste (Tabelle mit Overdue-Highlight, Filter nach Type/Status)
  • /settings/profile User-Profile (Name, Avatar-URL, Notification-Prefs)
  • /settings/users User-Verwaltung (nur Admin, Tabelle + Invite-Form)
  • /settings/org Org-Info (nur Admin, Name, Logo-URL, Default-Currency, Timezone)
  • /404 Not-Found-Page
  • /403 Forbidden-Page (z.B. Non-Admin auf /settings/users)

8. Test-Strategie (Kurzfassung, Details in Phase 5)

  • Unit-Tests: pytest, isolierte Tests für models/, schemas/, services/
  • Integration-Tests: pytest + httpx.AsyncClient gegen eine live FastAPI-Test-Instance mit echter SQLite-DB in tmp-Dir + Alembic upgrade head pro Test-Session
  • DB-Write-Tests: jedes SQLAlchemy-Model bekommt einen CRUD-Test gegen die echte Test-DB
  • Auth-Tests: positiver Login, negativer Login, expired JWT, fehlender JWT, fehlende Rollen (403)
  • API-Contract-Tests: Smoke-Tests für alle 51 Endpoints (200/201/204/400/401/403/404/422)
  • E2E-Tests (optional v1): Playwright gegen den Docker-Stack (login → account anlegen → deal erstellen → pipeline-view → drag-and-drop)
  • Smoke-Tests (Runtime): nach Docker-up werden via curl getestet: /health, Auth-Flow, 3+ zufällige Endpoints
  • Coverage-Target: ≥ 70 % Lines + Branches, gemessen mit pytest-cov, Threshold enforced in CI
  • Test-DB-Isolation: jede Test-Function bekommt eigene Transaktion (rollback nach Test), parallele Test-Runs via pytest-xdist

9. Deployment-Plan (Kurzfassung, Details in Phase 5)

  • Dev (lokal):

    • SQLite, uvicorn main:app --reload --port 8000
    • alembic upgrade head vor Start
    • .env-Datei mit Dev-Secrets (nicht in Git)
    • Frontend: python -m http.server 8080 (statische Files) oder direkt in FastAPI als StaticFiles gemounted
  • Prod (Coolify):

    • PostgreSQL via Coolify DB-Service (Image: postgres:16-alpine)
    • FastAPI-Container (Image: python:3.12-slim, Build via Dockerfile mit multi-stage)
    • Caddy/Nginx-Proxy in Coolify für TLS + Domain-Routing
    • Frontend: gleicher Container, statische Files via FastAPI StaticFiles
  • Coolify-Server:

    • Server: localhost (uuid lw80w8scs4044gwcw084s00s4)
    • Limits: 2 concurrent_builds, 25 queue_limit, 3600s build_timeout
    • Coolify-Build-Command: docker build -t crm-backend .
    • Coolify-Start-Command: alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
  • Healthcheck: /health Endpoint, Coolify-side curl -f http://localhost:8000/health || exit 1

  • Secrets (über Coolify Env-Vars, nie im Repo):

    • AUTH_SECRET (≥ 32 Zeichen, JWT-Signing-Key)
    • DATABASE_URL (PostgreSQL-Connection-String, von Coolify automatisch injected)
    • CORS_ORIGINS (komma-separierte Liste erlaubter Origins)
    • LOG_LEVEL (default INFO, in Prod WARNING)
  • Migration-Run: alembic upgrade head als Pre-Start-Schritt in Coolify (idempotent)

  • Rollback-Strategie: Coolify-Deployment-History, manuelle Rollback via Coolify-UI auf vorherigen Container-Tag

10. Risiken & offene Punkte

Risiken:

  • R-1: Single-Tenant jetzt, aber Migrations-Pfad zu Multi-Tenant: Trotz org_id-Spalte in allen Tabellen muss die Middleware/Query-Logik strikt nach org_id filtern. Risiko: ein vergessenes .where(org_id=...) in einem Query leakt Daten. Mitigation: zentraler OrgScopedQuery-Helper, der in allen Repositories genutzt wird.
  • R-2: Polymorphic-FKs (Notes, Tags) komplexer als relational-FKs: Kein DB-Constraint, der sicherstellt, dass parent_id in der richtigen Tabelle existiert. Risiko: orphaned Notes/Tags. Mitigation: Validierung in Service-Layer + Cleanup-Job in Phase 5.
  • R-3: Coolify localhost = lokaler Container: Coolify läuft auf localhost, der CRM-Container ist nur intern erreichbar. Eine echte Public-Domain mit TLS ist für v1.1 geplant. Risiko: Dev-Stack ≠ Prod-Stack. Mitigation: Docker-Compose-File, das identisch lokal wie in Coolify läuft.
  • R-4: Single-Writer SQLite in Dev-Parallelbetrieb: Wenn mehrere Developer/Tests parallel laufen, blockt SQLite. Mitigation: Tests nutzen separate in-memory-SQLites pro Worker; Prod-Plan ist explizit PostgreSQL.
  • R-5: JWT im localStorage = XSS-Risiko: Wenn ein Angreifer XSS schafft, kann er das JWT stehlen. Mitigation: strikte CSP-Header, kein innerHTML mit User-Input, Alpine.js mit x-text statt x-html als Default.

Offene Punkte (Entscheidungen ausstehend):

  • OP-1: Avatar-Upload ja/nein? Empfehlung: Nein in v1, nur avatar_url (User gibt externe URL an). Begründung: File-Upload-Endpoint + Storage-Lösung (S3-kompatibel oder Coolify-Volume) wäre eigener Scope.
  • OP-2: Email-Integration (SMTP) ja/nein? Empfehlung: Nein in v1. Begründung: SMTP-Provider-Konfiguration, Email-Templates, Bounce-Handling, SPF/DKIM-Setup = signifikante Komplexität. Passwort-Reset-Token werden in v1 als Response-Output zurückgegeben (für Tests/Dev), SMTP-Integration in v1.1.
  • OP-3: Audit-Log ja/nein? Empfehlung: Minimal in v1 (deal_stage_history deckt das Wichtigste ab), vollständig in v2 (alle PATCH/DELETE-Aktionen mit Before/After-Snapshot). Begründung: Volles Audit-Log braucht Trigger oder Middleware, die Performance kostet.
  • OP-4: i18n: Deutsch + Englisch oder nur Deutsch v1? Empfehlung: Beide (Aufwand gering bei JSON-Locale-File, großer UX-Wert).
  • OP-5: Soft-Delete vs. Hard-Delete für DSGVO „Recht auf Vergessenwerden"? Empfehlung: Soft-Delete + Admin-Tool für Hard-Delete in v1.1. Begründung: Audit-Trail bleibt erhalten, Revoke möglich.

11. User-Story-Mapping (Traceability)

# Story FR-Refs API-Endpoint UI-Page
1 Als Sales-Rep logge ich mich ein FR-1.2 POST /api/auth/login /login
2 Als Sales-Rep sehe ich beim ersten Besuch die Registrierung FR-1.1 POST /api/auth/register /register
3 Als Sales-Rep lege ich einen neuen Account an FR-2.1 POST /api/accounts /accounts (Modal)
4 Als Sales-Rep durchsuche Accounts nach Branche FR-2.3 GET /api/accounts?industry=... /accounts
5 Als Sales-Rep öffne einen Account und sehe alle Contacts FR-2.4 GET /api/accounts/{id} /accounts/{id} (Tab Contacts)
6 Als Sales-Rep füge ich einen Contact zu einem Account hinzu FR-3.1 POST /api/contacts /accounts/{id} (Modal)
7 Als Sales-Rep lege ich einen Deal auf einem Account an FR-4.1 POST /api/deals /accounts/{id} (Modal)
8 Als Sales-Rep öffne die Pipeline und sehe alle meine Deals als Kanban FR-4.2 GET /api/deals/pipeline /pipeline
9 Als Sales-Rep ziehe einen Deal per Drag-and-Drop in die nächste Stage FR-4.3, FR-4.5 PATCH /api/deals/{id}/stage /pipeline (Kanban)
10 Als Sales-Rep schließe einen Deal als Won ab mit Grund FR-4.4 PATCH /api/deals/{id}/stage /pipeline / /deals/{id} (Modal)
11 Als Sales-Rep plane eine Follow-up-Aktivität FR-5.1 POST /api/activities /accounts/{id} / /contacts/{id} (Modal)
12 Als Sales-Rep sehe ich alle meine überfälligen Aktivitäten FR-5.4 GET /api/activities?overdue=true /activities
13 Als Sales-Rep schließe eine Aktivität mit Outcome ab FR-5.3 PATCH /api/activities/{id}/complete /activities (Row)
14 Als Sales-Rep füge einem Account einen Tag hinzu FR-6.1, FR-6.3 POST /api/tags/link /accounts/{id} (Tag-Picker)
15 Als Sales-Rep füge eine Note zu einem Deal hinzu FR-6.2 POST /api/notes /deals (Tab Notes)
16 Als Sales-Rep öffne das Dashboard und sehe meine KPIs FR-7.1 GET /api/dashboard/kpis /
17 Als Sales-Rep sehe im Dashboard den Activity-Feed FR-7.2 GET /api/dashboard/feed /
18 Als Sales-Rep bearbeite mein Profil (Avatar, Notification-Prefs) FR-8.1 PATCH /api/users/me /settings/profile
19 Als Sales-Manager lege einen neuen Sales-Rep an FR-1.5 POST /api/users /settings/users
20 Als Sales-Manager sehe alle Deals meines Teams in der Pipeline FR-4.2 GET /api/deals/pipeline?owner_id=... /pipeline (Filter)
21 Als Admin bearbeite Org-Info (Name, Logo, Default-Currency) FR-8.2 PATCH /api/org /settings/org
22 Als User fordere ich einen Passwort-Reset an FR-1.4 POST /api/auth/password-reset/request /login ("Passwort vergessen")
23 Als User setze ich mein Passwort mit Token zurück FR-1.4 POST /api/auth/password-reset/confirm /reset-password?token=...
24 Als User logge mich aus FR-1.3 POST /api/auth/logout (Header-Logout-Button)

Phase-Status: REQUIREMENTS — Draft for User Approval

Nächste Phase nach User-Approval: solution_architect → 02-architecture.md + 03-task-graph.json