diff --git a/docs/INSIGHT-CRM.md b/docs/INSIGHT-CRM.md index ed9fb58..fdaa70c 100644 --- a/docs/INSIGHT-CRM.md +++ b/docs/INSIGHT-CRM.md @@ -208,4 +208,175 @@ Alle user-facing Strings wurden umbenannt: --- +## 2026-03-10 | Backend: Neues Company-Modul + Aenderungen an Contact und Deal + +### Neue Entity: Company (Unternehmen) + +Unternehmen sind jetzt als eigenstaendige Entity implementiert. Sie dienen als uebergeordnete Ebene fuer Kontakte und Vorgaenge. Ein Unternehmen kann mehrere Kontakte und Vorgaenge haben. + +### Neue API-Endpoints + +| Methode | Pfad | Beschreibung | +|---------|------|-------------| +| GET | `/crm/companies` | Liste (paginiert, filterbar, suchbar) | +| POST | `/crm/companies` | Unternehmen erstellen | +| GET | `/crm/companies/:id` | Detail (inkl. Kontakte + Vorgaenge) | +| PATCH | `/crm/companies/:id` | Unternehmen aktualisieren | +| DELETE | `/crm/companies/:id` | Unternehmen loeschen | + +### Company-Objekt (Felder) + +```typescript +interface Company { + id: string; // UUID + tenantId: string; // UUID + name: string; // Pflichtfeld, max 200 + industry?: string; // max 100 + email?: string; // max 255 + phone?: string; // max 50 + website?: string; // max 500 + street?: string; // max 200 + zip?: string; // max 20 + city?: string; // max 100 + state?: string; // max 100 + country?: string; // Default "DE", 2-Zeichen ISO + notes?: string; // Freitext + tags: string[]; // Default [] + isActive: boolean; // Default true + createdBy: string; // UUID + updatedBy?: string; // UUID + createdAt: string; // ISO DateTime + updatedAt: string; // ISO DateTime + _count: { contacts: number; deals: number }; +} +``` + +### Company-Liste: Query-Parameter + +| Parameter | Typ | Beschreibung | +|-----------|-----|-------------| +| `page` | number | Seite (default: 1) | +| `pageSize` | number | Eintraege pro Seite (default: 25) | +| `search` | string | Substring-Match in `name`, `industry`, `email`, `city` | +| `industry` | string | Exakter Filter nach Branche | +| `sort` | string | `createdAt`, `updatedAt`, `name`, `industry`, `city` | +| `order` | string | `asc` oder `desc` (default: `desc`) | + +### Company-Detail: Verschachtelte Daten + +`GET /crm/companies/:id` liefert zusaetzlich: + +- `contacts[]` — Top 20 aktive Kontakte mit: `id`, `firstName`, `lastName`, `email`, `phone`, `position`, `isActive` +- `deals[]` — Top 10 Vorgaenge mit: alle Deal-Felder + `pipeline` + `stage` Objekte +- `_count` — Zaehler fuer `contacts` und `deals` + +Beispiel-Response (gekuerzt): +```json +{ + "data": { + "id": "...", + "name": "Xinion GmbH", + "industry": "Enterprise Software", + "contacts": [ + { + "id": "...", + "firstName": "Thomas", + "lastName": "Reitz", + "email": "treitz@xinion.de", + "position": "Geschaeftsfuehrer", + "isActive": true + } + ], + "deals": [ + { + "id": "...", + "title": "INSIGHT Platform Lizenz", + "value": "48000", + "status": "OPEN", + "pipeline": { "id": "...", "name": "Standard Sales" }, + "stage": { "id": "...", "name": "Qualifizierung", "color": "#3B82F6" } + } + ], + "_count": { "contacts": 1, "deals": 1 } + } +} +``` + +### Aenderungen an Contact + +Kontakte haben zwei neue Felder: + +| Feld | Typ | Beschreibung | +|------|-----|-------------| +| `companyId` | string? (UUID) | Verknuepfung zum Unternehmen (optional) | +| `position` | string? | Position/Rolle im Unternehmen (max 200) | + +**Contact-Liste** liefert jetzt zusaetzlich: +```json +{ + "company": { "id": "...", "name": "Xinion GmbH", "industry": "Enterprise Software" } +} +``` + +**Contact-Detail** liefert: +```json +{ + "company": { "id": "...", "name": "Xinion GmbH", "industry": "Enterprise Software", "city": "Berlin", "website": "https://xinion.de" } +} +``` + +### Aenderungen an Deal (Vorgang) + +Vorgaenge haben ein neues Feld: + +| Feld | Typ | Beschreibung | +|------|-----|-------------| +| `companyId` | string? (UUID) | Verknuepfung zum Unternehmen (optional) | + +**Deal-Liste und Detail** liefern jetzt zusaetzlich: +```json +{ + "company": { "id": "...", "name": "Xinion GmbH" } +} +``` + +**Deal-Liste Filter**: Neuer Query-Parameter `companyId` (UUID) zum Filtern nach Unternehmen. + +### Vorschlaege fuer das Frontend + +1. **Neue Seiten/Routen**: + - `/crm/companies` — Unternehmensliste (wie Kontakte, mit Suche/Filter/Paginierung) + - `/crm/companies/:id` — Unternehmensdetail (2-Spalten: Info links, Kontakte+Vorgaenge rechts) + +2. **Sidebar**: Neuer NavLink "Unternehmen" in der CRM-Sektion (zwischen Kontakte und Vorgaenge oder davor) + +3. **Contact-Formular**: `companyId` Dropdown (Unternehmen-Suche) + `position` Textfeld hinzufuegen + +4. **Contact-Liste**: Company-Name als Spalte anzeigen (kommt aus `contact.company.name`) + +5. **Deal-Formular**: `companyId` Dropdown (Unternehmen-Suche) hinzufuegen + +6. **Deal-Liste**: Company-Name als Spalte anzeigen + +7. **Verlinkung**: Company-Name in Contact- und Deal-Listen als Link zu `/crm/companies/:id` + +### Swagger-Aenderung + +Der Swagger-Tag fuer Deals/Vorgaenge ist jetzt `Vorgaenge (Deals)` statt `Deals`. + +### Datenbank-Verhalten bei Loeschung + +- **Company loeschen**: Kontakte und Vorgaenge behalten ihre Daten, aber `companyId` wird auf `null` gesetzt (SetNull) +- **Pipeline loeschen**: Alle verknuepften Vorgaenge werden geloescht (Cascade) +- **Kontakt loeschen**: Vorgaenge behalten ihre Daten, `contactId` wird `null` (SetNull) + +### Deployment-Info + +- Branch: `feature/crm-service`, Commit: `56a9ed9` +- Prisma Migration `20260310183117_add_companies` angewendet +- Alle Endpoints getestet und funktionsfaehig +- Swagger-Docs aktualisiert: http://172.20.10.59/api/v1/crm/docs/ + +--- + *Bitte neue Eintraege unten anfuegen. Format: `## YYYY-MM-DD | Absender: Betreff`* diff --git a/packages/crm-service/Summarize.md b/packages/crm-service/Summarize.md index 8c6f86e..1384607 100644 --- a/packages/crm-service/Summarize.md +++ b/packages/crm-service/Summarize.md @@ -25,24 +25,40 @@ packages/crm-service/ redis/ — RedisService (Token-Blocklist, Cache) auth/ — JWT Strategy (RS256), JwtAuthGuard, RolesGuard, TenantGuard common/ — Decorators (@Public, @Roles, @CurrentUser), Pagination, ExceptionFilter - contacts/ — CRUD: Kontakte (PERSON, ORGANIZATION) + companies/ — CRUD: Unternehmen (uebergeordnete Entity) + contacts/ — CRUD: Kontakte (PERSON, ORGANIZATION) mit Company-Verknuepfung activities/ — CRUD: Aktivitaeten (NOTE, CALL, EMAIL, MEETING, TASK) - pipelines/ — CRUD: Sales-Pipelines mit Stages - deals/ — CRUD: Deals mit Pipeline/Stage-Zuordnung + pipelines/ — CRUD: Sales-Pipelines mit Stages (inkl. Stage-Update) + deals/ — CRUD: Vorgaenge mit Pipeline/Stage/Contact/Company-Zuordnung ``` ### Datenbank-Modelle (app_crm Schema) -- **Contact** — Kontakte mit Typen, Adresse, Tags, Audit-Trail +- **Company** — Unternehmen mit Branche, Adresse, Tags, Audit-Trail. Eltern-Entity fuer Contacts und Deals. +- **Contact** — Kontakte (Person/Organisation) mit optionaler Company-Zuordnung (companyId, position) - **Activity** — Aktivitaeten verknuepft mit Kontakten - **Pipeline** — Konfigurierbare Sales-Pipelines pro Tenant -- **PipelineStage** — Stufen innerhalb einer Pipeline -- **Deal** — Verkaufschancen mit Wert, Status, Pipeline-Zuordnung +- **PipelineStage** — Stufen innerhalb einer Pipeline (Name, Farbe, Reihenfolge editierbar) +- **Deal** — Vorgaenge mit Wert, Status, Pipeline/Stage/Contact/Company-Zuordnung + +### Entity-Beziehungen + +``` +Company (1) --< (n) Contact — companyId (optional, SetNull bei Loeschung) +Company (1) --< (n) Deal — companyId (optional, SetNull bei Loeschung) +Contact (1) --< (n) Activity — contactId (Cascade bei Loeschung) +Contact (1) --< (n) Deal — contactId (optional, SetNull bei Loeschung) +Pipeline (1) --< (n) PipelineStage — pipelineId (Cascade bei Loeschung) +Pipeline (1) --< (n) Deal — pipelineId (Cascade bei Loeschung) +PipelineStage (1) --< (n) Deal — stageId +``` ### API-Endpunkte | Methode | Pfad | Beschreibung | |---------|------|-------------| +| GET/POST | /api/v1/crm/companies | Liste / Erstellen | +| GET/PATCH/DELETE | /api/v1/crm/companies/:id | Detail / Update / Delete | | GET/POST | /api/v1/crm/contacts | Liste / Erstellen | | GET/PATCH/DELETE | /api/v1/crm/contacts/:id | Detail / Update / Delete | | GET/POST | /api/v1/crm/activities | Liste / Erstellen | @@ -50,6 +66,7 @@ packages/crm-service/ | GET/POST | /api/v1/crm/pipelines | Liste / Erstellen | | GET/PATCH/DELETE | /api/v1/crm/pipelines/:id | Detail / Update / Delete | | POST/DELETE | /api/v1/crm/pipelines/:id/stages | Stage hinzufuegen/entfernen | +| PATCH | /api/v1/crm/pipelines/:id/stages/:stageId | Stage bearbeiten (Name, Farbe, Reihenfolge) | | GET/POST | /api/v1/crm/deals | Liste / Erstellen | | GET/PATCH/DELETE | /api/v1/crm/deals/:id | Detail / Update / Delete | | GET | /health | Health-Check (public) | @@ -59,7 +76,8 @@ packages/crm-service/ - `docker-compose.crm.yml` im Projekt-Root - Port: 3100 - Netzwerke: insight-web, insight-db, insight-cache -- Traefik-Route: `Host(172.20.10.59) && PathPrefix(/api/v1/crm)` mit Priority 100 +- Traefik HTTP-Route: `Host(172.20.10.59) && PathPrefix(/api/v1/crm)` mit Priority 100 +- Traefik HTTPS-Route: `crm-secure` mit `entrypoints=websecure`, `tls=true`, Priority 100 - JWT Public Key als Read-Only Volume (.keys/jwt-public.pem) - Direkte PostgreSQL-Verbindung (PgBouncer unterstuetzt kein search_path fuer Schema-Auswahl) @@ -78,37 +96,43 @@ packages/crm-service/ **Erfolgreich deployed auf insight-dev-01 (172.20.10.59) am 2026-03-10** - Container: insight-crm (Development-Mode) -- Prisma Migration `20260310163211_init` angewendet +- Prisma Migrationen angewendet: + - `20260310163211_init` — Initiales Schema (Contact, Activity, Pipeline, PipelineStage, Deal) + - `20260310183117_add_companies` — Company-Entity, Contact.companyId/position, Deal.companyId - Alle API-Endpunkte getestet und funktionsfaehig -- Traefik-Routing aktiv: http://172.20.10.59/api/v1/crm/* -- Swagger-Docs: nicht ueber Traefik erreichbar (nur Container-intern) +- Traefik-Routing aktiv (HTTP + HTTPS): http(s)://172.20.10.59/api/v1/crm/* +- Swagger-Docs: http://172.20.10.59/api/v1/crm/docs/ ### Getestete Endpunkte | Test | Ergebnis | |------|----------| -| GET /contacts (leere Liste) | 200 OK, pagination korrekt | -| POST /contacts (Kontakt erstellen) | 201 Created, UUID generiert | -| GET /contacts/:id | 200 OK, Detail korrekt | -| PATCH /contacts/:id | 200 OK, Update + Tags | -| GET /contacts?search=Muster | 200 OK, Suche funktioniert | +| POST /companies (Erstellen mit allen Feldern) | 200 OK, UUID + _count korrekt | +| GET /companies (Liste) | 200 OK, pagination + _count korrekt | +| GET /companies/:id (Detail) | 200 OK, contacts[] + deals[] + _count | +| PATCH /companies/:id (Update) | 200 OK, updatedBy + tags korrekt | +| GET /companies?search=Xinion | 200 OK, Suche funktioniert | +| POST /contacts (mit companyId + position) | 201 Created, Company-Verknuepfung korrekt | +| GET /contacts/:id (mit Company) | 200 OK, company-Objekt enthalten | | POST /activities (Notiz) | 201 Created, contactId verknuepft | | POST /pipelines (mit 4 Stages) | 201 Created, Stages korrekt | -| POST /deals | 201 Created, Pipeline/Stage/Contact verknuepft | +| PATCH /pipelines/:id/stages/:stageId | 200 OK, Stage-Update korrekt | +| POST /deals (mit companyId + contactId) | 200 OK, Company + Contact verknuepft | +| GET /deals/:id (mit Company) | 200 OK, company + pipeline.stages enthalten | +| GET /deals?companyId=... | 200 OK, Filter nach Company funktioniert | | PATCH /deals/:id (WON) | 200 OK, closedAt automatisch gesetzt | -| GET /deals (Liste) | 200 OK, pagination korrekt | | GET /contacts ohne Token | 401 Unauthorized | | Validierung (falsche Felder) | 400 Bad Request, Details korrekt | ### Bekannte Einschraenkungen - PgBouncer kann nicht genutzt werden (search_path nicht kompatibel mit transaction pooling) -- Swagger-Docs nur Container-intern erreichbar (kein Traefik-Route fuer /api/v1/crm/docs) ### Naechste Schritte -1. DELETE-Endpunkte testen (Kontakte, Deals, Pipelines) -2. Swagger-Docs ueber Traefik erreichbar machen (optional) -3. Integration mit Frontend (CRM-Modul im Admin-Bereich) -4. E2E-Tests schreiben -5. Production-Build testen (multi-stage Dockerfile) +1. Frontend: Company-Modul (Seiten, Formulare, Sidebar-Link) +2. Frontend: Contact/Deal-Formulare um Company-Selektor erweitern +3. Activity-Liste komplett laden (UI-Button "Alle anzeigen") +4. Kanban-Board fuer Vorgaenge (Drag & Drop Stage-Wechsel) +5. E2E-Tests schreiben +6. Production-Build testen (multi-stage Dockerfile)