INSIGHT-MVP/docs/INSIGHT-CRM.md
Thomas Reitz 69f032a3d8 docs: update INSIGHT-CRM.md with architect requirements from Konzept v1.0
Extract and categorize new requirements from updated CLAUDE_BRIEFING.docx
and INSIGHT_Konzept_v1.0.docx. Add comprehensive CRM task breakdown
(Kap 22: full CRM spec, Kap 24: Office 365 integration) with prioritized
action items for the CRM backend developer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 14:39:18 +01:00

64 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

  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

  • Traefik HTTPS-Router: crm-secure Router angelegt mit entrypoints=websecure, tls=true, Priority 100. Deployed in Commit c9e2c4a.

  • Pipeline-Stages bearbeiten: Neuer Endpoint PATCH /crm/pipelines/:id/stages/:stageId hinzugefuegt. 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/: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:

{
  "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-Endpoint
  • 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)

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):

{
  "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

  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

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

  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:

{
  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:

  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 regeneriertprisma 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

{
  "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 deployed
  • 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:

// 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:

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:

-- 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:

+ 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:

+ 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)

Bitte neue Eintraege unten anfuegen. Format: ## YYYY-MM-DD | Absender: Betreff