Bug in lexware-contacts.service.ts:229 — tenantId: String (type) instead of tenantId: user.tenantId (value). Affects importCompany, likely also importContact. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
43 KiB
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):
{
"success": true,
"data": [...],
"pagination": { "page": 1, "pageSize": 25, "total": 42, "totalPages": 2 },
"meta": { "timestamp": "..." }
}
Einzelobjekt:
{
"success": true,
"data": { ... },
"meta": { "timestamp": "..." }
}
Fehler:
{
"success": false,
"error": { "code": "NOT_FOUND", "message": "...", "details": [] },
"meta": { "timestamp": "..." }
}
Annahmen / Abhaengigkeiten ans Backend
-
Contact-Detail liefert Activities mit --
GET /crm/contacts/:idgibt die letzten 10 Aktivitaeten im Feldactivitieszurueck. Das Frontend zeigt diese in der Timeline an. -
Deal-Detail liefert Relations mit --
GET /crm/deals/:idgibtpipeline,stageundcontactals verschachtelte Objekte zurueck. -
Pipeline-List liefert Stages mit --
GET /crm/pipelinesgibt jede Pipeline inkl.stages[]Array zurueck. Das Frontend nutzt diese fuer die Stage-Selektoren im Deal-Formular. -
Deal.value ist ein String -- Decimal kommt als String vom Backend (z.B.
"24000.00"). Das Frontend parst mitparseFloat(). -
Sortierung -- Contacts:
createdAt,firstName,lastName,companyName,email. Deals:createdAt,title,value,expectedCloseDate. -
Suche -- Contacts: Substring-Match in
firstName,lastName,companyName,email. Deals: Substring-Match intitle.
Bekannte Offene Punkte
- Traefik HTTPS-Router fuer CRM: Aktuell hat der CRM-Service nur einen HTTP-Router (
webEntrypoint). Fuer HTTPS (websecure) muesste ein zweiter Router mittls=trueangelegt werden (wie beicore-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
-
Traefik HTTPS-Router:
crm-secureRouter angelegt mitentrypoints=websecure,tls=true, Priority 100. Deployed in Commitc9e2c4a. -
Pipeline-Stages bearbeiten: Neuer Endpoint
PATCH /crm/pipelines/:id/stages/:stageIdhinzugefuegt. Akzeptiert:{ "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/:idmit{ 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:
{
"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
Pipeline-Stages bearbeiten— Frontend nutzt den neuen PATCH-EndpointDealDetail 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-frontendneu 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)
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,isActivedeals[]— Top 10 Vorgaenge mit: alle Deal-Felder +pipeline+stageObjekte_count— Zaehler fuercontactsunddeals
Beispiel-Response (gekuerzt):
{
"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:
{
"company": { "id": "...", "name": "Xinion GmbH", "industry": "Enterprise Software" }
}
Contact-Detail liefert:
{
"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:
{
"company": { "id": "...", "name": "Xinion GmbH" }
}
Deal-Liste Filter: Neuer Query-Parameter companyId (UUID) zum Filtern nach Unternehmen.
Vorschlaege fuer das Frontend
-
Neue Seiten/Routen:
/crm/companies— Unternehmensliste (wie Kontakte, mit Suche/Filter/Paginierung)/crm/companies/:id— Unternehmensdetail (2-Spalten: Info links, Kontakte+Vorgaenge rechts)
-
Sidebar: Neuer NavLink "Unternehmen" in der CRM-Sektion (zwischen Kontakte und Vorgaenge oder davor)
-
Contact-Formular:
companyIdDropdown (Unternehmen-Suche) +positionTextfeld hinzufuegen -
Contact-Liste: Company-Name als Spalte anzeigen (kommt aus
contact.company.name) -
Deal-Formular:
companyIdDropdown (Unternehmen-Suche) hinzufuegen -
Deal-Liste: Company-Name als Spalte anzeigen
-
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
companyIdwird aufnullgesetzt (SetNull) - Pipeline loeschen: Alle verknuepften Vorgaenge werden geloescht (Cascade)
- Kontakt loeschen: Vorgaenge behalten ihre Daten,
contactIdwirdnull(SetNull)
Deployment-Info
- Branch:
feature/crm-service, Commit:56a9ed9 - Prisma Migration
20260310183117_add_companiesangewendet - 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 umcompanyId,company-Relation - api.ts:
companiesApimit 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
- CompaniesPage: Tabelle mit Name, Branche, Stadt, E-Mail, Kontakte-Anzahl, Vorgaenge-Anzahl, Status, Aktionen; Suchfeld (debounced 300ms); Paginierung; Erstellen/Bearbeiten/Loeschen-Modals
- CompanyFormModal: Name*, Branche, E-Mail, Telefon, Website, Adresse (Strasse, PLZ/Stadt, Land), Notizen, Tags (kommasepariert), Aktiv-Checkbox
- 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:
- Kontakt-Verknuepfung: Lexware-Kontakte suchen und mit CRM Companies/Contacts verknuepfen oder importieren
- Beleg-Anzeige: Angebote, Auftragsbestaetigungen, Rechnungen und Gutschriften aus Lexware — anzeigbar am Unternehmen, Kontakt UND am Vorgang
- 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
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:
{
"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[]:
{
"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
-
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
-
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
- Verknuepfte Belege als Tabelle (aus
-
Tags-Integration: "ERP"-Tag in Company/Contact-Formularen hervorheben (z.B. besondere Farbe), da es den automatischen Push nach Lexware aktiviert
-
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 erreichbardown: API-Key konfiguriert aber API nicht erreichbarunconfigured: 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.sqlinprisma/migrations/20260310_add_lexware_integration/ - Neue Tabellen:
lexware_vouchers,deal_vouchers - Neue Felder:
lexware_contact_id,lexware_contact_version,lexware_synced_ataufcompaniesundcontacts
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
- Company/Contact Detail: Lexware-Card mit Status-Badge (Verknuepft/Nicht verknuepft), Such-Button, Sync/Push/Refresh-Buttons, Voucher-Tabelle mit Typ-Filter
- Deal Detail: Belege-Card mit verknuepften Vouchers, Link/Unlink-Funktion, Zugriff auf Company/Contact-Vouchers
- Lexware Search Modal: Debounced Suche (400ms), Anzeige von Name/Email/Adresse, Ein-Klick-Verknuepfung
- CRM Settings: Lexware-Toggle zum Ein-/Ausblenden aller Lexware-Sektionen
- 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 === nullzeigt 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:
{
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.
{
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:
{
"relatedCompanyId": "uuid",
"relationshipTypeId": "uuid",
"notes": "optional"
}
GET-Response (einzelne Beziehung):
{
"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:
{
"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:
- Branchen: CRUD-Tabelle mit Name, Farb-Badge + Color-Picker, Sortier-Pfeile
- Kontotypen: CRUD-Tabelle mit Name, Sortier-Pfeile
- 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 (nachprisma 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:
- Prisma-Migration
20260310_add_lexware_integration— War bereits manuell angewendet (VoucherType Enum existierte), wurde alsappliedmarkiert viaprisma migrate resolve - Prisma-Migration
20260311_add_company_detail_overhaul— Erfolgreich angewendet. Neue Tabellen:industries,account_types,relationship_types,company_relationships,contracts. Aenderungen ancompaniesundactivities. - Prisma-Client regeneriert —
prisma generateim Container ausgefuehrt - 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}
- Seed-Daten geladen fuer beide Tenants (
3fc0e74d-...und11111111-...):- 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
{
"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
Migration auf Server anwenden— Erledigt (beide Migrationen)Container neu bauen und deployen— Frontend + CRM Backend deployedSeed-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-crmContainer laeuft im Dev-Modus mit Volume-Mount. Code-Aenderungen werden automatisch erkannt. - Nach Schema-Aenderungen muss
prisma generateim Container ausgefuehrt werden. - Die
LexwareSyncContent-Komponente wurde als separater Export ausLexwareSyncPage.tsxextrahiert 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
pushundsyncMethoden, falls diese ebenfallstenantIdsetzen
Reproduktion
/crm/settings→ Tab "Lexoffice Sync" → "Import (Lexware → CRM)"- Beliebigen Lexware-Kontakt suchen (z.B. "team")
- Auf "Unternehmen" Button klicken
- → Rote Fehlermeldung: "Import fehlgeschlagen: Request failed with status code 500"
Bitte neue Eintraege unten anfuegen. Format: ## YYYY-MM-DD | Absender: Betreff