mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 03:46:40 +02:00
Backend (CRM-Expert Phase 1): - New enums: ContactSource, EntityStatus, CompanySize, OwnerRole, LostReason, EmailType, PhoneType - Contact: add linkedinUrl, birthday, source, department, status - Company: add vatId, taxId, tradeRegisterNumber, registerCourt, companySize, deliveryAddress, dataEnrichedAt/Source, status - Deal: add lostReason + lostReasonText (required when status=LOST) - Multi-value emails/phones tables (contact_emails, contact_phones) - Owner m:n model (contact_owners, company_owners, deal_owners) - Redis Pub/Sub CRM events (crm.contact.created, crm.deal.won, etc.) - Activity due_soon scheduler (cron every 15 min) - SQL migration with data migration for existing records Frontend integration: - types.ts: all new enums, interfaces, label maps - api.ts: owner CRUD endpoints (add/remove for contacts/companies/deals) - hooks.ts: 6 new owner mutation hooks - ContactFormModal: LinkedIn, birthday, source, department, status fields - ContactDetailPage: display new fields (LinkedIn, department, birthday, source, status badge) - CompanyDetailPage: display vatId, taxId, trade register, company size, delivery address, data enrichment info - DealFormModal: lost reason dropdown + text (shown when status=LOST) - DealDetailPage: display lost reason with label - CompaniesPage: EntityStatus-aware status dots (ACTIVE/INACTIVE/BLOCKED) - ActivityType: add FOLLOWUP to all label maps Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1775 lines
76 KiB
Markdown
1775 lines
76 KiB
Markdown
# INSIGHT CRM - Kommunikation Frontend <-> Backend
|
||
|
||
Dieses Dokument dient als Kommunikationskanal zwischen dem Frontend- und dem CRM-Backend-Entwickler.
|
||
|
||
---
|
||
|
||
## 2026-03-10 | Frontend: Erster Stand der CRM-Integration
|
||
|
||
### Was wurde umgesetzt
|
||
|
||
Das komplette CRM-Frontend-Modul ist implementiert und auf dem Server deployed (`feature/crm-service` Branch, Commit `c739dce`).
|
||
|
||
#### Neue Dateien (16 Dateien, ~4.800 Zeilen)
|
||
|
||
```
|
||
packages/frontend/src/crm/
|
||
types.ts -- Zentrale Interfaces (alle Entitaeten + API-Wrapper)
|
||
api.ts -- API-Funktionen (Axios, baseURL /api/v1/crm/*)
|
||
hooks.ts -- React Query Hooks + Query Key Factory
|
||
contacts/
|
||
ContactsPage.tsx + .module.css -- Liste mit Suche, Typ-Filter, Paginierung
|
||
ContactFormModal.tsx -- Create/Edit Modal (Person/Organisation)
|
||
ContactDetailPage.tsx + .module.css -- 2-Spalten: Info+Deals links, Aktivitaeten rechts
|
||
deals/
|
||
DealsPage.tsx + .module.css -- Liste mit Pipeline/Stage/Status-Filter
|
||
DealFormModal.tsx -- Create/Edit mit Kontakt-Suche + Pipeline/Stage-Selektor
|
||
DealDetailPage.tsx + .module.css -- Detail mit Stage-Fortschrittsbalken
|
||
pipelines/
|
||
PipelinesPage.tsx + .module.css -- Verwaltung mit klappbaren Cards + Stage-Management
|
||
activities/
|
||
ActivityFormModal.tsx -- Formular fuer Notiz/Anruf/E-Mail/Meeting/Aufgabe
|
||
```
|
||
|
||
#### Geaenderte Dateien (3)
|
||
|
||
| Datei | Aenderung |
|
||
|-------|-----------|
|
||
| `src/shell/App.tsx` | 5 CRM-Routen (`/crm/contacts`, `/crm/contacts/:id`, `/crm/deals`, `/crm/deals/:id`, `/crm/pipelines`) |
|
||
| `src/shell/AppLayout.tsx` | CRM-Sektion in Sidebar (aufklappbar, 3 NavLinks mit SVG-Icons) |
|
||
| `vite.config.ts` | Proxy `/api/v1/crm` -> `localhost:3100` fuer lokale Entwicklung |
|
||
|
||
### Welche API-Endpoints werden genutzt
|
||
|
||
| Modul | Endpoints | Methoden |
|
||
|-------|-----------|----------|
|
||
| Contacts | `/crm/contacts`, `/crm/contacts/:id` | GET (list+detail), POST, PATCH, DELETE |
|
||
| Deals | `/crm/deals`, `/crm/deals/:id` | GET (list+detail), POST, PATCH, DELETE |
|
||
| Pipelines | `/crm/pipelines`, `/crm/pipelines/:id`, `/crm/pipelines/:id/stages`, `/crm/pipelines/:id/stages/:stageId` | GET (list+detail), POST, PATCH, DELETE |
|
||
| Activities | `/crm/activities`, `/crm/activities/:id` | GET (list), POST, PATCH, DELETE |
|
||
|
||
### Erwartete Response-Formate
|
||
|
||
**Liste (paginiert):**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [...],
|
||
"pagination": { "page": 1, "pageSize": 25, "total": 42, "totalPages": 2 },
|
||
"meta": { "timestamp": "..." }
|
||
}
|
||
```
|
||
|
||
**Einzelobjekt:**
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ... },
|
||
"meta": { "timestamp": "..." }
|
||
}
|
||
```
|
||
|
||
**Fehler:**
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": { "code": "NOT_FOUND", "message": "...", "details": [] },
|
||
"meta": { "timestamp": "..." }
|
||
}
|
||
```
|
||
|
||
### Annahmen / Abhaengigkeiten ans Backend
|
||
|
||
1. **Contact-Detail liefert Activities mit** -- `GET /crm/contacts/:id` gibt die letzten 10 Aktivitaeten im Feld `activities` zurueck. Das Frontend zeigt diese in der Timeline an.
|
||
|
||
2. **Deal-Detail liefert Relations mit** -- `GET /crm/deals/:id` gibt `pipeline`, `stage` und `contact` als verschachtelte Objekte zurueck.
|
||
|
||
3. **Pipeline-List liefert Stages mit** -- `GET /crm/pipelines` gibt jede Pipeline inkl. `stages[]` Array zurueck. Das Frontend nutzt diese fuer die Stage-Selektoren im Deal-Formular.
|
||
|
||
4. **Deal.value ist ein String** -- Decimal kommt als String vom Backend (z.B. `"24000.00"`). Das Frontend parst mit `parseFloat()`.
|
||
|
||
5. **Sortierung** -- Contacts: `createdAt`, `firstName`, `lastName`, `companyName`, `email`. Deals: `createdAt`, `title`, `value`, `expectedCloseDate`.
|
||
|
||
6. **Suche** -- Contacts: Substring-Match in `firstName`, `lastName`, `companyName`, `email`. Deals: Substring-Match in `title`.
|
||
|
||
### Bekannte Offene Punkte
|
||
|
||
- [ ] **Traefik HTTPS-Router fuer CRM**: Aktuell hat der CRM-Service nur einen HTTP-Router (`web` Entrypoint). Fuer HTTPS (`websecure`) muesste ein zweiter Router mit `tls=true` angelegt werden (wie bei `core-api-secure`).
|
||
- [ ] **Activity-Liste komplett laden**: Die Contact-Detail-Seite zeigt nur die letzten 10 Aktivitaeten (aus dem Contact-Objekt). Fuer eine "Alle anzeigen"-Funktion wuerde ein separater `GET /crm/activities?contactId=...` Aufruf benoetigt (Hook existiert bereits).
|
||
- [ ] **Kanban-Board fuer Deals**: Aktuell nur Tabellenansicht. Ein Drag-and-Drop Kanban-Board waere ein sinnvolles Feature fuer spaeter.
|
||
- [ ] **Pipeline-Stages bearbeiten**: Derzeit kann man Stages nur hinzufuegen und loeschen, nicht den Namen/Farbe/Reihenfolge aendern (kein PATCH-Endpoint fuer Stages vorhanden).
|
||
|
||
---
|
||
|
||
## 2026-03-10 | Backend: Antwort auf Frontend-Integrationsbericht
|
||
|
||
### Status der Annahmen
|
||
|
||
Alle 6 Annahmen des Frontend-Entwicklers sind korrekt und werden vom Backend unterstuetzt:
|
||
|
||
| # | Annahme | Status | Hinweise |
|
||
|---|---------|--------|----------|
|
||
| 1 | Contact-Detail liefert Activities mit | **Bestaetigt** | `GET /crm/contacts/:id` liefert `activities[]` (letzte 10, sortiert nach `createdAt desc`) |
|
||
| 2 | Deal-Detail liefert Relations mit | **Bestaetigt** | `GET /crm/deals/:id` liefert `pipeline` (inkl. aller Stages!), `stage`, `contact` |
|
||
| 3 | Pipeline-List liefert Stages mit | **Bestaetigt** | `GET /crm/pipelines` liefert jede Pipeline inkl. `stages[]` + `_count.deals` |
|
||
| 4 | Deal.value ist ein String | **Bestaetigt** | Decimal wird als String geliefert (z.B. `"24000"`) |
|
||
| 5 | Sortierung | **Bestaetigt** | Contacts: `createdAt`, `firstName`, `lastName`, `companyName`, `email`. Deals: `createdAt`, `updatedAt`, `title`, `value`, `expectedCloseDate` |
|
||
| 6 | Suche | **Bestaetigt** | Contacts: case-insensitive Substring in `firstName`, `lastName`, `companyName`, `email`. Deals: case-insensitive Substring in `title` |
|
||
|
||
### Erledigte Offene Punkte
|
||
|
||
- [x] **Traefik HTTPS-Router**: `crm-secure` Router angelegt mit `entrypoints=websecure`, `tls=true`, Priority 100. Deployed in Commit `c9e2c4a`.
|
||
|
||
- [x] **Pipeline-Stages bearbeiten**: Neuer Endpoint `PATCH /crm/pipelines/:id/stages/:stageId` hinzugefuegt. Akzeptiert:
|
||
```json
|
||
{
|
||
"name": "Neuer Name", // optional, max 200
|
||
"sortOrder": 2, // optional, int >= 0
|
||
"color": "#EF4444" // optional, Hex #RRGGBB
|
||
}
|
||
```
|
||
|
||
### Noch offene Punkte (Backend-Sicht)
|
||
|
||
- [ ] **Activity-Liste komplett laden**: `GET /crm/activities?contactId=...` ist bereits implementiert und funktioniert. Das Frontend kann den bestehenden Hook direkt nutzen.
|
||
- [ ] **Kanban-Board**: Backend-seitig kein Handlungsbedarf - `PATCH /crm/deals/:id` mit `{ stageId: "..." }` genuegt fuer Drag & Drop.
|
||
|
||
### Hinweis zu Pipeline-Detail bei Deals
|
||
|
||
`GET /crm/deals/:id` liefert die Pipeline **inklusive aller Stages** (nicht nur die aktuelle Stage). Das ist nuetzlich fuer den Stage-Fortschrittsbalken im `DealDetailPage.tsx`:
|
||
|
||
```json
|
||
{
|
||
"pipeline": {
|
||
"id": "...",
|
||
"name": "Standard Sales",
|
||
"stages": [
|
||
{ "id": "...", "name": "Qualifizierung", "sortOrder": 0, "color": "#3B82F6" },
|
||
{ "id": "...", "name": "Angebot", "sortOrder": 1, "color": "#F59E0B" },
|
||
{ "id": "...", "name": "Verhandlung", "sortOrder": 2, "color": "#EF4444" },
|
||
{ "id": "...", "name": "Abschluss", "sortOrder": 3, "color": "#10B981" }
|
||
]
|
||
},
|
||
"stage": { "id": "...", "name": "Angebot", "color": "#F59E0B" }
|
||
}
|
||
```
|
||
|
||
### Deployment-Info
|
||
|
||
- Branch: `feature/crm-service`, Commit: `c9e2c4a`
|
||
- Server: insight-dev-01 (172.20.10.59)
|
||
- Container: `insight-crm` (neu gebuildet und deployed)
|
||
- Swagger-Docs: http://172.20.10.59/api/v1/crm/docs/
|
||
|
||
---
|
||
|
||
## 2026-03-10 | Frontend: Update nach Backend-Feedback
|
||
|
||
### Umgesetzte Verbesserungen
|
||
|
||
Basierend auf der Backend-Antwort wurden folgende Aenderungen umgesetzt (Commit `0b78160`):
|
||
|
||
#### 1. Pipeline-Stages inline bearbeitbar
|
||
|
||
Die `PipelinesPage.tsx` nutzt jetzt den neuen `PATCH /crm/pipelines/:id/stages/:stageId` Endpoint:
|
||
|
||
- Jede Stage kann per **Doppelklick** oder **Stift-Icon** bearbeitet werden
|
||
- Inline-Formular mit Name-Input und Color-Picker
|
||
- Speichern mit Enter oder Haekchen, Abbrechen mit Escape oder X
|
||
- Neuer Hook: `useUpdateStage()` mit automatischer Query-Invalidierung
|
||
|
||
#### 2. DealDetailPage optimiert
|
||
|
||
`DealDetailPage.tsx` nutzt jetzt **direkt `deal.pipeline.stages`** aus dem Deal-Objekt fuer den Stage-Fortschrittsbalken. Der vorherige separate `usePipeline()` API-Call wurde entfernt.
|
||
|
||
#### 3. UI-Umbenennung: "Deals" -> "Vorgaenge"
|
||
|
||
Alle user-facing Strings wurden umbenannt:
|
||
- Sidebar: "Deals" -> "Vorgaenge"
|
||
- Seitentitel: "Deals" -> "Vorgaenge"
|
||
- Buttons: "Neuer Deal" -> "Neuer Vorgang"
|
||
- Modals: "Deal bearbeiten/loeschen" -> "Vorgang bearbeiten/loeschen"
|
||
- Fehlermeldungen und Leer-Zustaende angepasst
|
||
|
||
**Hinweis**: API-Pfade (`/crm/deals`), TypeScript-Typen (`Deal`, `DealStatus`) und Komponentennamen (`DealsPage`, `DealFormModal`) bleiben unveraendert — nur die UI-Texte wurden geaendert.
|
||
|
||
### Aktualisierte Offene Punkte
|
||
|
||
- [x] ~~Pipeline-Stages bearbeiten~~ — Frontend nutzt den neuen PATCH-Endpoint
|
||
- [x] ~~DealDetail separater Pipeline-Call~~ — Nutzt jetzt deal.pipeline.stages
|
||
- [ ] **Activity-Liste komplett laden** — Hook existiert, UI-Button "Alle anzeigen" fehlt noch
|
||
- [ ] **Kanban-Board fuer Vorgaenge** — Feature fuer spaeter geplant
|
||
|
||
### Deployment-Info
|
||
|
||
- Branch: `feature/crm-service`, Commit: `0b78160`
|
||
- Server: insight-dev-01 (172.20.10.59)
|
||
- Container: `insight-frontend` neu gebaut und deployed
|
||
|
||
---
|
||
|
||
## 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/
|
||
|
||
---
|
||
|
||
## 2026-03-10 | Frontend: Company-Modul implementiert
|
||
|
||
### Was wurde umgesetzt
|
||
|
||
Das komplette Company-Frontend-Modul ist implementiert und deployed (Commit `36f571f`).
|
||
|
||
#### Neue Dateien (5 Dateien)
|
||
|
||
```
|
||
packages/frontend/src/crm/companies/
|
||
CompaniesPage.tsx + .module.css -- Liste mit Suche, Paginierung, CRUD-Modals
|
||
CompanyFormModal.tsx -- Create/Edit Modal (Name, Branche, Kontakt, Adresse, Tags)
|
||
CompanyDetailPage.tsx + .module.css -- 2-Spalten: Info links, Kontakte+Vorgaenge rechts
|
||
```
|
||
|
||
#### Geaenderte Dateien (11 Dateien)
|
||
|
||
- **types.ts**: Company-Interface, CreateCompanyPayload, UpdateCompanyPayload, CompaniesQueryParams; Contact erweitert um `companyId`, `position`, `company`-Relation; Deal erweitert um `companyId`, `company`-Relation
|
||
- **api.ts**: `companiesApi` mit 5 CRUD-Methoden
|
||
- **hooks.ts**: `crmKeys.companies` + 5 Hooks (useCompanies, useCompany, useCreateCompany, useUpdateCompany, useDeleteCompany); Cross-Invalidation (Contact/Deal-Mutations invalidieren Companies-Cache)
|
||
- **AppLayout.tsx**: NavLink "Unternehmen" mit Gebaeude-Icon zwischen Kontakte und Vorgaenge
|
||
- **App.tsx**: 2 Routen (`/crm/companies`, `/crm/companies/:id`)
|
||
- **ContactsPage.tsx**: Neue Spalte "Unternehmen" mit Link zu Company
|
||
- **ContactFormModal.tsx**: Unternehmen-Suche (debounced Dropdown) + Position-Feld
|
||
- **ContactDetailPage.tsx**: Unternehmen-Link + Position in Info-Card
|
||
- **DealsPage.tsx**: Neue Spalte "Unternehmen" mit Link zu Company
|
||
- **DealFormModal.tsx**: Unternehmen-Suche (debounced Dropdown)
|
||
- **DealDetailPage.tsx**: Unternehmen-Link in Info-Card
|
||
|
||
### Funktionsumfang Company-Modul
|
||
|
||
1. **CompaniesPage**: Tabelle mit Name, Branche, Stadt, E-Mail, Kontakte-Anzahl, Vorgaenge-Anzahl, Status, Aktionen; Suchfeld (debounced 300ms); Paginierung; Erstellen/Bearbeiten/Loeschen-Modals
|
||
2. **CompanyFormModal**: Name*, Branche, E-Mail, Telefon, Website, Adresse (Strasse, PLZ/Stadt, Land), Notizen, Tags (kommasepariert), Aktiv-Checkbox
|
||
3. **CompanyDetailPage**: Links Info-Card (alle Felder + Tags + Notizen), Rechts Kontakte-Tabelle + Vorgaenge-Tabelle mit Navigation zu Detail-Seiten
|
||
|
||
### Company-Integration in bestehende Module
|
||
|
||
- **Kontakte**: Unternehmen-Spalte in Liste, Dropdown-Suche im Formular, Link+Position im Detail
|
||
- **Vorgaenge**: Unternehmen-Spalte in Liste, Dropdown-Suche im Formular, Link im Detail
|
||
- Pattern: Identisch zur Kontakt-Suche in DealFormModal (debounced, dropdown, click-outside)
|
||
|
||
### Deployment
|
||
|
||
- Branch: `feature/crm-service`, Commit: `36f571f`
|
||
- TypeScript-Check + Build: erfolgreich
|
||
- Frontend Container neu gebaut und deployed
|
||
|
||
---
|
||
|
||
## 2026-03-10 | Backend: Lexware Office Integration
|
||
|
||
### Ueberblick
|
||
|
||
Der CRM-Service ist jetzt mit Lexware Office (Buchhaltung/ERP) integriert. Drei Hauptfunktionen:
|
||
|
||
1. **Kontakt-Verknuepfung**: Lexware-Kontakte suchen und mit CRM Companies/Contacts verknuepfen oder importieren
|
||
2. **Beleg-Anzeige**: Angebote, Auftragsbestaetigungen, Rechnungen und Gutschriften aus Lexware — anzeigbar am Unternehmen, Kontakt UND am Vorgang
|
||
3. **ERP-Push**: CRM-Entitaeten mit Tag "ERP" werden automatisch nach Lexware synchronisiert
|
||
|
||
### Neue API-Endpoints: Lexware Kontakte
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/lexware/contacts/search?name=&email=` | Lexware-Kontakte suchen (Proxy zur Lexware API) |
|
||
| POST | `/crm/lexware/contacts/link-company` | Lexware-Kontakt mit CRM Company verknuepfen |
|
||
| POST | `/crm/lexware/contacts/link-contact` | Lexware-Kontakt mit CRM Contact verknuepfen |
|
||
| DELETE | `/crm/lexware/contacts/unlink-company/:companyId` | Verknuepfung Company <-> Lexware loesen |
|
||
| DELETE | `/crm/lexware/contacts/unlink-contact/:contactId` | Verknuepfung Contact <-> Lexware loesen |
|
||
| POST | `/crm/lexware/contacts/import-company` | Neue CRM Company aus Lexware-Daten erstellen |
|
||
| POST | `/crm/lexware/contacts/import-contact` | Neuen CRM Contact aus Lexware-Daten erstellen |
|
||
| POST | `/crm/lexware/contacts/push/:entityType/:entityId` | CRM-Entitaet nach Lexware pushen (company/contact) |
|
||
| POST | `/crm/lexware/contacts/sync/:entityType/:entityId` | Lexware-Daten in CRM aktualisieren |
|
||
|
||
### Neue API-Endpoints: Lexware Belege (Vouchers)
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/lexware/vouchers/company/:companyId` | Belege fuer Unternehmen (gecacht) |
|
||
| GET | `/crm/lexware/vouchers/contact/:contactId` | Belege fuer Kontakt (gecacht) |
|
||
| GET | `/crm/lexware/vouchers/deal/:dealId` | Belege fuer Vorgang (via DealVoucher) |
|
||
| POST | `/crm/lexware/vouchers/deal/:dealId/link` | Beleg mit Vorgang verknuepfen |
|
||
| DELETE | `/crm/lexware/vouchers/deal/:dealId/unlink/:voucherId` | Beleg-Verknuepfung loesen |
|
||
| POST | `/crm/lexware/vouchers/refresh/company/:companyId` | Beleg-Cache manuell aktualisieren |
|
||
| POST | `/crm/lexware/vouchers/refresh/contact/:contactId` | Beleg-Cache manuell aktualisieren |
|
||
|
||
### Beleg-Filter Query-Parameter
|
||
|
||
| Parameter | Typ | Beschreibung |
|
||
|-----------|-----|-------------|
|
||
| `voucherType` | string | `QUOTATION`, `ORDER_CONFIRMATION`, `INVOICE`, `CREDIT_NOTE` |
|
||
| `voucherStatus` | string | Freitext-Filter nach Beleg-Status |
|
||
| `page` | number | Seite (default: 1) |
|
||
| `pageSize` | number | Eintraege pro Seite (default: 20) |
|
||
|
||
### LexwareVoucher-Objekt
|
||
|
||
```typescript
|
||
interface LexwareVoucher {
|
||
id: string; // CRM-interne UUID
|
||
voucherType: 'QUOTATION' | 'ORDER_CONFIRMATION' | 'INVOICE' | 'CREDIT_NOTE';
|
||
voucherNumber?: string; // z.B. "RE-2025-001"
|
||
voucherDate?: string; // ISO DateTime
|
||
voucherStatus?: string; // z.B. "open", "paid", "overdue"
|
||
totalGrossAmount?: string; // Decimal als String, z.B. "1190.00"
|
||
totalNetAmount?: string;
|
||
totalTaxAmount?: string;
|
||
currency: string; // Default "EUR"
|
||
title?: string;
|
||
lineItemsCount?: number;
|
||
lineItemsJson?: string; // JSON-String mit Positionen
|
||
lexwareDeepLink?: string; // Link direkt zu Lexware Office
|
||
fetchedAt: string; // Wann zuletzt aus Lexware geholt
|
||
}
|
||
```
|
||
|
||
### Aenderungen an bestehenden Responses
|
||
|
||
**Company-Objekt** hat jetzt zusaetzliche Felder:
|
||
```json
|
||
{
|
||
"lexwareContactId": "abc-123", // null wenn nicht verknuepft
|
||
"lexwareContactVersion": 3, // Optimistic Locking
|
||
"lexwareSyncedAt": "2026-03-10...", // Letzter Sync
|
||
"_count": { "contacts": 5, "deals": 2, "lexwareVouchers": 12 }
|
||
}
|
||
```
|
||
|
||
**Contact-Objekt**: Identische neue Felder wie Company.
|
||
|
||
**Deal-Detail** liefert jetzt zusaetzlich `dealVouchers[]`:
|
||
```json
|
||
{
|
||
"dealVouchers": [
|
||
{
|
||
"id": "...",
|
||
"linkedAt": "2026-03-10...",
|
||
"voucher": {
|
||
"id": "...",
|
||
"voucherType": "INVOICE",
|
||
"voucherNumber": "RE-2025-001",
|
||
"voucherDate": "2025-12-15...",
|
||
"voucherStatus": "paid",
|
||
"totalGrossAmount": "1190.00",
|
||
"currency": "EUR",
|
||
"title": "Lizenzgebuehr Q4",
|
||
"lexwareDeepLink": "https://app.lexware.de/permalink/..."
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Vorschlaege fuer das Frontend
|
||
|
||
1. **Company/Contact-Detail**: Tab oder Sektion "Lexware" mit:
|
||
- Status-Badge: "Verknuepft" (gruen) / "Nicht verknuepft" (grau)
|
||
- Button "Lexware-Kontakt suchen & verknuepfen" → Modal mit Suchfeld
|
||
- Button "Verknuepfung loesen"
|
||
- Button "Belege aktualisieren" (Refresh-Icon)
|
||
- Beleg-Tabelle: Typ, Nummer, Datum, Status, Brutto-Betrag, Link zu Lexware
|
||
|
||
2. **Deal-Detail**: Sektion "Belege" mit:
|
||
- Verknuepfte Belege als Tabelle (aus `dealVouchers`)
|
||
- Button "Beleg verknuepfen" → Dropdown/Modal mit verfuegbaren Belegen des Unternehmens/Kontakts
|
||
- Jeder Beleg hat einen externen Link zu Lexware Office
|
||
|
||
3. **Tags-Integration**: "ERP"-Tag in Company/Contact-Formularen hervorheben (z.B. besondere Farbe), da es den automatischen Push nach Lexware aktiviert
|
||
|
||
4. **VoucherType Labels** fuer die UI:
|
||
- `QUOTATION` → "Angebot"
|
||
- `ORDER_CONFIRMATION` → "Auftragsbestaetigung"
|
||
- `INVOICE` → "Rechnung"
|
||
- `CREDIT_NOTE` → "Gutschrift"
|
||
|
||
### Cron-Jobs (automatisch im Hintergrund)
|
||
|
||
- **Beleg-Sync**: Alle 4 Stunden werden Belege fuer alle verknuepften Entitaeten aus Lexware geholt
|
||
- **ERP-Push**: Alle 30 Minuten werden Companies/Contacts mit "ERP"-Tag nach Lexware gepusht
|
||
|
||
### Health Check
|
||
|
||
`GET /health` zeigt jetzt `"lexware": "up"|"down"|"unconfigured"`:
|
||
- `up`: Lexware API erreichbar
|
||
- `down`: API-Key konfiguriert aber API nicht erreichbar
|
||
- `unconfigured`: Kein API-Key gesetzt (kein Fehler, Modul einfach deaktiviert)
|
||
|
||
### Deployment-Hinweise
|
||
|
||
- Neue Env-Variable auf Server: `LEXWARE_API_KEY` (in `.env`)
|
||
- DB-Migration noetig: `migration.sql` in `prisma/migrations/20260310_add_lexware_integration/`
|
||
- Neue Tabellen: `lexware_vouchers`, `deal_vouchers`
|
||
- Neue Felder: `lexware_contact_id`, `lexware_contact_version`, `lexware_synced_at` auf `companies` und `contacts`
|
||
|
||
---
|
||
|
||
## 2026-03-10 | Frontend: Lexware Office Integration UI implementiert
|
||
|
||
**Commit:** `2381409` (feature/crm-service)
|
||
**Status:** Deployed auf insight-dev-01
|
||
|
||
### Was wurde gemacht?
|
||
|
||
Komplette Frontend-Integration fuer Lexware Office, basierend auf den neuen Backend-Endpunkten.
|
||
|
||
### Neue Dateien
|
||
|
||
| Datei | Beschreibung |
|
||
|-------|--------------|
|
||
| `src/crm/lexware/LexwareSection.tsx` | Wiederverwendbare Komponente fuer Company/Contact-Detailseiten |
|
||
| `src/crm/lexware/LexwareSection.module.css` | Styles (Badges, Voucher-Tabelle, Status-Farben, Dark Mode) |
|
||
| `src/crm/lexware/LexwareSearchModal.tsx` | Such-Modal: Lexware-Kontakte finden & verknuepfen |
|
||
| `src/crm/lexware/DealVouchersSection.tsx` | Belege-Sektion auf Deal-Detailseite mit Link/Unlink |
|
||
|
||
### Geaenderte Dateien
|
||
|
||
| Datei | Aenderung |
|
||
|-------|-----------|
|
||
| `src/crm/types.ts` | Neue Types: `LexwareVoucher`, `DealVoucher`, `LexwareContact`, `VoucherType`, `VOUCHER_TYPE_LABELS`; Erweitert: Company + Contact um `lexwareContactId/Version/SyncedAt`; Deal um `dealVouchers` |
|
||
| `src/crm/api.ts` | `lexwareContactsApi` (9 Methoden) + `lexwareVouchersApi` (7 Methoden) |
|
||
| `src/crm/hooks.ts` | 13 neue React Query Hooks (Query Keys, Queries, Mutations) |
|
||
| `src/crm/settings/CrmSettingsContext.tsx` | Neuer Module-Key `lexware` (Default: enabled) |
|
||
| `src/crm/settings/CrmSettingsPage.tsx` | Toggle fuer "Lexware Office" in CRM-Einstellungen |
|
||
| `src/crm/companies/CompanyDetailPage.tsx` | LexwareSection in rechter Spalte (unter Vorgaenge) |
|
||
| `src/crm/contacts/ContactDetailPage.tsx` | LexwareSection in linker Spalte (unter Vorgaenge) |
|
||
| `src/crm/deals/DealDetailPage.tsx` | DealVouchersSection nach Info-Card |
|
||
|
||
### Features
|
||
|
||
1. **Company/Contact Detail**: Lexware-Card mit Status-Badge (Verknuepft/Nicht verknuepft), Such-Button, Sync/Push/Refresh-Buttons, Voucher-Tabelle mit Typ-Filter
|
||
2. **Deal Detail**: Belege-Card mit verknuepften Vouchers, Link/Unlink-Funktion, Zugriff auf Company/Contact-Vouchers
|
||
3. **Lexware Search Modal**: Debounced Suche (400ms), Anzeige von Name/Email/Adresse, Ein-Klick-Verknuepfung
|
||
4. **CRM Settings**: Lexware-Toggle zum Ein-/Ausblenden aller Lexware-Sektionen
|
||
5. **Voucher-Tabelle**: Typ-Badges (farbig pro VoucherType), Status-Highlighting, Waehrungs-Formatierung, Deep-Link zu Lexware
|
||
|
||
### Hinweise fuer Backend
|
||
|
||
- Alle 16 Endpunkte sind im Frontend verdrahtet
|
||
- Bei `lexwareContactId === null` zeigt die UI den "Suchen & Verknuepfen"-Button
|
||
- Vouchers werden erst geladen wenn Entity verknuepft ist
|
||
- `VOUCHER_TYPE_LABELS`: QUOTATION=Angebot, ORDER_CONFIRMATION=Auftragsbestaetigung, INVOICE=Rechnung, CREDIT_NOTE=Gutschrift
|
||
- Die CRM-Einstellung "Lexware Office" kann die gesamte Integration per Toggle ausblenden
|
||
|
||
---
|
||
|
||
## 2026-03-11 | Backend: Company Detail Overhaul — Neue Entitaeten und Endpoints
|
||
|
||
### Ueberblick
|
||
|
||
Die Company-Entity wurde massiv erweitert: Branchen, Kontotypen und Beziehungstypen sind jetzt als admin-konfigurierbare Entitaeten implementiert (statt Freitext). Dazu kommen N:M-Unternehmensbeziehungen, ein Vertraege-Modell (DB-ready), und Activities koennen jetzt direkt an Companies gehaengt werden.
|
||
|
||
### Neue Prisma-Models
|
||
|
||
| Model | Tabelle | Beschreibung |
|
||
|-------|---------|-------------|
|
||
| `Industry` | `industries` | Admin-konfigurierbare Branchen mit Farbe (unique pro Tenant) |
|
||
| `AccountType` | `account_types` | Admin-konfigurierbare Kontotypen (unique pro Tenant) |
|
||
| `RelationshipType` | `relationship_types` | Admin-konfigurierbare Beziehungstypen (unique pro Tenant) |
|
||
| `CompanyRelationship` | `company_relationships` | N:M Company-zu-Company Beziehungen mit Typ und Notizen |
|
||
| `Contract` | `contracts` | Vertraege (DB-Modell vorhanden, UI-Platzhalter) |
|
||
|
||
### Aenderungen an Company
|
||
|
||
Neue Felder auf dem Company-Objekt:
|
||
|
||
```typescript
|
||
{
|
||
industryId?: string; // UUID -> Industry
|
||
accountTypeId?: string; // UUID -> AccountType
|
||
ownerId?: string; // UUID (Referenz auf core User, kein FK)
|
||
ownerName?: string; // Denormalisiert, z.B. "Thomas Reitz"
|
||
industryRef?: { // Verschachtelt bei GET /companies/:id
|
||
id: string;
|
||
name: string;
|
||
color: string; // Hex, z.B. "#3B82F6"
|
||
};
|
||
accountType?: { // Verschachtelt bei GET /companies/:id
|
||
id: string;
|
||
name: string;
|
||
};
|
||
relationships?: CompanyRelationship[]; // Bei GET /companies/:id
|
||
contracts?: Contract[]; // Bei GET /companies/:id
|
||
}
|
||
```
|
||
|
||
**Wichtig**: Das alte `industry`-Freitext-Feld bleibt vorlaeufig bestehen. Die Migration hat bestehende Werte in Industry-Records konvertiert und `industryId` gesetzt.
|
||
|
||
### Aenderungen an Activity
|
||
|
||
`contactId` ist jetzt **optional** (war vorher required). Neues Feld `companyId` (optional). Mindestens eines von beiden muss gesetzt sein.
|
||
|
||
```typescript
|
||
{
|
||
contactId?: string; // UUID, optional
|
||
companyId?: string; // UUID, NEU, optional
|
||
company?: { id: string; name: string }; // Verschachtelt
|
||
}
|
||
```
|
||
|
||
**Neuer Query-Parameter fuer aggregierten Feed:**
|
||
```
|
||
GET /crm/activities?companyId=X&includeContacts=true
|
||
```
|
||
Liefert alle Aktivitaeten die direkt an der Company haengen PLUS alle Aktivitaeten der verknuepften Kontakte. Kontakt-Aktivitaeten haben `contact`-Objekt im Response.
|
||
|
||
### Neue API-Endpoints: Industries (Branchen)
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/industries` | Liste aller Branchen (sortiert nach sortOrder) |
|
||
| POST | `/crm/industries` | Branche erstellen (`name`*, `color?`, `sortOrder?`) |
|
||
| PATCH | `/crm/industries/:id` | Branche bearbeiten |
|
||
| DELETE | `/crm/industries/:id` | Branche loeschen (Schutz bei Referenzen) |
|
||
|
||
### Neue API-Endpoints: AccountTypes (Kontotypen)
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/account-types` | Liste aller Kontotypen |
|
||
| POST | `/crm/account-types` | Kontotyp erstellen (`name`*, `sortOrder?`) |
|
||
| PATCH | `/crm/account-types/:id` | Kontotyp bearbeiten |
|
||
| DELETE | `/crm/account-types/:id` | Kontotyp loeschen |
|
||
|
||
### Neue API-Endpoints: RelationshipTypes (Beziehungstypen)
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/relationship-types` | Liste aller Beziehungstypen |
|
||
| POST | `/crm/relationship-types` | Beziehungstyp erstellen (`name`*, `sortOrder?`) |
|
||
| PATCH | `/crm/relationship-types/:id` | Beziehungstyp bearbeiten |
|
||
| DELETE | `/crm/relationship-types/:id` | Beziehungstyp loeschen |
|
||
|
||
### Neue API-Endpoints: Company Relationships (Unternehmensbeziehungen)
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/companies/:id/relationships` | Beziehungen eines Unternehmens (bidirektional) |
|
||
| POST | `/crm/companies/:id/relationships` | Beziehung erstellen |
|
||
| DELETE | `/crm/companies/:id/relationships/:relId` | Beziehung loeschen |
|
||
|
||
**POST-Body:**
|
||
```json
|
||
{
|
||
"relatedCompanyId": "uuid",
|
||
"relationshipTypeId": "uuid",
|
||
"notes": "optional"
|
||
}
|
||
```
|
||
|
||
**GET-Response (einzelne Beziehung):**
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"relatedCompany": { "id": "uuid", "name": "Firma XY" },
|
||
"relationshipType": { "id": "uuid", "name": "Endkunde" },
|
||
"direction": "outgoing",
|
||
"notes": "..."
|
||
}
|
||
```
|
||
`direction` ist `outgoing` wenn die Company der Ersteller ist, `incoming` wenn sie die Ziel-Company ist.
|
||
|
||
### Neuer Endpoint: Tenant-User (fuer Owner-Dropdown)
|
||
|
||
| Methode | Pfad | Beschreibung |
|
||
|---------|------|-------------|
|
||
| GET | `/crm/users` | Liste der Tenant-Benutzer |
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"data": [
|
||
{ "id": "uuid", "firstName": "Thomas", "lastName": "Reitz", "email": "treitz@xinion.de" }
|
||
]
|
||
}
|
||
```
|
||
|
||
### Seed-Daten (Standard-Konfiguration)
|
||
|
||
**Industries (8):** IT & Software (#3B82F6), Produktion (#F59E0B), Handel (#10B981), Dienstleistung (#8B5CF6), Gesundheit (#EF4444), Finanzen (#6366F1), Bildung (#EC4899), Oeffentlicher Sektor (#6B7280)
|
||
|
||
**AccountTypes (4):** Interessent, Endkunde, Personaldienstleister, Partner
|
||
|
||
**RelationshipTypes (4):** Endkunde, Abrechnungspartner, Muttergesellschaft, Tochtergesellschaft
|
||
|
||
### Deployment-Info
|
||
|
||
- Branch: `feature/crm-service`
|
||
- Prisma Migration: `20260311_add_company_detail_overhaul`
|
||
- Neue Module in app.module.ts: IndustriesModule, AccountTypesModule, RelationshipTypesModule, CompanyRelationshipsModule
|
||
- Seed-Data muss nach Migration ausgefuehrt werden
|
||
|
||
---
|
||
|
||
## 2026-03-11 | Frontend: Company Detail Page Overhaul — 3-Spalten-Layout
|
||
|
||
### Was wurde umgesetzt
|
||
|
||
Kompletter Umbau der CompanyDetailPage von 2-Spalten auf 3-Spalten-Layout. Dazu CRM-Einstellungen mit Admin-Konfiguration fuer Branchen, Kontotypen und Beziehungstypen.
|
||
|
||
### Neue Dateien
|
||
|
||
```
|
||
packages/frontend/src/crm/companies/
|
||
ActivityFeed.tsx -- Aggregierter Activity Feed (mittlere Spalte)
|
||
CompanyRelationshipsCard.tsx -- N:M Unternehmensbeziehungen Card
|
||
ContractsCard.tsx -- Platzhalter "Modul in Entwicklung"
|
||
```
|
||
|
||
### Geaenderte Dateien
|
||
|
||
| Datei | Aenderung |
|
||
|-------|-----------|
|
||
| `types.ts` | Neue Interfaces: Industry, AccountType, RelationshipType, CompanyRelationship, Contract, TenantUser; Company erweitert um industryId/Ref, accountTypeId/accountType, ownerId/ownerName; Activity erweitert um companyId |
|
||
| `api.ts` | Neue API-Objekte: industriesApi, accountTypesApi, relationshipTypesApi, companyRelationshipsApi, usersApi; activitiesApi erweitert um getByCompany() |
|
||
| `hooks.ts` | Neue Hooks: useIndustries, useAccountTypes, useRelationshipTypes, useCompanyRelationships, useCompanyActivities, useTenantUsers + jeweilige CRUD-Mutations |
|
||
| `CompanyDetailPage.tsx` | Kompletter Umbau: 3-Spalten-Layout (Stammdaten / Activity Feed / Relations) |
|
||
| `CompanyDetailPage.module.css` | Neues Grid: 300px / 1fr / 360px, responsive Breakpoints (1200px, 768px), Feed-Styles, Relation-Styles |
|
||
| `CompanyFormModal.tsx` | Dropdowns statt Freitext: Branche (useIndustries), Kontotyp (useAccountTypes), Zustaendigkeit (useTenantUsers) |
|
||
| `ActivityFormModal.tsx` | contactId jetzt optional, neues Prop companyId |
|
||
| `DealsPage.tsx` | Spacing Fix: minWidth fuer Stage (120px) und Wert (100px) Spalten |
|
||
| `settings/CrmSettingsPage.tsx` | 3 neue Config-Sektionen: Branchen (mit Color-Picker), Kontotypen, Beziehungstypen |
|
||
| `settings/CrmSettingsPage.module.css` | Styles fuer Config-Tabellen, Inline-Edit, Sort-Buttons |
|
||
|
||
### CompanyDetailPage — 3-Spalten-Layout
|
||
|
||
```
|
||
+------------------+------------------------+--------------------+
|
||
| Linke Spalte | Mittlere Spalte | Rechte Spalte |
|
||
| (300px) | (flex) | (360px) |
|
||
+------------------+------------------------+--------------------+
|
||
| Stammdaten: | Activity Feed: | Kontakte (Tabelle) |
|
||
| - Name | - Inline-Notiz-Form | Vorgaenge (Tabelle)|
|
||
| - Branche Badge | - Tabs: Notiz/Email/ | Beziehungen Card |
|
||
| (farbig) | Aufgabe | Vertraege |
|
||
| - Kontotyp | - Chronologische | (Platzhalter) |
|
||
| - Zustaendigkeit | Liste aller | Lexware Belege |
|
||
| - E-Mail, Tel | Aktivitaeten | |
|
||
| - Website | - "via [Kontakt]" | |
|
||
| - Adresse | Badge fuer | |
|
||
| - Tags | Kontakt-Aktivitaeten | |
|
||
| - Notizen | | |
|
||
+------------------+------------------------+--------------------+
|
||
```
|
||
|
||
**Responsive:**
|
||
- Ab 1200px: 2 Spalten (Links+Mitte gestapelt | Rechts)
|
||
- Ab 768px: 1 Spalte (alles gestapelt)
|
||
|
||
### Activity Feed Details
|
||
|
||
- Nutzt `GET /crm/activities?companyId=X&includeContacts=true`
|
||
- Inline-Formular oben: Betreff + Beschreibung + "Notiz speichern" Button
|
||
- Tabs: "Notiz" (aktiv), "E-Mail" (disabled, Platzhalter), "Aufgabe" (disabled, Platzhalter)
|
||
- Jeder Eintrag: Typ-Icon (SVG), Betreff, Ersteller, Zeitpunkt
|
||
- Kontakt-Aktivitaeten zeigen "via [Kontaktname]" Badge
|
||
|
||
### CRM-Einstellungen — Admin-Konfiguration
|
||
|
||
Drei neue Sektionen in `/crm/settings`:
|
||
|
||
1. **Branchen**: CRUD-Tabelle mit Name, Farb-Badge + Color-Picker, Sortier-Pfeile
|
||
2. **Kontotypen**: CRUD-Tabelle mit Name, Sortier-Pfeile
|
||
3. **Beziehungstypen**: CRUD-Tabelle mit Name, Sortier-Pfeile
|
||
|
||
Alle mit Inline-Add (Eingabezeile oben), Inline-Edit, Delete mit Bestaetigung.
|
||
|
||
### CompanyFormModal — Dropdown-Aenderungen
|
||
|
||
| Feld | Vorher | Nachher |
|
||
|------|--------|---------|
|
||
| Branche | Freitext-Input | Select-Dropdown aus `GET /crm/industries` |
|
||
| Kontotyp | — (neu) | Select-Dropdown aus `GET /crm/account-types` |
|
||
| Zustaendigkeit | — (neu) | Select-Dropdown aus `GET /crm/users` |
|
||
|
||
`ownerName` wird beim Submit aus der User-Liste aufgeloest und im Payload mitgesendet.
|
||
|
||
### Genutzte neue Backend-Endpoints
|
||
|
||
| Hook | Endpoint | Verwendung |
|
||
|------|----------|-----------|
|
||
| `useIndustries()` | `GET /crm/industries` | CompanyFormModal Dropdown, CRM Settings |
|
||
| `useAccountTypes()` | `GET /crm/account-types` | CompanyFormModal Dropdown, CRM Settings |
|
||
| `useRelationshipTypes()` | `GET /crm/relationship-types` | AddRelationshipModal, CRM Settings |
|
||
| `useCompanyRelationships(id)` | `GET /crm/companies/:id/relationships` | CompanyRelationshipsCard |
|
||
| `useCompanyActivities(id)` | `GET /crm/activities?companyId=X&includeContacts=true` | ActivityFeed |
|
||
| `useTenantUsers()` | `GET /crm/users` | CompanyFormModal Owner-Dropdown |
|
||
| `useCreateIndustry()` | `POST /crm/industries` | CRM Settings |
|
||
| `useUpdateIndustry()` | `PATCH /crm/industries/:id` | CRM Settings |
|
||
| `useDeleteIndustry()` | `DELETE /crm/industries/:id` | CRM Settings |
|
||
| (analog fuer AccountTypes + RelationshipTypes) | | |
|
||
| `useCreateCompanyRelationship()` | `POST /crm/companies/:id/relationships` | CompanyRelationshipsCard |
|
||
| `useDeleteCompanyRelationship()` | `DELETE /crm/companies/:id/relationships/:relId` | CompanyRelationshipsCard |
|
||
|
||
### Offene Punkte
|
||
|
||
- [ ] **Migration auf Server anwenden**: `20260311_add_company_detail_overhaul` + Seed-Data
|
||
- [ ] **Container neu bauen und deployen** (Frontend + Backend)
|
||
- [ ] **Kanban-Board fuer Vorgaenge** — Feature fuer spaeter geplant
|
||
- [ ] **Vertraege-UI implementieren** — DB-Modell vorhanden, UI ist noch Platzhalter
|
||
- [ ] **Activity Feed E-Mail/Aufgabe Tabs** — Derzeit Platzhalter (disabled)
|
||
|
||
### TypeScript-Status
|
||
|
||
- Frontend: `npx tsc --noEmit` — 0 Fehler
|
||
- Backend: `npx tsc --noEmit` — 0 Fehler (nach `prisma generate`)
|
||
|
||
---
|
||
|
||
## 2026-03-11 | Frontend: Server-Deployment & Bugfixes
|
||
|
||
### White-Screen-Fix
|
||
|
||
Die GUI zeigte nach dem letzten Deploy ein weisses Bild. Ursache: Die `hooks.ts` mit den neuen Hooks fuer Industries, AccountTypes und RelationshipTypes war nur lokal geaendert, aber nie committed worden. Die `CrmSettingsPage.tsx` importierte Exports die auf dem Server nicht existierten → esbuild Build-Fehler → leere Seite.
|
||
|
||
**Fix:** Alle uncommitteten CRM-Dateien (41 Dateien, Backend + Frontend) in einem Commit zusammengefasst und deployed.
|
||
|
||
### Server-Deployment der Company Detail Overhaul
|
||
|
||
Folgende Schritte wurden auf dem Server (172.20.10.59) ausgefuehrt:
|
||
|
||
1. **Prisma-Migration `20260310_add_lexware_integration`** — War bereits manuell angewendet (VoucherType Enum existierte), wurde als `applied` markiert via `prisma migrate resolve`
|
||
2. **Prisma-Migration `20260311_add_company_detail_overhaul`** — Erfolgreich angewendet. Neue Tabellen: `industries`, `account_types`, `relationship_types`, `company_relationships`, `contracts`. Aenderungen an `companies` und `activities`.
|
||
3. **Prisma-Client regeneriert** — `prisma generate` im Container ausgefuehrt
|
||
4. **CRM-Container neugestartet** — Alle neuen Controller registriert:
|
||
- `IndustriesController {/api/v1/crm/industries}`
|
||
- `AccountTypesController {/api/v1/crm/account-types}`
|
||
- `RelationshipTypesController {/api/v1/crm/relationship-types}`
|
||
- `CompanyRelationshipsController {/api/v1/crm/companies/:companyId/relationships}`
|
||
5. **Seed-Daten geladen** fuer beide Tenants (`3fc0e74d-...` und `11111111-...`):
|
||
- 8 Branchen pro Tenant (+ 1 migrierte je Tenant = 18 total)
|
||
- 4 Kontotypen pro Tenant (8 total)
|
||
- 4 Beziehungstypen pro Tenant (8 total)
|
||
|
||
### Health-Check nach Deployment
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"service": "crm-service",
|
||
"version": "0.2.0",
|
||
"services": { "database": "up", "redis": "up", "lexware": "up" }
|
||
}
|
||
```
|
||
|
||
### CRM-Einstellungen — Tabbed Layout
|
||
|
||
Die CRM-Settings-Seite (`/crm/settings`) wurde von gestapelten Cards auf ein **Tab-Layout** umgestellt:
|
||
|
||
| Tab | Inhalt |
|
||
|-----|--------|
|
||
| **Module** | Modul-Toggles (Kontakte, Unternehmen, Vorgaenge, Pipelines, Lexware) |
|
||
| **Lexoffice Sync** | Import/Export eingebettet (vorher separate Seite `/crm/lexware-sync`) |
|
||
| **Weitere Einstellungen** | Admin-Konfiguration: Branchen, Kontotypen, Beziehungstypen |
|
||
|
||
**Lexware Import** wurde ebenfalls ueberarbeitet: Statt Suchfeld-only gibt es jetzt eine **browsable paginierte Liste** aller Lexware-Kontakte mit aufklappbaren Ansprechpartnern und individuellen Import-Buttons.
|
||
|
||
### Commits
|
||
|
||
| Commit | Beschreibung |
|
||
|--------|-------------|
|
||
| `5532918` | feat(frontend): redesign Lexware Import with browsable list + Ansprechpartner |
|
||
| `4e5c26c` | feat(frontend): add tabbed layout to CRM Settings page |
|
||
| `0ed1e77` | feat(crm): add company detail overhaul with industries, account types, relationship types |
|
||
| `08b212b` | docs(crm): update INSIGHT-CRM.md with company detail overhaul entries |
|
||
|
||
### Offene Punkte
|
||
|
||
- [x] ~~Migration auf Server anwenden~~ — Erledigt (beide Migrationen)
|
||
- [x] ~~Container neu bauen und deployen~~ — Frontend + CRM Backend deployed
|
||
- [x] ~~Seed-Daten laden~~ — Fuer beide Tenants
|
||
- [ ] **Vertraege-UI implementieren** — DB-Modell vorhanden, UI ist Platzhalter (`ContractsCard.tsx`)
|
||
- [ ] **Activity Feed E-Mail/Aufgabe Tabs** — Tabs vorhanden aber disabled
|
||
- [ ] **Kanban-Board fuer Vorgaenge** — Backend ready, Frontend Feature fuer spaeter
|
||
|
||
### Hinweis an Backend
|
||
|
||
- Der `insight-crm` Container laeuft im Dev-Modus mit Volume-Mount. Code-Aenderungen werden automatisch erkannt.
|
||
- Nach Schema-Aenderungen muss `prisma generate` im Container ausgefuehrt werden.
|
||
- Die `LexwareSyncContent`-Komponente wurde als separater Export aus `LexwareSyncPage.tsx` extrahiert und wird sowohl auf der Standalone-Seite (`/crm/lexware-sync`) als auch eingebettet im Settings-Tab verwendet.
|
||
|
||
---
|
||
|
||
## 2026-03-11 | Frontend: Bug-Report — Lexware Import Company schlaegt fehl (500)
|
||
|
||
### Problem
|
||
|
||
Beim Klick auf "Unternehmen" (Import als Company) im Lexware Sync Tab kommt ein **500 Internal Server Error**.
|
||
|
||
### Fehlermeldung (aus `docker logs insight-crm`)
|
||
|
||
```
|
||
PrismaClientValidationError:
|
||
Invalid `this.prisma.company.create()` invocation in
|
||
/app/src/lexware/lexware-contacts.service.ts:229:32
|
||
|
||
→ 229 return this.prisma.company.create({
|
||
data: {
|
||
name: "team neusta SE",
|
||
email: "k.sauer@neusta.de",
|
||
phone: undefined,
|
||
street: "Konsul-Smidt-Straße 24",
|
||
zip: "28217",
|
||
city: "Bremen",
|
||
country: "DE",
|
||
notes: undefined,
|
||
lexwareContactId: "e23f5165-9c1e-40ba-9536-9990703421df",
|
||
lexwareContactVersion: 6,
|
||
lexwareSyncedAt: new Date("2026-03-11T09:05:10.318Z"),
|
||
createdBy: "4627b01c-2f23-4ee8-a44e-c04bff068a5f",
|
||
+ tenantId: String ← FEHLER: Typ "String" statt UUID-Wert
|
||
},
|
||
```
|
||
|
||
### Ursache
|
||
|
||
In `lexware-contacts.service.ts` Zeile 229 wird `tenantId` als **TypeScript-Typ `String`** uebergeben statt als tatsaechlicher UUID-Wert aus dem JWT-Token. Vermutlich steht dort so etwas wie `tenantId: String` statt `tenantId: user.tenantId` oder `tenantId: this.tenantId`.
|
||
|
||
### Betroffene Datei
|
||
|
||
`packages/crm-service/src/lexware/lexware-contacts.service.ts` — Zeile ~229 (`importCompany`-Methode)
|
||
|
||
### Vermutlich gleicher Fehler bei
|
||
|
||
- `importContact` (Lexware-Kontakt als CRM Contact importieren)
|
||
- Eventuell auch `push` und `sync` Methoden, falls diese ebenfalls `tenantId` setzen
|
||
|
||
### Reproduktion
|
||
|
||
1. `/crm/settings` → Tab "Lexoffice Sync" → "Import (Lexware → CRM)"
|
||
2. Beliebigen Lexware-Kontakt suchen (z.B. "team")
|
||
3. Auf "Unternehmen" Button klicken
|
||
4. → Rote Fehlermeldung: "Import fehlgeschlagen: Request failed with status code 500"
|
||
|
||
---
|
||
|
||
## 2026-03-11 | Backend: Fix — Lexware Import 500 (fehlende tenantId)
|
||
|
||
### Ursache
|
||
|
||
Der `TenantGuard` liess `PLATFORM_ADMIN`-User ohne `tenantId`-Pruefung durch:
|
||
|
||
```typescript
|
||
// ALT (fehlerhaft):
|
||
if (user?.role === 'PLATFORM_ADMIN') {
|
||
return true; // ← Kein tenantId-Check!
|
||
}
|
||
```
|
||
|
||
Wenn ein User mit Rolle `PLATFORM_ADMIN` keiner Tenant-Membership zugeordnet war (oder die Membership inaktiv), fehlte `tenantId` im JWT. Der Controller uebergab dann `user.tenantId!` = `undefined` an den Service, was zum Prisma-Validierungsfehler fuehrte.
|
||
|
||
### Fixes
|
||
|
||
**1. TenantGuard (`src/auth/guards/tenant.guard.ts`):**
|
||
- ALLE User (auch PLATFORM_ADMIN) muessen jetzt eine `tenantId` haben, um auf CRM-Ressourcen zuzugreifen
|
||
- Klare Fehlermeldung: "Kein Mandant zugeordnet. Bitte mit einem mandanten-gebundenen Account anmelden."
|
||
|
||
**2. Defensive Pruefung in Lexware-Service (`src/lexware/lexware-contacts.service.ts`):**
|
||
- `importAsCompany()` und `importAsContact()` pruefen zusaetzlich `if (!tenantId)` und werfen `BadRequestException` mit klarer Meldung
|
||
|
||
### Betroffene Dateien
|
||
|
||
| Datei | Aenderung |
|
||
|-------|-----------|
|
||
| `src/auth/guards/tenant.guard.ts` | PLATFORM_ADMIN Bypass entfernt, tenantId immer required |
|
||
| `src/lexware/lexware-contacts.service.ts` | Defensive tenantId-Pruefung in Import-Methoden |
|
||
|
||
### Auswirkung
|
||
|
||
- PLATFORM_ADMIN ohne Tenant-Zuordnung bekommt jetzt **403 Forbidden** statt **500 Internal Server Error**
|
||
- Alle anderen User sind nicht betroffen (hatten vorher schon den tenantId-Check)
|
||
- TypeScript-Check: 0 Fehler
|
||
|
||
---
|
||
|
||
## 2026-03-12 | Architekt: Neue Anforderungen aus Konzeptdokument v1.0 + Briefing
|
||
|
||
Der Architekt hat das Konzeptdokument (INSIGHT_Konzept_v1.0.docx) und das Claude Briefing (CLAUDE_BRIEFING.docx) aktualisiert. Folgende Kapitel sind neu oder erweitert und betreffen den CRM-Service direkt:
|
||
|
||
- **Kapitel 22** — CRM-Modul (vorher nur Platzhalter, jetzt vollstaendig spezifiziert)
|
||
- **Kapitel 24** — Office 365 Integration (neu, CRM-relevante Teile)
|
||
- **Kapitel 14 im Briefing** — Office 365 Kurzreferenz (neu)
|
||
|
||
### Hinweis: CORE vs CRM
|
||
|
||
Folgende Teile sind **CORE-Aufgaben** (NICHT fuer den CRM-Entwickler):
|
||
- Kap 24.1: OAuth-Flow + `user_integrations` Tabelle in `platform_core`
|
||
- Kap 24.8: Azure App-Registrierung im Azure Portal
|
||
- `.env`-Variablen: `MS_CLIENT_ID`, `MS_CLIENT_SECRET`, `MS_REDIRECT_URI`, `MS_INTEGRATION_ENCRYPTION_KEY`
|
||
|
||
Alles Folgende ist **CRM-Arbeit**.
|
||
|
||
---
|
||
|
||
### A) CRM-Modul Spezifikation (Kap 22) — Neue/Erweiterte Anforderungen
|
||
|
||
#### A.1 Kontakttypen & Felder (Kap 22.1)
|
||
|
||
Die vollstaendige Felddefinition fuer Person und Unternehmen liegt jetzt vor:
|
||
|
||
**Kontakttyp: Person**
|
||
|
||
| Feld | Typ | Pflicht | Bemerkung |
|
||
|------|-----|---------|-----------|
|
||
| Vorname | String | Ja | |
|
||
| Nachname | String | Ja | |
|
||
| Jobtitel | String | Nein | |
|
||
| Unternehmen | Relation -> Unternehmen | Nein | |
|
||
| Abteilung | String | Nein | |
|
||
| E-Mail | Array (String) | Nein | Mehrere, Typ: Arbeit / Privat / Sonstige |
|
||
| Telefon | Array (String) | Nein | Mehrere, Typ: Buero / Mobil / Fax |
|
||
| Adresse | Objekt | Nein | Strasse, PLZ, Stadt, Land |
|
||
| LinkedIn-URL | String (URL) | Nein | |
|
||
| Geburtsdatum | Date | Nein | Optional, kann ausgeblendet werden |
|
||
| Quelle | Enum | Nein | Messe, Empfehlung, Website, Kaltakquise, Import, Visitenkarte, Sonstige |
|
||
| Tags | Array (String) | Nein | Frei vergebbar, tenant-weit geteilt |
|
||
| Status | Enum | Ja | Aktiv / Inaktiv / Gesperrt (Default: Aktiv) |
|
||
| Notizen | Text (Markdown) | Nein | |
|
||
| Benutzerdefinierte Felder | Dynamisch | Nein | Siehe A.6 |
|
||
|
||
**Kontakttyp: Unternehmen**
|
||
|
||
| Feld | Typ | Pflicht | Bemerkung |
|
||
|------|-----|---------|-----------|
|
||
| Firmenname | String | Ja | |
|
||
| Branche | String | Nein | Freitext oder vordefinierte Kategorien |
|
||
| Website | String (URL) | Nein | |
|
||
| Telefon | Array (String) | Nein | Mehrere Nummern |
|
||
| Adresse Hauptsitz | Objekt | Nein | Strasse, PLZ, Stadt, Land |
|
||
| Adresse Lieferung | Objekt | Nein | Optional abweichende Lieferadresse |
|
||
| USt-IdNr. | String | Nein | |
|
||
| Steuernummer | String | Nein | |
|
||
| Handelsregisternummer | String | Nein | z.B. HRB 12345 — befuellbar via Datenanreicherung |
|
||
| Registergericht | String | Nein | z.B. Amtsgericht Muenchen — befuellbar via Datenanreicherung |
|
||
| Unternehmensgroesse | Enum | Nein | 1-10, 11-50, 51-200, 201-500, 500+ |
|
||
| Ansprechpartner | Relation -> Personen | Nein | Verknuepfte Personen |
|
||
| Tags | Array (String) | Nein | |
|
||
| Status | Enum | Ja | Aktiv / Inaktiv / Gesperrt |
|
||
| Notizen | Text (Markdown) | Nein | |
|
||
| Datenanreicherung | Automatisch | Nein | data_enriched_at + data_enriched_source |
|
||
| Benutzerdefinierte Felder | Dynamisch | Nein | Siehe A.6 |
|
||
|
||
**Backend-Aufgabe**: Abgleich mit bestehenden Prisma-Modellen. Fehlende Felder (LinkedIn, Geburtsdatum, Quelle, Unternehmensgroesse, USt-IdNr, Steuernummer, Handelsregisternummer, Registergericht, Adresse Lieferung, data_enriched_at/source) muessen ergaenzt werden.
|
||
|
||
#### A.2 Firmendaten-Anreicherung / Data Enrichment (Kap 22.2) — NEU
|
||
|
||
"Firmendaten laden"-Button im Unternehmenformular. Stammdaten aus externen Registern abrufen und als Vorschlag in einem Modal anzeigen.
|
||
|
||
**Quellen:**
|
||
1. **Unternehmensregister.de** (kostenlos, oeffentlich) — HR-Nummer, Registergericht, Rechtsform, Sitz, Eintragsdatum
|
||
2. **North Data API** (kommerziell, API-Key pro Tenant) — Adresse, Branche, Umsatz, Mitarbeiterzahl, Gesellschafter, Verflechtungen
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] `POST /crm/companies/:id/enrich` oder `POST /crm/data-enrichment/company` Endpoint
|
||
- [ ] Paralleler Abruf: Unternehmensregister.de + North Data API (Timeout je 10 Sek.)
|
||
- [ ] Ergebnisse normalisieren und zusammenfuehren
|
||
- [ ] `data_enriched_at` + `data_enriched_source` in Company speichern
|
||
- [ ] Admin-Einstellung: North Data API-Key pro Tenant (Admin > CRM > Integrationen)
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] "Firmendaten laden" Button im Unternehmenformular
|
||
- [ ] Anreicherungs-Modal: Linke Spalte = aktueller Wert, Rechte Spalte = Vorschlag (Quelle), Checkbox pro Feld
|
||
- [ ] "Auswahl uebernehmen" -> Felder im Formular setzen (Speichern erst bei explizitem Speichern)
|
||
- [ ] CRM-Settings-Seite: North Data API-Key Konfiguration
|
||
|
||
#### A.3 Zustaendigkeit / Account Owner (Kap 22.3) — NEU
|
||
|
||
Jeder Kontakt/Unternehmen kann mit mehreren internen Mitarbeitern verknuepft werden (m:n).
|
||
|
||
| Aspekt | Entscheidung |
|
||
|--------|-------------|
|
||
| Modell | m:n — ein Kontakt hat mehrere Owner, ein Mitarbeiter hat mehrere Kontakte |
|
||
| Rollen pro Zuweisung | `OWNER`, `MEMBER`, `WATCHER` (unterschiedliche Bearbeitungsrechte) |
|
||
| Mitarbeiter-Referenz | `user_id` aus `platform_core` (kein Kopieren von User-Daten) |
|
||
| Anzeige | Avatare der zugewiesenen Mitarbeiter in der Kontaktkarte |
|
||
| Pflicht-Owner | Mindestens 1 Owner pro Kontakt bei Erstellung (Default: erstellender User) |
|
||
|
||
**DB-Tabelle:** `crm_contact_owners (contact_id, user_id, role: OWNER|MEMBER|WATCHER)`
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] Contact-Owner CRUD: `POST/DELETE /crm/contacts/:id/owners`
|
||
- [ ] Bei Kontakt-Erstellung automatisch erstellenden User als OWNER setzen
|
||
- [ ] Owner-Info in Contact-Detail-Response mitliefern
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] Avatare der Owner in der Kontaktkarte anzeigen
|
||
- [ ] Owner-Zuweisung UI (User suchen + Rolle waehlen)
|
||
|
||
#### A.4 Pipeline & Deal-Management — Erweiterte Spec (Kap 22.4)
|
||
|
||
Ergaenzungen zur bestehenden Implementierung:
|
||
|
||
- **Forecast-Ansicht**: Aggregierter Dealwert pro Stage gewichtet mit Wahrscheinlichkeit
|
||
- **Lost-Grund**: Enum + Freitext bei Status Lost: Preis, Timing, Wettbewerber, Kein Bedarf, Sonstige
|
||
- **Pipeline-Sichtbarkeit**: Enum — Alle Tenant-User / Nur zugewiesene Teams
|
||
- **Deal-Owner**: m:n analog zu Kontakt-Owner (crm_deal_owners)
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] `lost_reason` Enum + `lost_reason_text` Freitext in Deals
|
||
- [ ] Deal-Owner CRUD (analog Contact-Owner)
|
||
- [ ] Forecast-Endpoint: `GET /crm/deals/forecast` (Wert x Wahrscheinlichkeit pro Stage)
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] Lost-Grund Modal bei Stage-Wechsel zu "Lost"
|
||
- [ ] Kanban-Board (Drag & Drop)
|
||
- [ ] Forecast-Ansicht
|
||
|
||
#### A.5 Aktivitaeten & Aufgaben (Kap 22.5) — Erweiterte Spec
|
||
|
||
6 Aktivitaetstypen sind definiert:
|
||
|
||
| Typ | Icon | Bemerkung |
|
||
|-----|------|-----------|
|
||
| Anruf (Call) | Telefon | Kann Gespraechsnotizen enthalten |
|
||
| Meeting | Kalender | Datum, Uhrzeit, Ort / Videolink |
|
||
| E-Mail | Brief | Freitext-Notiz, KEINE echte E-Mail-Integration in MVP |
|
||
| Aufgabe (Task) | Checkbox | Faelligkeitsdatum, Zuweisung an Mitarbeiter |
|
||
| Notiz | Stift | Freitext ohne Faelligkeitsdatum |
|
||
| Follow-Up | Pfeil | Erinnerung zu einem definierten Zeitpunkt |
|
||
|
||
**Hinweis**: E-Mail-Integration (Outlook) kommt erst mit Office 365 (Kap 24).
|
||
|
||
#### A.6 Benutzerdefinierte Felder / Custom Fields (Kap 22.6) — NEU
|
||
|
||
Pro Kontakttyp (Person, Unternehmen) und pro Deal koennen eigene Felder definiert werden.
|
||
|
||
**Unterstuetzte Feldtypen:**
|
||
|
||
| Typ | Beschreibung | Beispiel |
|
||
|-----|-------------|---------|
|
||
| Text | Einzeiliger Freitext | Kundennummer |
|
||
| Textarea | Mehrzeiliger Freitext | Besondere Hinweise |
|
||
| Zahl | Integer oder Decimal | Umsatz, Vertragslaufzeit |
|
||
| Datum | Datumspicker | Vertragsbeginn |
|
||
| Auswahl (Dropdown) | Vordefinierte Werte | Kundensegment: A / B / C |
|
||
| Mehrfachauswahl | Mehrere Werte waehlbar | Interessensgebiete |
|
||
| Checkbox | Boolean Ja/Nein | DSGVO-Einwilligung |
|
||
| URL | Validierter Link | Portal-Link |
|
||
|
||
**DB-Tabellen:**
|
||
```sql
|
||
crm_custom_field_defs (id, entity_type: PERSON|COMPANY|DEAL, name, label, field_type,
|
||
options jsonb, is_required, position)
|
||
crm_custom_field_values (id, field_def_id, entity_id, value_text, value_number,
|
||
value_date, value_boolean, value_json)
|
||
```
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] CRUD fuer Field-Definitions: `GET/POST/PATCH/DELETE /crm/custom-fields`
|
||
- [ ] Wert-Speicherung bei Entity-Create/Update
|
||
- [ ] Custom Fields in Entity-Detail-Response mitliefern
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] Admin-Bereich: Custom Fields Verwaltung (Drag & Drop Reihenfolge, Pflichtfeld-Flag)
|
||
- [ ] Dynamische Formular-Felder in Contact/Deal-Formularen
|
||
- [ ] Custom Fields als optionale Spalten in Listen-Ansichten
|
||
- [ ] Custom Fields als Filter in der Suche
|
||
|
||
#### A.7 Kontakt-Import (Kap 22.7) — NEU
|
||
|
||
**Import via Datei:**
|
||
- [ ] CSV-Import mit visuellem Spalten-Mapper
|
||
- [ ] Excel-Import (.xlsx)
|
||
- [ ] vCard-Import (.vcf, einzeln und ZIP)
|
||
- [ ] Vorschau der ersten 10 Datensaetze + Validierungsfehler anzeigen
|
||
- [ ] Duplikat-Erkennung via E-Mail: ueberspringen / zusammenfuehren / als Duplikat markieren
|
||
|
||
**Visitenkarten-Scan (Anthropic Vision API):**
|
||
- [ ] "Visitenkarte scannen" — Kamera-/Datei-Dialog
|
||
- [ ] Bild base64-kodiert an CRM-Backend senden
|
||
- [ ] Backend ruft Anthropic Vision API auf (strukturierter Prompt)
|
||
- [ ] Vorausgefuelltes Kontaktformular zur Bestaetigung
|
||
- [ ] Rate Limit: max. 50 Scans pro Tenant pro Tag (konfigurierbar)
|
||
- [ ] Bild wird NICHT dauerhaft gespeichert
|
||
|
||
#### A.8 Berechtigungsmodell (Kap 22.8) — NEU
|
||
|
||
Ownership-basiertes Sichtbarkeitsmodell:
|
||
|
||
| Stufe | Beschreibung | Sieht was? |
|
||
|-------|-------------|-----------|
|
||
| Eigene | User sieht nur eigene Datensaetze (Owner) | Eigene Kontakte, Deals, Aktivitaeten |
|
||
| Team | User sieht alle Datensaetze der eigenen Abteilung | Kontakte aller Kollegen |
|
||
| Alle | User sieht alle Datensaetze des Tenants | Vollzugriff (lesend) |
|
||
|
||
**Berechtigungen nach Rolle:**
|
||
|
||
| Rolle | Sichtbarkeit | Erstellen | Bearbeiten | Loeschen |
|
||
|-------|-------------|-----------|------------|----------|
|
||
| tenant_admin | Alle | Ja | Alle | Alle |
|
||
| team_lead | Team | Ja | Team + Eigene | Eigene |
|
||
| tenant_member | Konfigurierbar | Ja | Eigene + zugewiesene | Eigene |
|
||
| tenant_readonly | Konfigurierbar | Nein | Nein | Nein |
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] Sichtbarkeitsfilter in allen List-Queries (Contacts, Deals, Activities)
|
||
- [ ] Tenant-Admin Einstellung: Default-Sichtbarkeit pro Rolle
|
||
- [ ] Per-User Override moeglich
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] Admin > CRM-Einstellungen > Berechtigungen: Sichtbarkeitsstufe konfigurieren
|
||
|
||
#### A.9 Reporting & Dashboards (Kap 22.9) — NEU
|
||
|
||
5 Reports/Dashboards sind definiert:
|
||
|
||
| Dashboard | Inhalt | Aktualisierung |
|
||
|-----------|--------|---------------|
|
||
| Pipeline-Uebersicht | Deals pro Stage, Gesamtvolumen, gewichteter Forecast | Echtzeit |
|
||
| Aktivitaeten-Uebersicht | Offene Aufgaben, ueberfaellige Aktivitaeten, Volumen/Woche | Echtzeit |
|
||
| Kontaktwachstum | Neue Kontakte/Monat, aufgeschluesselt nach Quelle | Taeglich |
|
||
| Win/Loss-Analyse | Won vs Lost Deals, Lost-Gruende als Torte, Durchschnittliche Deal-Dauer | Taeglich |
|
||
| Mitarbeiter-Performance | Deals pro Mitarbeiter, Aktivitaeten-Anzahl, Response-Zeit | Taeglich |
|
||
|
||
- Alle Reports als CSV exportierbar
|
||
- Datumsbereiche: letzte 7 / 30 / 90 Tage, benutzerdefiniert
|
||
- Mitarbeiter-Performance nur fuer tenant_admin und team_lead sichtbar
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] Reporting-Endpoints: `GET /crm/reports/pipeline`, `/reports/activities`, `/reports/contacts-growth`, `/reports/win-loss`, `/reports/performance`
|
||
- [ ] CSV-Export fuer alle Reports
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] CRM Dashboard-Seite mit den 5 Report-Widgets
|
||
- [ ] Datumbereich-Selektor
|
||
- [ ] CSV-Export Buttons
|
||
|
||
#### A.10 CRM Datenbankschema (Kap 22.10)
|
||
|
||
Vollstaendiges Schema laut Architekt:
|
||
|
||
```sql
|
||
-- Kernentitaeten
|
||
crm_contacts (id, type: PERSON|COMPANY, status, source, created_by, created_at, updated_at)
|
||
crm_persons (id, contact_id, first_name, last_name, job_title, department, birthday, notes)
|
||
crm_companies (id, contact_id, name, industry, website, vat_id, tax_id, size_class, notes)
|
||
crm_contact_emails (id, contact_id, email, type: WORK|PERSONAL|OTHER, is_primary)
|
||
crm_contact_phones (id, contact_id, phone, type: OFFICE|MOBILE|FAX, is_primary)
|
||
crm_contact_addresses (id, contact_id, type: MAIN|DELIVERY, street, zip, city, country)
|
||
|
||
-- Beziehungen
|
||
crm_person_company (person_contact_id, company_contact_id)
|
||
crm_contact_owners (contact_id, user_id, role: OWNER|MEMBER|WATCHER)
|
||
crm_contact_tags (contact_id, tag)
|
||
|
||
-- Pipeline & Deals
|
||
crm_pipelines (id, name, is_default, visibility)
|
||
crm_pipeline_stages (id, pipeline_id, name, color, position, probability, is_won_stage, is_lost_stage)
|
||
crm_deals (id, contact_id, pipeline_id, stage_id, title, value, currency,
|
||
probability_override, expected_close_date, status: OPEN|WON|LOST,
|
||
lost_reason, lost_reason_text, notes, created_by)
|
||
crm_deal_owners (deal_id, user_id, role: OWNER|MEMBER|WATCHER)
|
||
|
||
-- Aktivitaeten
|
||
crm_activities (id, type: CALL|MEETING|EMAIL|TASK|NOTE|FOLLOWUP,
|
||
contact_id nullable, deal_id nullable,
|
||
title, body, due_date, completed_at, assigned_to_user_id, created_by)
|
||
|
||
-- Custom Fields
|
||
crm_custom_field_defs (id, entity_type: PERSON|COMPANY|DEAL, name, label, field_type,
|
||
options jsonb, is_required, position)
|
||
crm_custom_field_values (id, field_def_id, entity_id, value_text, value_number,
|
||
value_date, value_boolean, value_json)
|
||
```
|
||
|
||
**Hinweis**: Das aktuelle Prisma-Schema im CRM-Service weicht teilweise ab (z.B. `app_crm` Schema-Prefix statt `crm_` Tabellen-Prefix, flaches Company/Contact-Modell statt Contact+Person+Company Split). Der Architekt hat das Ziel-Schema definiert — Abgleich und Migration sind Backend-Aufgaben.
|
||
|
||
#### A.11 CRM Events (Kap 22.11)
|
||
|
||
| Event | Ausgeloest von | Empfaenger/Zweck |
|
||
|-------|---------------|-----------------|
|
||
| `crm.contact.created` | CRM-Service | Zukuenftige Module (Marketing etc.) |
|
||
| `crm.contact.updated` | CRM-Service | Zukuenftige Module |
|
||
| `crm.deal.stage_changed` | CRM-Service | Reporting, Automatisierungen |
|
||
| `crm.deal.won` | CRM-Service | Reporting, Modul-Integrationen |
|
||
| `crm.deal.lost` | CRM-Service | Reporting |
|
||
| `crm.activity.due_soon` | CRM-Service (Scheduler) | Benachrichtigungs-Service |
|
||
| `core.user.deactivated` | Core-Service | CRM prueft Owner-Reassignment |
|
||
|
||
**Backend-Aufgabe:**
|
||
- [ ] Redis Pub/Sub Events bei Kontakt/Deal/Activity-Aenderungen publishen
|
||
|
||
---
|
||
|
||
### B) Office 365 Integration — CRM-relevante Teile (Kap 24)
|
||
|
||
Die OAuth-Verbindung (Kap 24.1) und Azure App-Registrierung (Kap 24.8) sind **CORE-Aufgaben**. Der CRM-Service nutzt die bestehende MS-Verbindung des Users fuer folgende Features:
|
||
|
||
#### B.1 E-Mail Tab im CRM-Kontakt (Kap 24.2) — Read-only
|
||
|
||
Pro CRM-Kontakt wird ein "E-Mails"-Tab angezeigt mit allen Outlook-Mails von/an die Kontakt-E-Mail-Adresse.
|
||
|
||
**Graph API Abfrage:**
|
||
```
|
||
GET /me/messages
|
||
?$filter=from/emailAddress/address eq '{kontakt_email}'
|
||
or toRecipients/any(...)
|
||
&$select=id,subject,from,toRecipients,receivedDateTime,bodyPreview,isRead
|
||
&$orderby=receivedDateTime desc
|
||
&$top=25
|
||
```
|
||
|
||
| Feature | Detail |
|
||
|---------|--------|
|
||
| Anzeige | Absender, Betreff, Datum, Vorschautext (max. 255 Zeichen) |
|
||
| Sortierung | Neueste zuerst, 25 pro Seite |
|
||
| Caching | Redis 5 Min (Key: `user:{id}:mails:contact:{id}`) |
|
||
| Kein Volltext | Nur bodyPreview, kein vollstaendiger E-Mail-Body |
|
||
| Kein Senden | Read-only |
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] `GET /crm/contacts/:id/emails` Endpoint (Proxy zu Graph API)
|
||
- [ ] MS Access Token aus Redis / Refresh via Core
|
||
- [ ] Redis-Caching (5 Min TTL)
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] "E-Mails" Tab in ContactDetailPage (nur wenn MS-Verbindung aktiv)
|
||
- [ ] Ausgegraut wenn keine MS-Verbindung, Hinweis "Microsoft 365 verbinden"
|
||
|
||
#### B.2 Kalender Tab im CRM-Kontakt (Kap 24.3) — Read-only
|
||
|
||
Outlook-Termine bei denen der CRM-Kontakt als Teilnehmer eingetragen ist.
|
||
|
||
**Graph API Abfrage:**
|
||
```
|
||
GET /me/calendarView
|
||
?startDateTime={heute}T00:00:00Z
|
||
&endDateTime={heute+90Tage}T23:59:59Z
|
||
&$filter=attendees/any(a: a/emailAddress/address eq '{kontakt_email}')
|
||
&$select=id,subject,start,end,location,attendees,bodyPreview,onlineMeetingUrl
|
||
```
|
||
|
||
| Feature | Detail |
|
||
|---------|--------|
|
||
| Anzeige | Kommende 90 Tage + vergangene 90 Tage |
|
||
| Felder | Titel, Datum/Uhrzeit, Ort, Online-Meeting-Link, Teilnehmer |
|
||
| Caching | Redis 5 Min |
|
||
| Kein Schreiben | Read-only |
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] `GET /crm/contacts/:id/calendar` Endpoint
|
||
- [ ] Redis-Caching
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] "Kalender" Tab in ContactDetailPage
|
||
|
||
#### B.3 Aufgaben Sync — Bidirektional mit Microsoft To Do (Kap 24.4)
|
||
|
||
CRM-Aufgaben koennen optional nach Microsoft To Do synchronisiert werden.
|
||
|
||
**Richtung INSIGHT -> To Do:**
|
||
- Trigger: Aufgabe erstellt/geaendert im CRM
|
||
- Action: `POST /me/todo/lists/{defaultListId}/tasks` (oder PATCH wenn ms_task_id bekannt)
|
||
- `ms_task_id` wird in `crm_activities` gespeichert
|
||
|
||
**Richtung To Do -> INSIGHT:**
|
||
- NestJS `@Cron` Polling alle 5 Min fuer User mit aktiver MS-Verbindung
|
||
- `GET /me/todo/lists/{listId}/tasks?$filter=lastModifiedDateTime gt {letzter_sync}`
|
||
- Status-Aenderungen (erledigt/offen) werden uebernommen
|
||
- Redis-Lock pro User verhindert parallele Sync-Jobs
|
||
- Nur INSIGHT-erstellte Aufgaben werden synchronisiert (die eine ms_task_id haben)
|
||
|
||
**DB-Aenderungen an crm_activities:**
|
||
```sql
|
||
+ ms_task_id VARCHAR(255) -- To Do Aufgaben ID
|
||
+ ms_task_list_id VARCHAR(255) -- To Do Listen ID
|
||
+ ms_synced_at TIMESTAMPTZ -- Letzter erfolgreicher Sync
|
||
```
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] ms_task_id, ms_task_list_id, ms_synced_at Felder in Activity-Model
|
||
- [ ] Graph API Integration: POST/PATCH Tasks
|
||
- [ ] @Cron Scheduler (alle 5 Min) fuer To Do -> INSIGHT Sync
|
||
- [ ] Redis-Lock pro User
|
||
- [ ] Konfliktbehandlung: INSIGHT gewinnt (Last Write Wins mit Timestamp)
|
||
|
||
#### B.4 Kontakte Export nach Outlook (Kap 24.5) — Manuell
|
||
|
||
Button "Nach Outlook exportieren" im CRM-Kontakt (Person).
|
||
|
||
**Ablauf:**
|
||
1. User klickt "Nach Outlook exportieren"
|
||
2. Pruefung: MS-Verbindung aktiv? -> Wenn nein: Hinweis
|
||
3. Existiert Kontakt in Outlook (ms_contact_id)? -> PATCH (Update) / POST (Neu)
|
||
4. ms_contact_id wird in crm_persons gespeichert
|
||
|
||
**Feldzuordnung CRM -> Outlook:**
|
||
|
||
| CRM Feld | Outlook Contacts (Graph API) |
|
||
|----------|------------------------------|
|
||
| Vorname + Nachname | givenName + surname |
|
||
| Jobtitel | jobTitle |
|
||
| Unternehmen | companyName |
|
||
| Abteilung | department |
|
||
| E-Mails | emailAddresses[] |
|
||
| Telefone | businessPhones[] / mobilePhone |
|
||
| Adresse | businessAddress |
|
||
| LinkedIn-URL | businessHomePage |
|
||
| Notizen | personalNotes |
|
||
|
||
**DB-Aenderung an crm_persons:**
|
||
```sql
|
||
+ ms_contact_id VARCHAR(255) -- Outlook Kontakt ID
|
||
```
|
||
|
||
**Backend-Aufgaben:**
|
||
- [ ] `POST /crm/contacts/:id/export-to-outlook` Endpoint
|
||
- [ ] ms_contact_id Feld in Person-Model
|
||
- [ ] Bei 404 (Kontakt in Outlook geloescht): neuen Kontakt erstellen, ms_contact_id aktualisieren
|
||
|
||
**Frontend-Aufgaben:**
|
||
- [ ] "Nach Outlook exportieren" Button in Kontakt-Header (nur fuer Personen)
|
||
- [ ] Ausgegraut wenn keine MS-Verbindung
|
||
|
||
---
|
||
|
||
### C) Zusammenfassung: Priorisierte Aufgabenliste fuer CRM-Entwickler
|
||
|
||
**Prio 1 — Kurzfristig (bestehende Features erweitern):**
|
||
- [ ] Fehlende Kontakt-/Unternehmens-Felder im Prisma-Schema ergaenzen (LinkedIn, Geburtsdatum, Quelle, USt-IdNr etc.)
|
||
- [ ] Lost-Grund (Enum + Freitext) bei Deals
|
||
- [ ] Contact/Deal-Owner m:n Modell (crm_contact_owners, crm_deal_owners)
|
||
- [ ] Redis Pub/Sub Events bei Entity-Aenderungen
|
||
|
||
**Prio 2 — Mittelfristig (neue Features):**
|
||
- [ ] Custom Fields (Definition + Wert-Speicherung + Admin-UI)
|
||
- [ ] Firmendaten-Anreicherung (Unternehmensregister.de + North Data)
|
||
- [ ] Kontakt-Import (CSV, Excel, vCard) mit Spalten-Mapper + Duplikat-Erkennung
|
||
- [ ] Berechtigungsmodell (Eigene/Team/Alle Sichtbarkeit)
|
||
- [ ] Kanban-Board fuer Deals (Drag & Drop)
|
||
- [ ] Forecast-Ansicht
|
||
|
||
**Prio 3 — Spaeter (abhaengig von Core-Vorarbeiten):**
|
||
- [ ] Office 365: E-Mail Tab (benoetigt OAuth-Infrastruktur im Core)
|
||
- [ ] Office 365: Kalender Tab
|
||
- [ ] Office 365: Aufgaben Sync mit Microsoft To Do
|
||
- [ ] Office 365: Kontakte Export nach Outlook
|
||
- [ ] Visitenkarten-Scan (Anthropic Vision API)
|
||
- [ ] CRM Reporting & Dashboards (5 Reports + CSV-Export)
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 2026-03-12 | Plattform-Admin: Briefing fuer CRM-Backend-Experten
|
||
|
||
### Kontext
|
||
|
||
Der Architekt hat das Konzeptdokument v1.0 und das Claude Briefing aktualisiert. Es gibt umfangreiche neue Anforderungen fuer das CRM-Modul (Details: siehe Eintrag oben vom gleichen Datum). Dieses Briefing definiert die **Reihenfolge und Abhaengigkeiten** fuer die CRM-Backend-Entwicklung.
|
||
|
||
### Aktueller Stand
|
||
|
||
Das CRM-Backend (`packages/crm-service/`) hat bereits folgende Module produktiv:
|
||
|
||
| Modul | Status | Endpoints |
|
||
|-------|--------|-----------|
|
||
| Contacts (Person + Company) | Laeuft | CRUD + Suche + Paginierung |
|
||
| Deals | Laeuft | CRUD + Pipeline-Zuordnung |
|
||
| Pipelines + Stages | Laeuft | CRUD + Stage-Management |
|
||
| Activities | Laeuft | CRUD (6 Typen) |
|
||
| Industries | Laeuft | CRUD |
|
||
| Companies (Standalone) | Laeuft | CRUD + Lexware-Sync |
|
||
| Trade Events (Messe-Timer) | Laeuft | CRUD + Active-Filter |
|
||
| CRM Settings | Laeuft | Modulverwaltung pro Tenant |
|
||
|
||
Das Frontend fuer alle obigen Module ist ebenfalls deployed und funktional.
|
||
|
||
### Phasenplan: Was wann zu tun ist
|
||
|
||
#### Phase 1 — SOFORT starten (keine Abhaengigkeiten)
|
||
|
||
Diese Aufgaben erweitern bestehende Module und koennen sofort umgesetzt werden:
|
||
|
||
**1.1 Fehlende Felder im Prisma-Schema ergaenzen**
|
||
- Person: `linkedinUrl`, `birthday`, `source` (Enum), `status` (Enum)
|
||
- Company: `vatId`, `taxId`, `tradeRegisterNumber`, `registerCourt`, `companySize` (Enum), `deliveryAddress`, `dataEnrichedAt`, `dataEnrichedSource`
|
||
- Referenz: Abschnitt A.1 oben fuer vollstaendige Feldliste
|
||
- **Achtung:** Bestehende API-Responses und DTOs muessen die neuen Felder enthalten
|
||
- **Migration:** `prisma db push` auf dem Server nach Schema-Aenderung
|
||
|
||
**1.2 Contact/Deal Owner (m:n Modell)**
|
||
- Neue Tabellen: `crm_contact_owners` und `crm_deal_owners` (contact_id/deal_id, user_id, role: OWNER|MEMBER|WATCHER)
|
||
- Bei Kontakt-Erstellung: erstellenden User automatisch als OWNER setzen
|
||
- Owner-Info in Detail-Responses mitliefern (User-ID + Rolle)
|
||
- Endpoints: `POST/DELETE /crm/contacts/:id/owners`, analog fuer Deals
|
||
- Referenz: Abschnitt A.3 + A.4 oben
|
||
|
||
**1.3 Lost-Grund bei Deals**
|
||
- Neue Felder: `lostReason` (Enum: PRICE|TIMING|COMPETITOR|NO_NEED|OTHER) + `lostReasonText` (Freitext)
|
||
- Bei Stage-Wechsel zu Lost: lostReason Pflichtfeld
|
||
- Referenz: Abschnitt A.4 oben
|
||
|
||
**1.4 Redis Pub/Sub Events**
|
||
- Events publishen bei: Contact created/updated, Deal stage_changed/won/lost, Activity due_soon
|
||
- Key-Prefix: `app:crm:events:*`
|
||
- Event-Format: `{ type: 'crm.deal.won', tenantId, entityId, userId, timestamp, payload }`
|
||
- Referenz: Abschnitt A.11 oben
|
||
|
||
#### Phase 2 — DANACH (neue Features, keine CORE-Abhaengigkeit)
|
||
|
||
Nach Abschluss von Phase 1 koennen diese Features **parallel** entwickelt werden:
|
||
|
||
**2.1 Custom Fields System**
|
||
- DB: `crm_custom_field_defs` + `crm_custom_field_values` (Details in A.6)
|
||
- CRUD Endpoints fuer Field-Definitions: `GET/POST/PATCH/DELETE /crm/custom-fields`
|
||
- Wert-Speicherung bei Entity Create/Update
|
||
- Custom Fields in Entity-Detail-Responses mitliefern
|
||
- 8 Feldtypen: Text, Textarea, Zahl, Datum, Dropdown, Mehrfachauswahl, Checkbox, URL
|
||
|
||
**2.2 Firmendaten-Anreicherung (Data Enrichment)**
|
||
- `POST /crm/companies/:id/enrich` Endpoint
|
||
- Paralleler Abruf: Unternehmensregister.de (kostenlos) + North Data API (API-Key pro Tenant)
|
||
- Ergebnis als Vorschlag zurueckgeben (Frontend zeigt Vergleich-Modal)
|
||
- Admin-Einstellung fuer North Data API-Key
|
||
- Referenz: Abschnitt A.2 oben
|
||
|
||
**2.3 Kontakt-Import**
|
||
- CSV-Import mit Spalten-Mapping (Backend parst, gibt Vorschau zurueck)
|
||
- Excel-Import (.xlsx) — gleiche Logik
|
||
- vCard-Import (.vcf + ZIP)
|
||
- Duplikat-Erkennung via E-Mail
|
||
- Referenz: Abschnitt A.7 oben
|
||
|
||
**2.4 Berechtigungsmodell**
|
||
- Sichtbarkeitsfilter in allen List-Queries: Eigene / Team / Alle
|
||
- Konfigurierbar pro Rolle via Tenant-Admin
|
||
- Referenz: Abschnitt A.8 oben
|
||
|
||
**2.5 Forecast-Endpoint**
|
||
- `GET /crm/deals/forecast` — Aggregierter Dealwert pro Stage gewichtet mit Wahrscheinlichkeit
|
||
- Referenz: Abschnitt A.4 oben
|
||
|
||
#### Phase 3 — SPAETER (abhaengig von CORE OAuth)
|
||
|
||
> **BLOCKER:** Diese Features benoetigen die Microsoft 365 OAuth-Integration im Core-Service.
|
||
> Der Core-Entwickler arbeitet **parallel** an: OAuth-Flow, `user_integrations`-Tabelle,
|
||
> Azure App-Registrierung. Erst wenn das steht, kann Phase 3 beginnen.
|
||
|
||
**3.1 E-Mail Tab** — `GET /crm/contacts/:id/emails` (Proxy zu MS Graph API, Redis 5 Min Cache)
|
||
**3.2 Kalender Tab** — `GET /crm/contacts/:id/calendar` (Graph API, Redis Cache)
|
||
**3.3 Aufgaben Sync** — Bidirektional mit Microsoft To Do (@Cron alle 5 Min, Redis-Lock pro User)
|
||
**3.4 Kontakte Export** — `POST /crm/contacts/:id/export-to-outlook` (Graph API)
|
||
|
||
Details: Abschnitte B.1–B.4 oben
|
||
|
||
#### Phase 4 — NICE-TO-HAVE
|
||
|
||
**4.1 Visitenkarten-Scan** — Anthropic Vision API, Rate Limit 50/Tag/Tenant
|
||
**4.2 CRM Reporting** — 5 Dashboards + CSV-Export (Pipeline, Aktivitaeten, Kontaktwachstum, Win/Loss, Mitarbeiter-Performance)
|
||
|
||
### Wichtige technische Hinweise
|
||
|
||
1. **Schema-Abgleich noetig:** Das Ziel-Schema des Architekten (A.10) weicht vom aktuellen Prisma-Schema ab. Aktuell: flaches Contact/Company-Modell. Ziel: `crm_contacts` + `crm_persons` + `crm_companies` Split. **Entscheidung noetig:** Schrittweise migrieren oder Big-Bang-Umbau?
|
||
|
||
2. **User-Referenzen:** Contact/Deal-Owner referenzieren `user_id` aus `platform_core`. Keine User-Daten kopieren — bei Bedarf per REST-Call an Core-Service (`GET /api/users/:id`) oder JWT-Payload nutzen.
|
||
|
||
3. **Neue Rollen:** Das Berechtigungsmodell (A.8) definiert `team_lead` und `tenant_readonly` — diese Rollen existieren im Core noch NICHT. Abstimmung mit Core-Entwickler noetig.
|
||
|
||
4. **Bestehende Frontend-Hooks:** Das Frontend hat bereits React Query Hooks fuer alle bestehenden Endpoints. Neue Endpoints/Felder muessen in `packages/frontend/src/crm/types.ts`, `api.ts` und `hooks.ts` ergaenzt werden — das uebernimmt der Frontend-Entwickler nach Rueckmeldung.
|
||
|
||
### Kommunikationsweg
|
||
|
||
Bitte fuer jede abgeschlossene Phase einen Eintrag hier in diese Datei schreiben:
|
||
- Welche Endpoints sind neu/geaendert?
|
||
- Welche Prisma-Migrationen wurden ausgefuehrt?
|
||
- Gibt es Breaking Changes an bestehenden APIs?
|
||
- Welche Frontend-Anpassungen sind noetig?
|
||
|
||
Format: `## YYYY-MM-DD | CRM-Backend: [Betreff]`
|
||
|
||
---
|
||
|
||
## 2026-03-12 | CRM-Backend: Phase 1 — Schema-Expansion, Owner, Lost-Reason, Events
|
||
|
||
### Zusammenfassung
|
||
|
||
Umsetzung der Phase 1 ("SOFORT") gemaess Architektur-Briefing. Alle Arbeitspakete ohne externe Abhaengigkeiten implementiert.
|
||
|
||
### 1. Prisma Schema + SQL Migration
|
||
|
||
**Neue Enums (7):** `ContactSource`, `EntityStatus`, `CompanySize`, `OwnerRole`, `LostReason`, `EmailType`, `PhoneType` + `FOLLOWUP` zu ActivityType.
|
||
|
||
**Neue Felder auf Contact:** `linkedinUrl`, `birthday`, `source` (ContactSource), `department`, `status` (EntityStatus).
|
||
|
||
**Neue Felder auf Company:** `vatId`, `taxId`, `tradeRegisterNumber`, `registerCourt`, `companySize`, `deliveryStreet/Zip/City/Country`, `dataEnrichedAt`, `dataEnrichedSource`, `status` (EntityStatus).
|
||
|
||
**Neue Felder auf Deal:** `lostReason` (LostReason), `lostReasonText`.
|
||
|
||
**Neue Tabellen (5):** `contact_emails`, `contact_phones`, `contact_owners`, `company_owners`, `deal_owners`.
|
||
|
||
**Migration:** `prisma/migrations/20260312_phase1_schema_expansion/migration.sql` — inkl. Daten-Migration (isActive→status, email/phone→contact_emails/phones, ownerId→company_owners).
|
||
|
||
### 2. Multi-Value E-Mails & Telefone (Breaking Change)
|
||
|
||
Bisherige Einzel-Felder `email`, `phone`, `mobile` bleiben als **deprecated Legacy** erhalten. Neue Multi-Value Tabellen `contact_emails` und `contact_phones` dienen als primaere Datenquelle. Legacy-Felder werden automatisch aus Primary-Eintraegen synchronisiert.
|
||
|
||
**Neue Endpoints/Verhalten:**
|
||
- `POST /contacts` akzeptiert jetzt `emails: [{email, type, isPrimary}]` und `phones: [{phone, type, isPrimary}]`
|
||
- `PATCH /contacts/:id` mit Replace-Strategie (delete all + recreate)
|
||
- Analog fuer Companies
|
||
- Lexware-Import erzeugt automatisch Multi-Value Eintraege
|
||
- `status` Feld (ACTIVE/INACTIVE/BLOCKED) ersetzt `isActive` Boolean — beide werden synchron gehalten
|
||
|
||
**DTOs:**
|
||
- `CreateEmailDto`: `{email, type?: EmailType, isPrimary?: boolean}`
|
||
- `CreatePhoneDto`: `{phone, type?: PhoneType, isPrimary?: boolean}`
|
||
- `EntityStatus`: ACTIVE, INACTIVE, BLOCKED
|
||
- `CompanySize`: SIZE_1_10, SIZE_11_50, SIZE_51_200, SIZE_201_500, SIZE_500_PLUS
|
||
- `ContactSource`: TRADE_FAIR, REFERRAL, WEBSITE, COLD_CALL, IMPORT, BUSINESS_CARD, OTHER
|
||
|
||
### 3. Owner m:n Modell
|
||
|
||
Jeder Contact, Company und Deal hat jetzt ein mehrstufiges Ownership-Modell mit Rollen.
|
||
|
||
**Neue Endpoints:**
|
||
```
|
||
POST /crm/contacts/:id/owners Body: {userId, role?}
|
||
DELETE /crm/contacts/:id/owners/:userId
|
||
|
||
POST /crm/companies/:id/owners Body: {userId, role?}
|
||
DELETE /crm/companies/:id/owners/:userId
|
||
|
||
POST /crm/deals/:id/owners Body: {userId, role?}
|
||
DELETE /crm/deals/:id/owners/:userId
|
||
```
|
||
|
||
**OwnerRole Enum:** OWNER, MEMBER, WATCHER.
|
||
|
||
Bei `create()` wird der erstellende User automatisch als OWNER eingetragen. Upsert-Verhalten: Wenn Owner bereits existiert, wird nur die Rolle aktualisiert.
|
||
|
||
### 4. Lost-Reason bei Deals
|
||
|
||
**Neue Felder:** `lostReason` (Enum: PRICE, TIMING, COMPETITOR, NO_NEED, OTHER), `lostReasonText` (Freitext).
|
||
|
||
**Validierung:**
|
||
- `PATCH /deals/:id` mit `status: 'LOST'` erfordert `lostReason` (im DTO oder bereits gesetzt) — sonst 400 BadRequest.
|
||
- `PATCH /deals/:id` mit `status: 'WON'` loescht automatisch `lostReason` und `lostReasonText`.
|
||
|
||
### 5. Redis Pub/Sub Events
|
||
|
||
**CrmEventPublisher** (global verfuegbar): Publiziert Events auf Redis Channels im Format `app:crm:events:{type}`.
|
||
|
||
**Event-Typen:**
|
||
- `crm.contact.created` — nach Contact-Erstellung
|
||
- `crm.contact.updated` — nach Contact-Update
|
||
- `crm.deal.created` — nach Deal-Erstellung
|
||
- `crm.deal.stage_changed` — bei Stage-Aenderung (payload: previousStageId, newStageId)
|
||
- `crm.deal.won` — bei Deal WON (payload: value, currency)
|
||
- `crm.deal.lost` — bei Deal LOST (payload: lostReason)
|
||
- `crm.activity.due_soon` — Activities faellig in 24h (Cron alle 15 Min)
|
||
|
||
**Event-Payload:**
|
||
```json
|
||
{
|
||
"type": "crm.contact.created",
|
||
"tenantId": "uuid",
|
||
"entityId": "uuid",
|
||
"userId": "uuid",
|
||
"timestamp": "ISO-8601",
|
||
"payload": {}
|
||
}
|
||
```
|
||
|
||
### Response-Aenderungen fuer Frontend
|
||
|
||
Alle Entity-Responses (Contact, Company, Deal) enthalten jetzt zusaetzlich:
|
||
- `emails: [{id, email, type, isPrimary, createdAt}]`
|
||
- `phones: [{id, phone, type, isPrimary, createdAt}]`
|
||
- `owners: [{id, tenantId, userId, role, createdAt}]`
|
||
- `status: 'ACTIVE' | 'INACTIVE' | 'BLOCKED'`
|
||
|
||
Contact zusaetzlich: `linkedinUrl`, `birthday`, `source`, `department`.
|
||
Company zusaetzlich: `vatId`, `taxId`, `tradeRegisterNumber`, `registerCourt`, `companySize`, `deliveryStreet/Zip/City/Country`.
|
||
Deal zusaetzlich: `lostReason`, `lostReasonText`.
|
||
|
||
### Geaenderte Dateien (18) + Neue Dateien (10)
|
||
|
||
Siehe Plan-Datei fuer komplette Dateiliste. Wichtigste neue Dateien:
|
||
- `src/common/dto/contact-info.dto.ts` — Shared DTOs + Enums
|
||
- `src/common/dto/owner.dto.ts` — AddOwnerDto + OwnerRole
|
||
- `src/owners/owners.service.ts` + `owners.module.ts` — Shared Owner CRUD
|
||
- `src/events/crm-event-publisher.service.ts` — CRM Event Publisher
|
||
- `src/events/crm-events.module.ts` — Global Events Module
|
||
- `src/events/activity-due-soon.scheduler.ts` — Cron-Job
|
||
|
||
### TODO fuer Frontend
|
||
|
||
1. `types.ts` um neue Felder erweitern (emails[], phones[], owners[], status, linkedinUrl, birthday, source, department, lostReason, etc.)
|
||
2. Contact/Company Formulare um Multi-Value Email/Phone Eingabe erweitern
|
||
3. Owner-Management UI (Zuweisen/Entfernen von Owners)
|
||
4. Deal-Detail: Lost-Reason Feld bei Status LOST einblenden
|
||
5. EntityStatus Filter in Listen verwenden (statt isActive)
|
||
|
||
---
|
||
|
||
*Bitte neue Eintraege unten anfuegen. Format: `## YYYY-MM-DD | Absender: Betreff`*
|