28 KiB
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
localhoststandalone (uuidlw80w8scs4044gwcw084s00s4)
1. Vision & Scope
Was ist v1? v1 ist ein leichtgewichtiges, self-hosted CRM für kleine bis mittelgroße Sales-Teams (5–25 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_idbereits 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/meEndpunkt 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
usersTabelle angelegt mit gehashtempassword_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_attimestamp) - 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_atgesetzt, Datensatz bleibt in DB) - DB-Account in
accountsTabelle angelegt, FK zuusers(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_reasonbzw.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_linksverknü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, 0–1)
- FR-7.2 Activity-Feed (GET /api/dashboard/feed?limit=20): Letzte 20 Activities gruppiert nach
date(created_at) = todayundolder
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
/docsund/redoc
- NFR-5 Observability:
- Strukturiertes Logging: JSON (python-json-logger), Felder
timestamp,level,request_id,user_id,path,method,status,latency_ms /healthEndpoint: 200 wenn DB-Connection OK, sonst 503/metricsPrometheus-compatible (optional v1.1, vorbereitet viaprometheus-fastapi-instrumentator)- Request-ID-Middleware für Tracing
- Strukturiertes Logging: JSON (python-json-logger), Felder
- 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)
- v1: Deutsch + Englisch, Strings in separater
- 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.AsyncClientgegen eine live FastAPI-Test-Instance mit echter SQLite-DB in tmp-Dir + Alembicupgrade headpro 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
curlgetestet:/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 headvor Start.env-Datei mit Dev-Secrets (nicht in Git)- Frontend:
python -m http.server 8080(statische Files) oder direkt in FastAPI als StaticFiles gemounted
- SQLite,
-
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
- PostgreSQL via Coolify DB-Service (Image:
-
Coolify-Server:
- Server:
localhost(uuidlw80w8scs4044gwcw084s00s4) - 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
- Server:
-
Healthcheck:
/healthEndpoint, Coolify-sidecurl -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 headals 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 nachorg_idfiltern. Risiko: ein vergessenes.where(org_id=...)in einem Query leakt Daten. Mitigation: zentralerOrgScopedQuery-Helper, der in allen Repositories genutzt wird. - R-2: Polymorphic-FKs (Notes, Tags) komplexer als relational-FKs: Kein DB-Constraint, der sicherstellt, dass
parent_idin 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 auflocalhost, 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
innerHTMLmit User-Input, Alpine.js mitx-textstattx-htmlals 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_historydeckt 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