122 lines
5.9 KiB
Markdown
122 lines
5.9 KiB
Markdown
|
|
# leocrm — Requirements (v0.1, Mini-CRM, Single-Tenant)
|
||
|
|
|
||
|
|
## 1. Ziel
|
||
|
|
|
||
|
|
Ein schlankes, in sich geschlossenes Mini-CRM, das folgende Kern-Workflows abdeckt:
|
||
|
|
- Anmeldung (Login/Logout) per Session-Cookie
|
||
|
|
- Verwaltung von **Firmen** (Anlegen, Anzeigen, Bearbeiten, Löschen, Liste mit Suche)
|
||
|
|
- Verwaltung von **Kontaktpersonen** (Anlegen, Anzeigen, Bearbeiten, Löschen, Zuordnung zu Firma)
|
||
|
|
- Übersichts-Dashboard mit Anzahl Firmen, Kontakten und letzten Änderungen
|
||
|
|
|
||
|
|
Single-Tenant, eine Org, eine SQLite-Datei. Keine Rollen, keine Multi-User-Berechtigungen jenseits Login ja/nein.
|
||
|
|
|
||
|
|
## 2. Personas
|
||
|
|
|
||
|
|
- **Admin/Nutzer** (1 Person pro Org): meldet sich an, verwaltet Daten.
|
||
|
|
- **Entwickler**: betreibt die App lokal und in Coolify.
|
||
|
|
|
||
|
|
## 3. Funktionale Anforderungen (Must-Have)
|
||
|
|
|
||
|
|
| ID | Anforderung | Akzeptanzkriterium |
|
||
|
|
|----|-------------|---------------------|
|
||
|
|
| F-1 | Login-Formular | GET `/login` zeigt Formular; POST mit gültigen Credentials setzt Session-Cookie und leitet auf `/` weiter; ungültige Credentials → 401 + Fehlermeldung. |
|
||
|
|
| F-2 | Logout | POST `/logout` löscht Session und leitet auf `/login` weiter. |
|
||
|
|
| F-3 | Auth-Schutz | Alle Seiten außer `/login` und `/api/health` erfordern eine aktive Session; ohne Session → 302 → `/login`. |
|
||
|
|
| F-4 | Dashboard | GET `/` zeigt Anzahl Companies, Anzahl Contacts, letzte 5 Änderungen (Companies + Contacts). |
|
||
|
|
| F-5 | Companies-Liste | GET `/companies` zeigt Tabelle mit Name, Stadt, Land, Anzahl Contacts; Volltextsuche über Name/Stadt filtert. |
|
||
|
|
| F-6 | Company anlegen | GET `/companies/new` zeigt Formular; POST erstellt Datensatz, Redirect auf Detail. |
|
||
|
|
| F-7 | Company-Detail | GET `/companies/{id}` zeigt Stammdaten + Liste der zugeordneten Contacts + Link „Contact hinzufügen". |
|
||
|
|
| F-8 | Company bearbeiten | GET `/companies/{id}/edit` zeigt vorausgefülltes Formular; PATCH speichert, Redirect auf Detail. |
|
||
|
|
| F-9 | Company löschen | POST `/companies/{id}/delete` löscht inkl. zugeordneter Contacts (cascade). |
|
||
|
|
| F-10 | Contact anlegen | GET `/contacts/new?company_id={id}` zeigt Formular; POST erstellt Datensatz mit FK auf Company. |
|
||
|
|
| F-11 | Contact-Detail | GET `/contacts/{id}` zeigt alle Felder + Link zur Firma. |
|
||
|
|
| F-12 | Contact bearbeiten | GET `/contacts/{id}/edit` zeigt Formular; PATCH speichert. |
|
||
|
|
| F-13 | Contact löschen | POST `/contacts/{id}/delete` löscht. FK-Company bleibt erhalten. |
|
||
|
|
| F-14 | API parallel zu HTML | Für jede HTML-Aktion gibt es einen äquivalenten JSON-API-Endpoint (siehe API-Spec unten), damit Tests Headless durchlaufen können. |
|
||
|
|
| F-15 | Health-Endpoint | GET `/api/health` → 200 `{"status":"ok","db":"ok"}`. |
|
||
|
|
| F-16 | Demo-Seed | Beim ersten Start wird automatisch 1 Admin-User (`admin`/`admin`), 2 Beispiel-Firmen und 3 Beispiel-Kontakte angelegt, falls DB leer. |
|
||
|
|
|
||
|
|
## 4. Datenmodell
|
||
|
|
|
||
|
|
```
|
||
|
|
User
|
||
|
|
id INTEGER PK
|
||
|
|
username TEXT UNIQUE NOT NULL
|
||
|
|
password_hash TEXT NOT NULL
|
||
|
|
created_at TIMESTAMP DEFAULT now
|
||
|
|
|
||
|
|
Company
|
||
|
|
id INTEGER PK
|
||
|
|
name TEXT NOT NULL
|
||
|
|
street TEXT
|
||
|
|
zip TEXT
|
||
|
|
city TEXT
|
||
|
|
country TEXT DEFAULT 'DE'
|
||
|
|
email TEXT
|
||
|
|
phone TEXT
|
||
|
|
website TEXT
|
||
|
|
notes TEXT
|
||
|
|
created_at TIMESTAMP DEFAULT now
|
||
|
|
updated_at TIMESTAMP DEFAULT now
|
||
|
|
|
||
|
|
Contact
|
||
|
|
id INTEGER PK
|
||
|
|
company_id INTEGER FK -> Company.id ON DELETE CASCADE
|
||
|
|
first_name TEXT NOT NULL
|
||
|
|
last_name TEXT NOT NULL
|
||
|
|
email TEXT
|
||
|
|
phone TEXT
|
||
|
|
position TEXT
|
||
|
|
notes TEXT
|
||
|
|
created_at TIMESTAMP DEFAULT now
|
||
|
|
updated_at TIMESTAMP DEFAULT now
|
||
|
|
```
|
||
|
|
|
||
|
|
## 5. API-Spec (für Headless-Tests)
|
||
|
|
|
||
|
|
| Methode | Pfad | Body / Params | Erfolg | Fehler |
|
||
|
|
|--------|------|---------------|--------|--------|
|
||
|
|
| GET | `/api/health` | — | 200 | — |
|
||
|
|
| POST | `/api/auth/login` | `{username,password}` | 200 + Set-Cookie | 401 |
|
||
|
|
| POST | `/api/auth/logout` | — | 204 | — |
|
||
|
|
| GET | `/api/companies` | `?q=&limit=&offset=` | 200 List | — |
|
||
|
|
| POST | `/api/companies` | JSON | 201 | 400 |
|
||
|
|
| GET | `/api/companies/{id}` | — | 200 | 404 |
|
||
|
|
| PATCH | `/api/companies/{id}` | JSON | 200 | 404, 400 |
|
||
|
|
| DELETE | `/api/companies/{id}` | — | 204 | 404 |
|
||
|
|
| GET | `/api/contacts` | `?company_id=` | 200 | — |
|
||
|
|
| POST | `/api/contacts` | JSON | 201 | 400, 404 (Company) |
|
||
|
|
| GET | `/api/contacts/{id}` | — | 200 | 404 |
|
||
|
|
| PATCH | `/api/contacts/{id}` | JSON | 200 | 404 |
|
||
|
|
| DELETE | `/api/contacts/{id}` | — | 204 | 404 |
|
||
|
|
|
||
|
|
## 6. Nicht-Ziele (Out of Scope für v0.1)
|
||
|
|
|
||
|
|
- Multi-Tenant / Multi-Org
|
||
|
|
- Rollen-/Rechtesystem (jeder Login-User darf alles)
|
||
|
|
- E-Mail-Versand, Datei-Upload, Bilder
|
||
|
|
- Audit-Log, Soft-Delete, Papierkorb
|
||
|
|
- 2FA, OAuth, Passwort-Reset per Mail
|
||
|
|
- Internationalisierung (deutsche UI, hartkodiert)
|
||
|
|
- Mobile-Apps, Push-Notifications
|
||
|
|
- Reporting, Dashboards mit Charts (nur Counts)
|
||
|
|
|
||
|
|
## 7. Annahmen
|
||
|
|
|
||
|
|
- Coolify-Host `server.media-on.de` ist erreichbar; Coolify-Token liegt in der Plugin-Config
|
||
|
|
- Forgejo-Host `forgejo.media-on.de` ist erreichbar; Token liegt in der Plugin-Config
|
||
|
|
- SQLite reicht für v0.1 (< 100k Datensätze), Migration auf Postgres später möglich
|
||
|
|
- Deployment erfolgt als einzelner Coolify-Service (Docker Compose mit 1 Container)
|
||
|
|
- Default-Admin-Credentials `admin/admin` werden beim ersten Start geseeded und sind nur in v0.1; im v0.2 verpflichtender Passwort-Change
|
||
|
|
|
||
|
|
## 8. Qualitätskriterien
|
||
|
|
|
||
|
|
- **Type-Check**: `python -m mypy app/` ohne Fehler (oder bewusst als nicht-blockend dokumentiert)
|
||
|
|
- **Tests**: `pytest` mit mindestens **15 Tests**, alle grün (Health, Auth happy + fail, Company CRUD 5, Contact CRUD 5, Cascade-Delete 1, Search 1)
|
||
|
|
- **Server-Start**: `uvicorn app.main:app` startet ohne ImportError oder 500er
|
||
|
|
- **Health-Endpoint**: 200, nicht 500
|
||
|
|
- **API-Endpoints**: alle 13 oben genannten Endpunkte liefern die spezifizierten Status-Codes
|
||
|
|
- **Build**: Docker-Image buildet ohne Fehler, Image-Größe < 300 MB
|
||
|
|
- **Deploy**: Coolify-Service ist `running:healthy` nach `POST /api/v1/deploy`
|
||
|
|
- **Git**: alle Änderungen committed, `git status` ist sauber
|