# INSIGHT CRM - Kommunikation Frontend <-> Backend Dieses Dokument dient als Kommunikationskanal zwischen dem Frontend- und dem CRM-Backend-Entwickler. --- ## 2026-03-10 | Frontend: Erster Stand der CRM-Integration ### Was wurde umgesetzt Das komplette CRM-Frontend-Modul ist implementiert und auf dem Server deployed (`feature/crm-service` Branch, Commit `c739dce`). #### Neue Dateien (16 Dateien, ~4.800 Zeilen) ``` packages/frontend/src/crm/ types.ts -- Zentrale Interfaces (alle Entitaeten + API-Wrapper) api.ts -- API-Funktionen (Axios, baseURL /api/v1/crm/*) hooks.ts -- React Query Hooks + Query Key Factory contacts/ ContactsPage.tsx + .module.css -- Liste mit Suche, Typ-Filter, Paginierung ContactFormModal.tsx -- Create/Edit Modal (Person/Organisation) ContactDetailPage.tsx + .module.css -- 2-Spalten: Info+Deals links, Aktivitaeten rechts deals/ DealsPage.tsx + .module.css -- Liste mit Pipeline/Stage/Status-Filter DealFormModal.tsx -- Create/Edit mit Kontakt-Suche + Pipeline/Stage-Selektor DealDetailPage.tsx + .module.css -- Detail mit Stage-Fortschrittsbalken pipelines/ PipelinesPage.tsx + .module.css -- Verwaltung mit klappbaren Cards + Stage-Management activities/ ActivityFormModal.tsx -- Formular fuer Notiz/Anruf/E-Mail/Meeting/Aufgabe ``` #### Geaenderte Dateien (3) | Datei | Aenderung | |-------|-----------| | `src/shell/App.tsx` | 5 CRM-Routen (`/crm/contacts`, `/crm/contacts/:id`, `/crm/deals`, `/crm/deals/:id`, `/crm/pipelines`) | | `src/shell/AppLayout.tsx` | CRM-Sektion in Sidebar (aufklappbar, 3 NavLinks mit SVG-Icons) | | `vite.config.ts` | Proxy `/api/v1/crm` -> `localhost:3100` fuer lokale Entwicklung | ### Welche API-Endpoints werden genutzt | Modul | Endpoints | Methoden | |-------|-----------|----------| | Contacts | `/crm/contacts`, `/crm/contacts/:id` | GET (list+detail), POST, PATCH, DELETE | | Deals | `/crm/deals`, `/crm/deals/:id` | GET (list+detail), POST, PATCH, DELETE | | Pipelines | `/crm/pipelines`, `/crm/pipelines/:id`, `/crm/pipelines/:id/stages`, `/crm/pipelines/:id/stages/:stageId` | GET (list+detail), POST, PATCH, DELETE | | Activities | `/crm/activities`, `/crm/activities/:id` | GET (list), POST, PATCH, DELETE | ### Erwartete Response-Formate **Liste (paginiert):** ```json { "success": true, "data": [...], "pagination": { "page": 1, "pageSize": 25, "total": 42, "totalPages": 2 }, "meta": { "timestamp": "..." } } ``` **Einzelobjekt:** ```json { "success": true, "data": { ... }, "meta": { "timestamp": "..." } } ``` **Fehler:** ```json { "success": false, "error": { "code": "NOT_FOUND", "message": "...", "details": [] }, "meta": { "timestamp": "..." } } ``` ### Annahmen / Abhaengigkeiten ans Backend 1. **Contact-Detail liefert Activities mit** -- `GET /crm/contacts/:id` gibt die letzten 10 Aktivitaeten im Feld `activities` zurueck. Das Frontend zeigt diese in der Timeline an. 2. **Deal-Detail liefert Relations mit** -- `GET /crm/deals/:id` gibt `pipeline`, `stage` und `contact` als verschachtelte Objekte zurueck. 3. **Pipeline-List liefert Stages mit** -- `GET /crm/pipelines` gibt jede Pipeline inkl. `stages[]` Array zurueck. Das Frontend nutzt diese fuer die Stage-Selektoren im Deal-Formular. 4. **Deal.value ist ein String** -- Decimal kommt als String vom Backend (z.B. `"24000.00"`). Das Frontend parst mit `parseFloat()`. 5. **Sortierung** -- Contacts: `createdAt`, `firstName`, `lastName`, `companyName`, `email`. Deals: `createdAt`, `title`, `value`, `expectedCloseDate`. 6. **Suche** -- Contacts: Substring-Match in `firstName`, `lastName`, `companyName`, `email`. Deals: Substring-Match in `title`. ### Bekannte Offene Punkte - [ ] **Traefik HTTPS-Router fuer CRM**: Aktuell hat der CRM-Service nur einen HTTP-Router (`web` Entrypoint). Fuer HTTPS (`websecure`) muesste ein zweiter Router mit `tls=true` angelegt werden (wie bei `core-api-secure`). - [ ] **Activity-Liste komplett laden**: Die Contact-Detail-Seite zeigt nur die letzten 10 Aktivitaeten (aus dem Contact-Objekt). Fuer eine "Alle anzeigen"-Funktion wuerde ein separater `GET /crm/activities?contactId=...` Aufruf benoetigt (Hook existiert bereits). - [ ] **Kanban-Board fuer Deals**: Aktuell nur Tabellenansicht. Ein Drag-and-Drop Kanban-Board waere ein sinnvolles Feature fuer spaeter. - [ ] **Pipeline-Stages bearbeiten**: Derzeit kann man Stages nur hinzufuegen und loeschen, nicht den Namen/Farbe/Reihenfolge aendern (kein PATCH-Endpoint fuer Stages vorhanden). --- ## 2026-03-10 | Backend: Antwort auf Frontend-Integrationsbericht ### Status der Annahmen Alle 6 Annahmen des Frontend-Entwicklers sind korrekt und werden vom Backend unterstuetzt: | # | Annahme | Status | Hinweise | |---|---------|--------|----------| | 1 | Contact-Detail liefert Activities mit | **Bestaetigt** | `GET /crm/contacts/:id` liefert `activities[]` (letzte 10, sortiert nach `createdAt desc`) | | 2 | Deal-Detail liefert Relations mit | **Bestaetigt** | `GET /crm/deals/:id` liefert `pipeline` (inkl. aller Stages!), `stage`, `contact` | | 3 | Pipeline-List liefert Stages mit | **Bestaetigt** | `GET /crm/pipelines` liefert jede Pipeline inkl. `stages[]` + `_count.deals` | | 4 | Deal.value ist ein String | **Bestaetigt** | Decimal wird als String geliefert (z.B. `"24000"`) | | 5 | Sortierung | **Bestaetigt** | Contacts: `createdAt`, `firstName`, `lastName`, `companyName`, `email`. Deals: `createdAt`, `updatedAt`, `title`, `value`, `expectedCloseDate` | | 6 | Suche | **Bestaetigt** | Contacts: case-insensitive Substring in `firstName`, `lastName`, `companyName`, `email`. Deals: case-insensitive Substring in `title` | ### Erledigte Offene Punkte - [x] **Traefik HTTPS-Router**: `crm-secure` Router angelegt mit `entrypoints=websecure`, `tls=true`, Priority 100. Deployed in Commit `c9e2c4a`. - [x] **Pipeline-Stages bearbeiten**: Neuer Endpoint `PATCH /crm/pipelines/:id/stages/:stageId` hinzugefuegt. Akzeptiert: ```json { "name": "Neuer Name", // optional, max 200 "sortOrder": 2, // optional, int >= 0 "color": "#EF4444" // optional, Hex #RRGGBB } ``` ### Noch offene Punkte (Backend-Sicht) - [ ] **Activity-Liste komplett laden**: `GET /crm/activities?contactId=...` ist bereits implementiert und funktioniert. Das Frontend kann den bestehenden Hook direkt nutzen. - [ ] **Kanban-Board**: Backend-seitig kein Handlungsbedarf - `PATCH /crm/deals/:id` mit `{ stageId: "..." }` genuegt fuer Drag & Drop. ### Hinweis zu Pipeline-Detail bei Deals `GET /crm/deals/:id` liefert die Pipeline **inklusive aller Stages** (nicht nur die aktuelle Stage). Das ist nuetzlich fuer den Stage-Fortschrittsbalken im `DealDetailPage.tsx`: ```json { "pipeline": { "id": "...", "name": "Standard Sales", "stages": [ { "id": "...", "name": "Qualifizierung", "sortOrder": 0, "color": "#3B82F6" }, { "id": "...", "name": "Angebot", "sortOrder": 1, "color": "#F59E0B" }, { "id": "...", "name": "Verhandlung", "sortOrder": 2, "color": "#EF4444" }, { "id": "...", "name": "Abschluss", "sortOrder": 3, "color": "#10B981" } ] }, "stage": { "id": "...", "name": "Angebot", "color": "#F59E0B" } } ``` ### Deployment-Info - Branch: `feature/crm-service`, Commit: `c9e2c4a` - Server: insight-dev-01 (172.20.10.59) - Container: `insight-crm` (neu gebuildet und deployed) - Swagger-Docs: http://172.20.10.59/api/v1/crm/docs/ --- ## 2026-03-10 | Frontend: Update nach Backend-Feedback ### Umgesetzte Verbesserungen Basierend auf der Backend-Antwort wurden folgende Aenderungen umgesetzt (Commit `0b78160`): #### 1. Pipeline-Stages inline bearbeitbar Die `PipelinesPage.tsx` nutzt jetzt den neuen `PATCH /crm/pipelines/:id/stages/:stageId` Endpoint: - Jede Stage kann per **Doppelklick** oder **Stift-Icon** bearbeitet werden - Inline-Formular mit Name-Input und Color-Picker - Speichern mit Enter oder Haekchen, Abbrechen mit Escape oder X - Neuer Hook: `useUpdateStage()` mit automatischer Query-Invalidierung #### 2. DealDetailPage optimiert `DealDetailPage.tsx` nutzt jetzt **direkt `deal.pipeline.stages`** aus dem Deal-Objekt fuer den Stage-Fortschrittsbalken. Der vorherige separate `usePipeline()` API-Call wurde entfernt. #### 3. UI-Umbenennung: "Deals" -> "Vorgaenge" Alle user-facing Strings wurden umbenannt: - Sidebar: "Deals" -> "Vorgaenge" - Seitentitel: "Deals" -> "Vorgaenge" - Buttons: "Neuer Deal" -> "Neuer Vorgang" - Modals: "Deal bearbeiten/loeschen" -> "Vorgang bearbeiten/loeschen" - Fehlermeldungen und Leer-Zustaende angepasst **Hinweis**: API-Pfade (`/crm/deals`), TypeScript-Typen (`Deal`, `DealStatus`) und Komponentennamen (`DealsPage`, `DealFormModal`) bleiben unveraendert — nur die UI-Texte wurden geaendert. ### Aktualisierte Offene Punkte - [x] ~~Pipeline-Stages bearbeiten~~ — Frontend nutzt den neuen PATCH-Endpoint - [x] ~~DealDetail separater Pipeline-Call~~ — Nutzt jetzt deal.pipeline.stages - [ ] **Activity-Liste komplett laden** — Hook existiert, UI-Button "Alle anzeigen" fehlt noch - [ ] **Kanban-Board fuer Vorgaenge** — Feature fuer spaeter geplant ### Deployment-Info - Branch: `feature/crm-service`, Commit: `0b78160` - Server: insight-dev-01 (172.20.10.59) - Container: `insight-frontend` neu gebaut und deployed --- ## 2026-03-10 | Backend: Neues Company-Modul + Aenderungen an Contact und Deal ### Neue Entity: Company (Unternehmen) Unternehmen sind jetzt als eigenstaendige Entity implementiert. Sie dienen als uebergeordnete Ebene fuer Kontakte und Vorgaenge. Ein Unternehmen kann mehrere Kontakte und Vorgaenge haben. ### Neue API-Endpoints | Methode | Pfad | Beschreibung | |---------|------|-------------| | GET | `/crm/companies` | Liste (paginiert, filterbar, suchbar) | | POST | `/crm/companies` | Unternehmen erstellen | | GET | `/crm/companies/:id` | Detail (inkl. Kontakte + Vorgaenge) | | PATCH | `/crm/companies/:id` | Unternehmen aktualisieren | | DELETE | `/crm/companies/:id` | Unternehmen loeschen | ### Company-Objekt (Felder) ```typescript interface Company { id: string; // UUID tenantId: string; // UUID name: string; // Pflichtfeld, max 200 industry?: string; // max 100 email?: string; // max 255 phone?: string; // max 50 website?: string; // max 500 street?: string; // max 200 zip?: string; // max 20 city?: string; // max 100 state?: string; // max 100 country?: string; // Default "DE", 2-Zeichen ISO notes?: string; // Freitext tags: string[]; // Default [] isActive: boolean; // Default true createdBy: string; // UUID updatedBy?: string; // UUID createdAt: string; // ISO DateTime updatedAt: string; // ISO DateTime _count: { contacts: number; deals: number }; } ``` ### Company-Liste: Query-Parameter | Parameter | Typ | Beschreibung | |-----------|-----|-------------| | `page` | number | Seite (default: 1) | | `pageSize` | number | Eintraege pro Seite (default: 25) | | `search` | string | Substring-Match in `name`, `industry`, `email`, `city` | | `industry` | string | Exakter Filter nach Branche | | `sort` | string | `createdAt`, `updatedAt`, `name`, `industry`, `city` | | `order` | string | `asc` oder `desc` (default: `desc`) | ### Company-Detail: Verschachtelte Daten `GET /crm/companies/:id` liefert zusaetzlich: - `contacts[]` — Top 20 aktive Kontakte mit: `id`, `firstName`, `lastName`, `email`, `phone`, `position`, `isActive` - `deals[]` — Top 10 Vorgaenge mit: alle Deal-Felder + `pipeline` + `stage` Objekte - `_count` — Zaehler fuer `contacts` und `deals` Beispiel-Response (gekuerzt): ```json { "data": { "id": "...", "name": "Xinion GmbH", "industry": "Enterprise Software", "contacts": [ { "id": "...", "firstName": "Thomas", "lastName": "Reitz", "email": "treitz@xinion.de", "position": "Geschaeftsfuehrer", "isActive": true } ], "deals": [ { "id": "...", "title": "INSIGHT Platform Lizenz", "value": "48000", "status": "OPEN", "pipeline": { "id": "...", "name": "Standard Sales" }, "stage": { "id": "...", "name": "Qualifizierung", "color": "#3B82F6" } } ], "_count": { "contacts": 1, "deals": 1 } } } ``` ### Aenderungen an Contact Kontakte haben zwei neue Felder: | Feld | Typ | Beschreibung | |------|-----|-------------| | `companyId` | string? (UUID) | Verknuepfung zum Unternehmen (optional) | | `position` | string? | Position/Rolle im Unternehmen (max 200) | **Contact-Liste** liefert jetzt zusaetzlich: ```json { "company": { "id": "...", "name": "Xinion GmbH", "industry": "Enterprise Software" } } ``` **Contact-Detail** liefert: ```json { "company": { "id": "...", "name": "Xinion GmbH", "industry": "Enterprise Software", "city": "Berlin", "website": "https://xinion.de" } } ``` ### Aenderungen an Deal (Vorgang) Vorgaenge haben ein neues Feld: | Feld | Typ | Beschreibung | |------|-----|-------------| | `companyId` | string? (UUID) | Verknuepfung zum Unternehmen (optional) | **Deal-Liste und Detail** liefern jetzt zusaetzlich: ```json { "company": { "id": "...", "name": "Xinion GmbH" } } ``` **Deal-Liste Filter**: Neuer Query-Parameter `companyId` (UUID) zum Filtern nach Unternehmen. ### Vorschlaege fuer das Frontend 1. **Neue Seiten/Routen**: - `/crm/companies` — Unternehmensliste (wie Kontakte, mit Suche/Filter/Paginierung) - `/crm/companies/:id` — Unternehmensdetail (2-Spalten: Info links, Kontakte+Vorgaenge rechts) 2. **Sidebar**: Neuer NavLink "Unternehmen" in der CRM-Sektion (zwischen Kontakte und Vorgaenge oder davor) 3. **Contact-Formular**: `companyId` Dropdown (Unternehmen-Suche) + `position` Textfeld hinzufuegen 4. **Contact-Liste**: Company-Name als Spalte anzeigen (kommt aus `contact.company.name`) 5. **Deal-Formular**: `companyId` Dropdown (Unternehmen-Suche) hinzufuegen 6. **Deal-Liste**: Company-Name als Spalte anzeigen 7. **Verlinkung**: Company-Name in Contact- und Deal-Listen als Link zu `/crm/companies/:id` ### Swagger-Aenderung Der Swagger-Tag fuer Deals/Vorgaenge ist jetzt `Vorgaenge (Deals)` statt `Deals`. ### Datenbank-Verhalten bei Loeschung - **Company loeschen**: Kontakte und Vorgaenge behalten ihre Daten, aber `companyId` wird auf `null` gesetzt (SetNull) - **Pipeline loeschen**: Alle verknuepften Vorgaenge werden geloescht (Cascade) - **Kontakt loeschen**: Vorgaenge behalten ihre Daten, `contactId` wird `null` (SetNull) ### Deployment-Info - Branch: `feature/crm-service`, Commit: `56a9ed9` - Prisma Migration `20260310183117_add_companies` angewendet - Alle Endpoints getestet und funktionsfaehig - Swagger-Docs aktualisiert: http://172.20.10.59/api/v1/crm/docs/ --- ## 2026-03-10 | Frontend: Company-Modul implementiert ### Was wurde umgesetzt Das komplette Company-Frontend-Modul ist implementiert und deployed (Commit `36f571f`). #### Neue Dateien (5 Dateien) ``` packages/frontend/src/crm/companies/ CompaniesPage.tsx + .module.css -- Liste mit Suche, Paginierung, CRUD-Modals CompanyFormModal.tsx -- Create/Edit Modal (Name, Branche, Kontakt, Adresse, Tags) CompanyDetailPage.tsx + .module.css -- 2-Spalten: Info links, Kontakte+Vorgaenge rechts ``` #### Geaenderte Dateien (11 Dateien) - **types.ts**: Company-Interface, CreateCompanyPayload, UpdateCompanyPayload, CompaniesQueryParams; Contact erweitert um `companyId`, `position`, `company`-Relation; Deal erweitert um `companyId`, `company`-Relation - **api.ts**: `companiesApi` mit 5 CRUD-Methoden - **hooks.ts**: `crmKeys.companies` + 5 Hooks (useCompanies, useCompany, useCreateCompany, useUpdateCompany, useDeleteCompany); Cross-Invalidation (Contact/Deal-Mutations invalidieren Companies-Cache) - **AppLayout.tsx**: NavLink "Unternehmen" mit Gebaeude-Icon zwischen Kontakte und Vorgaenge - **App.tsx**: 2 Routen (`/crm/companies`, `/crm/companies/:id`) - **ContactsPage.tsx**: Neue Spalte "Unternehmen" mit Link zu Company - **ContactFormModal.tsx**: Unternehmen-Suche (debounced Dropdown) + Position-Feld - **ContactDetailPage.tsx**: Unternehmen-Link + Position in Info-Card - **DealsPage.tsx**: Neue Spalte "Unternehmen" mit Link zu Company - **DealFormModal.tsx**: Unternehmen-Suche (debounced Dropdown) - **DealDetailPage.tsx**: Unternehmen-Link in Info-Card ### Funktionsumfang Company-Modul 1. **CompaniesPage**: Tabelle mit Name, Branche, Stadt, E-Mail, Kontakte-Anzahl, Vorgaenge-Anzahl, Status, Aktionen; Suchfeld (debounced 300ms); Paginierung; Erstellen/Bearbeiten/Loeschen-Modals 2. **CompanyFormModal**: Name*, Branche, E-Mail, Telefon, Website, Adresse (Strasse, PLZ/Stadt, Land), Notizen, Tags (kommasepariert), Aktiv-Checkbox 3. **CompanyDetailPage**: Links Info-Card (alle Felder + Tags + Notizen), Rechts Kontakte-Tabelle + Vorgaenge-Tabelle mit Navigation zu Detail-Seiten ### Company-Integration in bestehende Module - **Kontakte**: Unternehmen-Spalte in Liste, Dropdown-Suche im Formular, Link+Position im Detail - **Vorgaenge**: Unternehmen-Spalte in Liste, Dropdown-Suche im Formular, Link im Detail - Pattern: Identisch zur Kontakt-Suche in DealFormModal (debounced, dropdown, click-outside) ### Deployment - Branch: `feature/crm-service`, Commit: `36f571f` - TypeScript-Check + Build: erfolgreich - Frontend Container neu gebaut und deployed --- ## 2026-03-10 | Backend: Lexware Office Integration ### Ueberblick Der CRM-Service ist jetzt mit Lexware Office (Buchhaltung/ERP) integriert. Drei Hauptfunktionen: 1. **Kontakt-Verknuepfung**: Lexware-Kontakte suchen und mit CRM Companies/Contacts verknuepfen oder importieren 2. **Beleg-Anzeige**: Angebote, Auftragsbestaetigungen, Rechnungen und Gutschriften aus Lexware — anzeigbar am Unternehmen, Kontakt UND am Vorgang 3. **ERP-Push**: CRM-Entitaeten mit Tag "ERP" werden automatisch nach Lexware synchronisiert ### Neue API-Endpoints: Lexware Kontakte | Methode | Pfad | Beschreibung | |---------|------|-------------| | GET | `/crm/lexware/contacts/search?name=&email=` | Lexware-Kontakte suchen (Proxy zur Lexware API) | | POST | `/crm/lexware/contacts/link-company` | Lexware-Kontakt mit CRM Company verknuepfen | | POST | `/crm/lexware/contacts/link-contact` | Lexware-Kontakt mit CRM Contact verknuepfen | | DELETE | `/crm/lexware/contacts/unlink-company/:companyId` | Verknuepfung Company <-> Lexware loesen | | DELETE | `/crm/lexware/contacts/unlink-contact/:contactId` | Verknuepfung Contact <-> Lexware loesen | | POST | `/crm/lexware/contacts/import-company` | Neue CRM Company aus Lexware-Daten erstellen | | POST | `/crm/lexware/contacts/import-contact` | Neuen CRM Contact aus Lexware-Daten erstellen | | POST | `/crm/lexware/contacts/push/:entityType/:entityId` | CRM-Entitaet nach Lexware pushen (company/contact) | | POST | `/crm/lexware/contacts/sync/:entityType/:entityId` | Lexware-Daten in CRM aktualisieren | ### Neue API-Endpoints: Lexware Belege (Vouchers) | Methode | Pfad | Beschreibung | |---------|------|-------------| | GET | `/crm/lexware/vouchers/company/:companyId` | Belege fuer Unternehmen (gecacht) | | GET | `/crm/lexware/vouchers/contact/:contactId` | Belege fuer Kontakt (gecacht) | | GET | `/crm/lexware/vouchers/deal/:dealId` | Belege fuer Vorgang (via DealVoucher) | | POST | `/crm/lexware/vouchers/deal/:dealId/link` | Beleg mit Vorgang verknuepfen | | DELETE | `/crm/lexware/vouchers/deal/:dealId/unlink/:voucherId` | Beleg-Verknuepfung loesen | | POST | `/crm/lexware/vouchers/refresh/company/:companyId` | Beleg-Cache manuell aktualisieren | | POST | `/crm/lexware/vouchers/refresh/contact/:contactId` | Beleg-Cache manuell aktualisieren | ### Beleg-Filter Query-Parameter | Parameter | Typ | Beschreibung | |-----------|-----|-------------| | `voucherType` | string | `QUOTATION`, `ORDER_CONFIRMATION`, `INVOICE`, `CREDIT_NOTE` | | `voucherStatus` | string | Freitext-Filter nach Beleg-Status | | `page` | number | Seite (default: 1) | | `pageSize` | number | Eintraege pro Seite (default: 20) | ### LexwareVoucher-Objekt ```typescript interface LexwareVoucher { id: string; // CRM-interne UUID voucherType: 'QUOTATION' | 'ORDER_CONFIRMATION' | 'INVOICE' | 'CREDIT_NOTE'; voucherNumber?: string; // z.B. "RE-2025-001" voucherDate?: string; // ISO DateTime voucherStatus?: string; // z.B. "open", "paid", "overdue" totalGrossAmount?: string; // Decimal als String, z.B. "1190.00" totalNetAmount?: string; totalTaxAmount?: string; currency: string; // Default "EUR" title?: string; lineItemsCount?: number; lineItemsJson?: string; // JSON-String mit Positionen lexwareDeepLink?: string; // Link direkt zu Lexware Office fetchedAt: string; // Wann zuletzt aus Lexware geholt } ``` ### Aenderungen an bestehenden Responses **Company-Objekt** hat jetzt zusaetzliche Felder: ```json { "lexwareContactId": "abc-123", // null wenn nicht verknuepft "lexwareContactVersion": 3, // Optimistic Locking "lexwareSyncedAt": "2026-03-10...", // Letzter Sync "_count": { "contacts": 5, "deals": 2, "lexwareVouchers": 12 } } ``` **Contact-Objekt**: Identische neue Felder wie Company. **Deal-Detail** liefert jetzt zusaetzlich `dealVouchers[]`: ```json { "dealVouchers": [ { "id": "...", "linkedAt": "2026-03-10...", "voucher": { "id": "...", "voucherType": "INVOICE", "voucherNumber": "RE-2025-001", "voucherDate": "2025-12-15...", "voucherStatus": "paid", "totalGrossAmount": "1190.00", "currency": "EUR", "title": "Lizenzgebuehr Q4", "lexwareDeepLink": "https://app.lexware.de/permalink/..." } } ] } ``` ### Vorschlaege fuer das Frontend 1. **Company/Contact-Detail**: Tab oder Sektion "Lexware" mit: - Status-Badge: "Verknuepft" (gruen) / "Nicht verknuepft" (grau) - Button "Lexware-Kontakt suchen & verknuepfen" → Modal mit Suchfeld - Button "Verknuepfung loesen" - Button "Belege aktualisieren" (Refresh-Icon) - Beleg-Tabelle: Typ, Nummer, Datum, Status, Brutto-Betrag, Link zu Lexware 2. **Deal-Detail**: Sektion "Belege" mit: - Verknuepfte Belege als Tabelle (aus `dealVouchers`) - Button "Beleg verknuepfen" → Dropdown/Modal mit verfuegbaren Belegen des Unternehmens/Kontakts - Jeder Beleg hat einen externen Link zu Lexware Office 3. **Tags-Integration**: "ERP"-Tag in Company/Contact-Formularen hervorheben (z.B. besondere Farbe), da es den automatischen Push nach Lexware aktiviert 4. **VoucherType Labels** fuer die UI: - `QUOTATION` → "Angebot" - `ORDER_CONFIRMATION` → "Auftragsbestaetigung" - `INVOICE` → "Rechnung" - `CREDIT_NOTE` → "Gutschrift" ### Cron-Jobs (automatisch im Hintergrund) - **Beleg-Sync**: Alle 4 Stunden werden Belege fuer alle verknuepften Entitaeten aus Lexware geholt - **ERP-Push**: Alle 30 Minuten werden Companies/Contacts mit "ERP"-Tag nach Lexware gepusht ### Health Check `GET /health` zeigt jetzt `"lexware": "up"|"down"|"unconfigured"`: - `up`: Lexware API erreichbar - `down`: API-Key konfiguriert aber API nicht erreichbar - `unconfigured`: Kein API-Key gesetzt (kein Fehler, Modul einfach deaktiviert) ### Deployment-Hinweise - Neue Env-Variable auf Server: `LEXWARE_API_KEY` (in `.env`) - DB-Migration noetig: `migration.sql` in `prisma/migrations/20260310_add_lexware_integration/` - Neue Tabellen: `lexware_vouchers`, `deal_vouchers` - Neue Felder: `lexware_contact_id`, `lexware_contact_version`, `lexware_synced_at` auf `companies` und `contacts` --- *Bitte neue Eintraege unten anfuegen. Format: `## YYYY-MM-DD | Absender: Betreff`*