# INSIGHT MVP - Aenderungsprotokoll

## Stand: 2026-03-13

### Aktueller Sprint: CRM Phase 3 — Kanban-Board + Microsoft 365 OAuth-Integration (Feature-Branch: feature/crm-service)

---

### Aenderungen 2026-03-13 (18): Experten-Profil PDF-Export — 3 weitere Korrekturen

#### Backend (Core Service)
- `expert-profile/expert-profile.service.ts` — `getExportData()`: Anhänge jetzt inkludiert (`attachments: { orderBy: { createdAt: 'asc' } }`)
- `expert-profile/profile-export.service.ts` — PDF-Export:
  - Neues `ExportAttachment` Interface + `attachments[]` in `ExportData`
  - Tasks: Bullet `•` wird nur noch hinzugefügt, wenn Original-Zeile bereits ein Aufzählungszeichen (`•`, `\u2022`) oder Nummerierung (`1.`) enthält — kein spurious Bullet für Plaintext-Zeilen
  - Zertifizierungen: Schriftgröße reduziert (Titel 10pt→9pt, Aussteller 9pt→8pt, Abstände angepasst) — passt jetzt zur linken Spalte
  - Zertifizierungen: Timeline-Linienlänge 40→32px
  - Anhänge: Bild-Anhänge (image/jpeg, image/png, etc.) werden als zusätzliche Seiten ans PDF angehängt (Überschrift "ANLAGE: dateiname" + Bild; PDFs + andere Formate werden übersprungen)
- `expert-profile/profile-export.service.ts` — DOCX-Export:
  - Tasks: Gleiche Bullet-Fix-Logik (nur Bullet wenn Original-Zeile Bullet/Nummer hat)

#### TypeScript
- `npx tsc --noEmit` in packages/core-service: 0 Fehler

#### Deployment-Hinweis (Schritt 18)
- Rebuild + Restart: nur core-service

---

### Aenderungen 2026-03-13 (17): Experten-Profil PDF-Export — 5 Korrekturen

#### Backend (Core Service)
- `expert-profile/profile-export.service.ts` — PDF-Export verbessert:
  - Neue Helper-Methoden `stripMarkdown()` und `normalizeTaskLine()`
  - Zeitraum fett (Helvetica-Bold, #555555)
  - Firmenname + Branche fett (Helvetica-Bold, #333333)
  - Tasks: Markdown-Marker (**bold**, *italic*, __u__) werden vor Ausgabe entfernt
  - Sprachen/Erfahrung: Abstand 14px → 11px, Sektion-Gap 8px → 4px
  - Zertifizierungen in rechte Spalte verschoben (nach Berufserfahrung)
  - Return-Typ: `Promise<{ buffer, firstName, lastName }>` (war: `Promise<Buffer>`)
- `expert-profile/profile-export.service.ts` — DOCX analog angepasst:
  - Zeitraum + Firma fett; normalizeTaskLine() fuer Tasks; Zertifizierungen in rightParagraphs
- `expert-profile/expert-profile.controller.ts`:
  - PDF: `Vorname_Nachname_CV.pdf`, DOCX: `Vorname_Nachname_CV.docx`
  - RFC-5987 `filename*=UTF-8''...` fuer Umlaut-Dateinamen

#### TypeScript
- `npx tsc --noEmit` in packages/core-service: 0 Fehler

#### Deployment-Hinweis (Schritt 17)
- Rebuild + Restart: nur core-service

---

### Aenderungen 2026-03-13 (16): Global Admin — Login-Screen-Branding (Hintergrund + Logo)

#### Backend (Core Service)
- `settings/settings.controller.ts` — `GET /settings/branding` + `POST /settings/branding` erweitert:
  - 4 neue Felder: `loginBgType` ('gradient'|'solid'|'image'), `loginBgColor1`, `loginBgColor2`, `loginBgImage`
  - `loginBgImage` Groeßen-Validierung: max. 2MB
  - `loginBgType` Enum-Validierung: nur erlaubte Werte werden gespeichert
  - Rueckwaertskompatibel: bestehende Redis-Daten ohne neue Felder liefern `null`-Defaults

#### Frontend
- `admin/AdminCustomizePage.tsx` — Neuer Card-Block "Login-Hintergrund":
  - Typ-Toggle: Farbverlauf / Einfarbig / Hintergrundbild
  - Farbverlauf: Zwei Farbpicker (Von/Bis) mit Hex-Eingabe
  - Einfarbig: Ein Farbpicker mit Hex-Eingabe
  - Hintergrundbild: Datei-Upload (max. 2MB, nur Bilder), Entfernen-Button
  - Live-Vorschau: Mini-Login-Mockup (200x120px) mit aktuellem Hintergrund und Logo
- `auth/LoginPage.tsx` — Dynamisches Branding:
  - `useQuery(['settings', 'branding'])` — oeffentlicher Endpoint, kein Auth noetig
  - `useMemo` → `containerStyle`: leitet CSS `background` aus `loginBgType` ab
  - Logo-Anzeige: zeigt `<img>` mit `branding.logo` statt hardcoded "INSIGHT"-Text
- `auth/LoginPage.module.css` — Neue Klasse `.logoImage` (max-width 180px, object-fit contain)

#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
- `npx tsc --noEmit` in packages/core-service: 0 Fehler

#### Deployment-Hinweis (Schritt 16)
- Rebuild + Restart: core-service (Backend) und frontend
- Kein Datenbankschema-Aenderung, keine Migration noetig (nur Redis)

---

### Aenderungen 2026-03-13 (15): Experten-Profil – BulletEditor Fett/Kursiv/Unterstrichen + Aufgaben-Anzeige

#### Frontend
- `profile/sections/ProjectModal.tsx` — BulletEditor: Inline-Formatierungen
  - Neue Hilfsfunktion `blWrapFormat()` auf Modul-Ebene: Wraps/Unwraps markierten Text mit Marker-Paar; kein Text markiert → Marker-Paar einsetzen (Cursor dazwischen); Text bereits gewrapped → aushakenlen
  - Toolbar: Neuer Button "B" (Fett, `**text**`), "I" (Kursiv, `*text*`), "U" (Unterstrichen, `__text__`)
  - Keyboard-Shortcuts: Strg+B, Strg+I, Strg+U (Mac: Cmd+B/I/U)
- `profile/sections/ProjectsSection.tsx` — Aufgaben-Anzeige mit Markdown-Renderer
  - Neue Funktion `renderInline()`: wandelt **bold**, *italic*, __underline__ Marker in React-Elemente um (regex-basiert, kein XSS, kein dangerouslySetInnerHTML)
  - Neue Komponente `<RichText text={...}>`: Rendert Aufgaben-Text Zeile fuer Zeile mit Bullet-Punkt-Layout, nummerierter Liste, Einrueckung und Inline-Formatierung
  - Projekt-Listenelement: zeigt Aufgaben-Text unterhalb von Taetikeit/Firma an (separator border-top) — nur wenn Aufgaben vorhanden
  - Neues Layout `entryItemExpanded + entryItemRow` (column statt row wenn Aufgaben vorhanden)
- `profile/ExpertProfileTab.module.css` — Neue Klassen: `.bulletBtnBold`, `.bulletBtnItalic`, `.bulletBtnUnderline`; `.entryItemExpanded`, `.entryItemRow`, `.entryTasks`; `.richText`, `.richTextLine`, `.richTextBullet`, `.richTextNum`, `.richTextBlank`

#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler

#### Deployment-Hinweis (Schritt 15)
- Rebuild + Restart: nur frontend

---

### Aenderungen 2026-03-13 (14): Experten-Profil – BulletEditor mit nummerierter Liste + Tab-Einrueckung

#### Frontend
- `profile/sections/ProjectModal.tsx` — BulletEditor erweitert:
  - Neuer Button "1. Liste": toggled nummerierte Liste (1. 2. 3.) ein/aus; konvertiert auch Bullet→Nummeriert und umgekehrt
  - Tab-Taste: rueckt aktuelle Zeile um 2 Leerzeichen ein; Shift+Tab nimmt sie wieder raus
  - Enter in nummerierter Liste: naechste Zeile erhaelt automatisch naechste Zahl (z.B. "3. " → "4. ")
  - Enter in leerer Listenelement: beendet die Liste (Bullet oder Nummeriert)
  - Enter beruecksichtigt Einrueckung (eingerueckte "  • " → naechste Zeile ebenfalls "  • ")
  - Modul-Level-Hilfsfunktion `blLineAt()`: parst Zeile an Cursor-Position
  - Toolbar-Hinweistext: "Tab = einruecken · Shift+Tab = ausruecken"
- `profile/ExpertProfileTab.module.css` — `.bulletToolbarSep` (vertikale Linie), `.bulletToolbarHint` (Hinweistext)

#### Deployment-Hinweis (Schritt 14)
- Rebuild + Restart: nur frontend

---

### Aenderungen 2026-03-13 (13): Experten-Profil – Bullet-Editor fuer Aufgaben + Popup-Backdrop deaktiviert

#### Frontend
- `components/Modal.tsx` — `onClick={onClose}` vom Overlay-div entfernt: Popups schliessen sich nicht mehr durch Klick neben das Popup (betrifft alle Modal-Instanzen im Projekt)
- `components/Drawer.tsx` — Gleiches: Drawer schliesst sich nicht mehr durch Klick auf den Backdrop
- `profile/sections/ProjectModal.tsx` — Neue lokale `BulletEditor`-Komponente (keine externe Library): Toolbar mit "☰ Liste"-Button (toggled `• ` am Zeilenanfang ein/aus), automatische Aufzaehlungsfortsetzung beim Enter-Druck, leere Bullet-Zeile `• ` + Enter beendet die Liste; ersetzte die einfache `<textarea>` im Feld "Aufgaben"
- `profile/ExpertProfileTab.module.css` — Neue CSS-Klassen: `.bulletEditor`, `.bulletToolbar`, `.bulletBtn`, `.bulletEditorTextarea`, `.bulletBtn:hover`; Editor-Container mit `border + :focus-within`-Ring analog zu Input-Feldern; Textarea innerhalb des Editors hat keinen eigenen Rand (verhindert doppelten Rahmen)

#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler

#### Deployment-Hinweis (Schritt 13)
- Rebuild + Restart: nur frontend (kein Backend-Aenderung)

---

### Aenderungen 2026-03-13 (12): Bug-Fix Sprachen im Experten-Profil

#### Backend (core-service)
- `core/expert-profile/dto/create-language.dto.ts` — `@IsIn` Validator um fehlende Werte erweitert: `Verhandlungssicher`, `Fliesend`, `Gut` waren nicht enthalten (Frontend sendete diese Werte → 400 Fehler); alle 10 Werte jetzt korrekt: `['Muttersprache', 'Verhandlungssicher', 'Fliesend', 'Gut', 'C2', 'C1', 'B2', 'B1', 'A2', 'A1']`

#### Deployment-Hinweis (Schritt 12)
- Rebuild + Restart: nur core-service

---

### Aenderungen 2026-03-13 (11): Kontakt-Detailseite – Breite, Outlook-Daten-Sektion, Felder, Outlook-Push

#### Backend (crm-service)
- `graph/graph.module.ts` — `exports: [GraphService]` ergaenzt (GraphService ist jetzt in anderen Modulen nutzbar)
- `graph/graph.service.ts` — Neue Methode `pushContactToOutlook(userJwt, contact)`: prueft via `GET /me/contacts?$filter=emailAddresses/any(...)` ob Kontakt in Outlook existiert; existiert er → PATCH (Update); existiert er nicht → POST (Neuanlage); befuellt Outlook-Kontakt mit givenName, surname, jobTitle, department, companyName, emailAddresses, businessPhones, mobilePhone, businessAddress
- `contacts/contacts.module.ts` — `GraphModule` importiert (ermoeglicht GraphService-Injektion)
- `contacts/contacts.controller.ts` — Neuer Endpoint `POST /crm/contacts/:id/push-to-outlook`: laedt CRM-Kontakt, leitet alle relevanten Felder an `graphService.pushContactToOutlook` weiter; gibt `{ created: boolean, outlookContactId: string }` zurueck

#### Frontend
- `crm/api.ts` — `contactsApi.pushToOutlook(id)`: `POST /crm/contacts/:id/push-to-outlook`
- `crm/contacts/ContactDetailPage.tsx`:
  - **Breite**: Aeusserer Wrapper mit `maxWidth: 960px, margin: 0 auto`
  - **Kontaktdaten**: Neues Feld "Typ" (Person/Organisation) ganz oben in der rechten Spalte; "Status" immer angezeigt (bisher nur wenn != ACTIVE); `contact.companyName` als Fallback wenn kein verknuepftes Unternehmen; `state` (Bundesland) in der Adresszeile ergaenzt; `state`-Bedingung fuer Adressblock
  - **Outlook Daten** (neue Bezeichnung fuer "Microsoft 365"): Sektion einklappbar (default: eingeklappt), Chevron-Toggle im Karten-Header; "Aufgaben"-Tab entfernt (M365Tab jetzt nur `emails | calendar`); `TasksTab`-Import entfernt; "In Outlook speichern"-Button im Header mit Status-Feedback (Speichern…/✓ In Outlook gespeichert/Fehler)
  - `CONTACT_TYPE_LABELS` Konstante, `handlePushToOutlook` Funktion, `outlookExpanded` + `pushStatus` States

#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
- `npx tsc --noEmit` in packages/crm-service: 0 neue Fehler (vorhandene pre-existing Fehler aus Prisma-Client-Mismatch auf lokalem Mac — werden durch `prisma generate` auf Server behoben)

#### Deployment-Hinweis (Schritt 11)
- Rebuild + Restart: crm-service + frontend (kein neues DB-Schema, kein migrate benoetigt)

---

### Aenderungen 2026-03-13 (10): Dediziertes Projektanfrage-Formular + Button in Vorgänge-Liste

#### Frontend
- `crm/deals/ProjectRequestFormModal.tsx` — Neues dediziertes Modal "Neue Projektanfrage": Projektdetails-Sektion oben (Beschreibung, Auslastung/Start, Laufzeit/Vorort-Anteil, Stundensätze), darunter Vorgangsdaten (Titel, Pipeline/Stage, Kontakt, Unternehmen, Volumen/Abschluss, Notizen); Auto-Select bei genau einem isProjectType-Typ; Warnung wenn kein Typ konfiguriert; Submit-Button disabled bei fehlendem Typ
- `crm/deals/DealsPage.tsx` — Zweiter Button "Neue Projektanfrage" (outlined, primary) neben "Neuer Vorgang"; `isProjectRequestOpen` State; `ProjectRequestFormModal` eingebunden

---

### Aenderungen 2026-03-13 (9): Projektanfrage-Vorgangstyp (isProjectType + ProjectRequestDetails)

#### Backend (crm-service)
- `prisma/crm.schema.prisma` — `DealType` um `isProjectType Boolean @default(false)` erweitert; neues 1:1-Model `ProjectRequestDetails` (notes, workload, startDate, duration, onsitePercent, rateRemote, rateOnsite) mit `ON DELETE CASCADE` zu `Deal`; `Deal.projectRequest ProjectRequestDetails?` Relation hinzugefuegt
- `prisma/migrations/20260313_project_request/migration.sql` — `ALTER TABLE deal_types ADD COLUMN is_project_type`; `CREATE TABLE project_request_details` mit Unique-Index auf deal_id und FK mit CASCADE
- `src/deal-types/dto/create-deal-type.dto.ts` — `isProjectType?: boolean` (`@IsBoolean`) hinzugefuegt
- `src/deal-types/dto/update-deal-type.dto.ts` — `isProjectType?: boolean` hinzugefuegt
- `src/deals/dto/project-request.dto.ts` — Neues DTO: notes, workload (0-100%), startDate, duration, onsitePercent (0-100%), rateRemote, rateOnsite
- `src/deals/dto/create-deal.dto.ts` — `projectRequest?: ProjectRequestDto` mit `@ValidateNested()` + `@Type(()=>ProjectRequestDto)` hinzugefuegt
- `src/deals/deals.service.ts` — `create()`: Nested `projectRequest.create` + `include: {projectRequest:true}`; `findOne()`: `include: {projectRequest:true}`; `update()`: `projectRequest` aus Destructuring extrahiert, `upsert` vor Deal-Update, `include: {projectRequest:true}`

#### Frontend
- `crm/types.ts` — `DealType.isProjectType: boolean`; `CreateDealTypePayload.isProjectType?`; neues Interface `ProjectRequestDetails`; `CreateProjectRequestPayload`; `Deal.projectRequest?`; `Deal.dealType.isProjectType` ergaenzt; `CreateDealPayload.projectRequest?`
- `crm/deals/DealFormModal.tsx` — Vorgangsart-Dropdown an Anfang des Formulars; Projektanfrage-Sektion (grau hinterlegt, blauer Titel) erscheint conditional bei `isProjectType=true`: Beschreibung, Auslastung/Start-Zeile, Laufzeit/Vorort-Anteil-Zeile, Stundensatz Remote/Vorort-Zeile; Initialisierung aus `deal.projectRequest` beim Bearbeiten; Payload-Zusammenbau mit `projectRequest`-Objekt
- `crm/settings/CrmSettingsPage.tsx` — `DealTypesConfig`: `isProjectType` State + Spalte "Projektanfrage" in Tabelle (Checkbox im Edit-/Add-Modus, ✓/— im View-Modus)

#### Deployment-Hinweis (Schritt 9)
- Server: `prisma migrate deploy --schema prisma/crm.schema.prisma`
- Server: `prisma generate --schema prisma/crm.schema.prisma`
- Rebuild + Restart: crm-service + frontend

---

### Aenderungen 2026-03-13 (8): Vorgangsart (DealType) + Pipeline-Leerstate

#### Backend (crm-service)
- `prisma/crm.schema.prisma` — Neues Model `DealType` (id, tenantId, name, color, sortOrder) mit `@@unique([tenantId, name])`, `@@map("deal_types")`; Relation `deals Deal[]`; Deal-Model um optionales Feld `dealTypeId String? @map("deal_type_id") @db.Uuid` + Relation `dealType DealType? @relation(...)` erweitert
- `prisma/migrations/20260313_deal_type/migration.sql` — Migration: CREATE TABLE `app_crm.deal_types`, ALTER TABLE `app_crm.deals` ADD COLUMN `deal_type_id`, Unique/Index/FK-Constraints
- `src/deal-types/dto/create-deal-type.dto.ts` — DTO: name (required), color (optional, Hex-Validierung), sortOrder (optional)
- `src/deal-types/dto/update-deal-type.dto.ts` — Alle Felder optional
- `src/deal-types/deal-types.service.ts` — CRUD-Service (findAll/create/update/remove) analog IndustriesService, Duplikat-Check, Nutzungs-Guard beim Loeschen
- `src/deal-types/deal-types.controller.ts` — REST-Controller: GET/POST/PATCH/DELETE unter `/crm/deal-types`, TenantGuard
- `src/deal-types/deal-types.module.ts` — NestJS-Modul
- `src/app.module.ts` — DealTypesModule registriert
- `src/deals/dto/create-deal.dto.ts` — `dealTypeId?: string` (UUID, optional) hinzugefuegt
- `src/deals/deals.service.ts` — `dealTypeId` beim Create an Prisma weitergegeben

#### Frontend
- `crm/types.ts` — Neues Interface `DealType` (id, tenantId, name, color, sortOrder, _count?); `CreateDealTypePayload`, `UpdateDealTypePayload`; `Deal.dealTypeId: string|null` + `dealType?: {...}|null`; `CreateDealPayload.dealTypeId?: string`
- `crm/api.ts` — `dealTypesApi` (list/create/update/delete auf `/crm/deal-types`) importiert; Typ-Imports erweitert
- `crm/hooks.ts` — `crmKeys.dealTypes`, `useDealTypes`, `useCreateDealType`, `useUpdateDealType`, `useDeleteDealType`
- `crm/settings/CrmSettingsPage.tsx` — `DealTypesConfig`-Komponente (analog IndustriesConfig mit Farbpicker, Sortierung); als erster Block im Tab "Weitere Einstellungen" platziert
- `crm/deals/DealFormModal.tsx` — Vorgangsart-Dropdown (select mit DealType-Optionen, "— Keine —" als Default); Pipeline-Leerstate-Hinweis mit Link zu CRM-Einstellungen; `dealTypeId` State + Payload-Uebergabe

#### Deployment-Hinweis
Nach Deploy: `npx prisma migrate deploy && npx prisma generate` im crm-service ausfuehren.

---

### Aenderungen 2026-03-13 (7): Profil-Bereich nach oben rechts verschoben (Topbar)

#### Frontend
- `shell/AppLayout.tsx` — Profil-/Logout-/Theme-Bereich aus der Sidebar entfernt und in einen neuen `<header className={styles.topbar}>` am Anfang des `<main>`-Bereichs verschoben:
  - Theme-Schalter: 3 kompakte Icon-Buttons (☀ ☾ ⚙) als Gruppe, aktiver Modus hervorgehoben
  - Benutzer-Profil: Avatar + Name, klickbar → `/profile`
  - Logout: Icon-Button (Tuer-Pfeil-SVG) mit Tooltip "Abmelden"
  - `<Outlet />` nun in `<div className={styles.mainContent}>` eingebettet (eigene Padding-Klasse)
  - Sidebar hat nur noch: Logo/Brand + Navigation + Admin-Link (deutlich aufgeraeumt)
- `shell/AppLayout.module.css` — Theme-Toggle- und UserInfo-Abschnitte entfernt (ca. 150 Zeilen weniger); `.main` auf `display:flex; flex-direction:column` umgestellt; neues `.mainContent { padding:2rem }`; neue Klassen: `.topbar`, `.topbarThemeGroup`, `.topbarThemeBtn(Active)`, `.topbarUser`, `.topbarUserName`, `.topbarIconBtn`

---

### Aenderungen 2026-03-13 (6): Dashboard Home-Tab — Analoguhr, 3-Tage-Wetter, Spruch, kompakte Widgets

#### Frontend
- `components/AnalogClock.tsx` + `AnalogClock.module.css` — Neue SVG-Analoguhr mit Stunden-/Minuten-/Sekundenzeiger (Gegengewicht), digitaler Zeit- und Datumsanzeige; aktualisiert jede Sekunde via setInterval
- `hooks/useWeather.ts` — Open-Meteo API um 3-Tage-Prognose erweitert (`&daily=weather_code,temperature_2m_max,temperature_2m_min&forecast_days=3`); `WeatherData` um `forecast: ForecastDay[]` ergaenzt; neues Interface `ForecastDay`
- `shell/DashboardPage.tsx` — Home-Tab vollstaendig ueberarbeitet:
  - Spruch des Tages (35 dt. Zitate, deterministisch nach Tagesdatum) rechtsbuendig im Header neben dem Willkommensnamen
  - 3-Spalten-Layout: Linke Spalte (240px): Analoguhr + WeatherWidget (aus Header verschoben) + 3-Tage-Prognose; Mittlere Spalte (flex:1): Kompakte Aufgaben + Kompakte E-Mails; Rechte Sidebar (300px, unveraendert): Messe-Ticker + Tagesagenda
  - `HomeTasksWidget`: Top-8 offene Aufgaben (CRM + O365, gleiche Zusammenfuehrungs-Logik wie DashboardTasksTab), direkt erledigbar, „Alle →" schaltet auf Aufgaben-Tab
  - `HomeEmailsWidget`: Posteingang-E-Mails der letzten 3 Tage (client-seitiger Filter), direkter Outlook-Link, „Alle →" schaltet auf E-Mail-Tab
  - `role`-Parameter aus HomeTab entfernt (nicht mehr angezeigt)
- `shell/DashboardPage.module.css` — Neue CSS-Klassen: `.quoteOfDay`, `.homeLeft`, `.forecastStrip`, `.forecastDay*`, `.homeWidgetCard`, `.homeTaskRow*`, `.homeEmailRow*` u.a. (ca. 280 neue Zeilen)

---

### Aenderungen 2026-03-13 (5): Erweiterte Profilfelder (analog O365) + Profilbild-Sync aus Microsoft 365

#### Backend: Core-Service
- `prisma/core.schema.prisma` — 4 neue optionale Felder im User-Modell: `jobTitle`, `department`, `companyName`, `officeLocation` (mit `@map` auf snake_case, `@db.VarChar`)
- `prisma/migrations/20260313_user_profile_extra_fields/migration.sql` — ALTER TABLE fuer die 4 neuen Spalten
- `core/users/dto/update-user.dto.ts` — Neue Felder mit `@IsOptional`, `@ValidateIf`, `@IsString`, `@MaxLength`
- `core/users/users.service.ts` — `findById()`, `update()`, `updateProfile()` um neue Felder erweitert

#### Backend: CRM-Service
- `graph/graph.service.ts` — `M365UserProfile` um `department`, `companyName`, `officeLocation` erweitert; `getM365Profile()` `$select` erweitert; neue Methode `getM365Photo()`: Graph `/me/photos/96x96/$value` als ArrayBuffer → Base64 JPEG Data-URL; 404/400 → null
- `graph/office365.controller.ts` — Neuer Endpoint `GET /crm/office365/photo`

#### Frontend
- `auth/AuthContext.tsx` — `User`-Interface: `jobTitle?`, `department?`, `companyName?`, `officeLocation?`
- `crm/types.ts` — `M365UserProfile` um `department`, `companyName`, `officeLocation` erweitert
- `crm/api.ts` — `office365Api.getM365Photo()` hinzugefuegt
- `profile/ProfilePage.tsx` — 4 neue State-Variablen, Organisations-Fieldset (2×2 Grid), `handleEnrichFromO365` ueberschreibt Profilfoto immer; Auto-Sync-Hook befuellt alle neuen Felder
- `hooks/useO365ProfileSync.ts` — Fetcht Profil + Foto parallel; Profilfoto nur gesetzt wenn `!user.avatar`

---

### Aenderungen 2026-03-13 (4): O365-Profil-Auto-Sync beim Login (useO365ProfileSync)

#### Frontend
- `hooks/useO365ProfileSync.ts` — Neuer Hook: synchronisiert INSIGHT-Profil einmalig pro Browser-Session aus Microsoft 365; sessionStorage-Flag (`o365_profile_synced`) verhindert Mehrfach-Sync; stille Ausfuehrmg (kein UI-Feedback); bei Fehler wird Flag entfernt fuer naechsten Retry; `PATCH /users/me` mit Kontaktfeldern (firstName, lastName, phone, mobile, city, street, postalCode)
- `shell/AppLayout.tsx` — `useO365ProfileSync()` direkt in der AppLayout-Komponente aufgerufen

---

### Aenderungen 2026-03-13 (3): Dashboard Kalender-Tab — Monats-/Wochenansicht + Tages-Agenda

#### Backend: CRM-Service
- `graph/graph.service.ts` — Neue Methode `getCalendarEventsForRange(userJwt, userId, startDate, endDate)`: laedt Kalender-Termine fuer beliebigen Zeitraum via `/me/calendarView`, Redis-Cache 5 Min; `wellKnownName` aus `getMailFolders()` $select entfernt (400-Fehler auf Exchange-Tenants die das OData-Property nicht unterstuetzen)
- `graph/office365.controller.ts` — Neuer Endpoint `GET /crm/office365/calendar/range?startDate=&endDate=` (vor `@Get('calendar')` definiert, um Routing-Konflikt zu vermeiden)

#### Frontend
- `crm/types.ts` — `M365MailFolder.wellKnownName` als `optional` markiert (nicht alle Exchange-Tenants liefern das Feld)
- `crm/api.ts` — `office365Api.getCalendarRange(startDate, endDate)`
- `crm/hooks.ts` — Neuer Hook `useOffice365CalendarRange(startDate, endDate)`
- `shell/DashboardCalendarTab.tsx` — Neue Hauptkomponente mit: Toolbar (Vor/Zurueck/Heute + Monat/Woche Toggle), MonthView (6×7 CSS-Grid, Heute-Kreis, Event-Chips max. 2 + "+N"), WeekView (7-Spalten-Grid, Events mit farbigem linken Rand, Klick oeffnet Outlook), DayAgenda (rechts 1/3 — Uhrzeit/Betreff/Ort/Teilnehmer, Online-Badge, Outlook-Link); deterministisches Event-Coloring per ID-Hash
- `shell/DashboardCalendarTab.module.css` — Vollstaendiges Styling
- `shell/DashboardPage.tsx` — Kalender-Tab ersetzt ComingSoonTab durch DashboardCalendarTab

#### Fix: Ordner-Sidebar 400-Fehler
- `DashboardEmailTab.tsx` — Ordner-Sortierung auf Display-Name-Basis umgestellt (Posteingang/Inbox, Gesendete Elemente/Sent Items, etc.) da `wellKnownName` nicht verfuegbar; `isInboxFolder()` erkennt Posteingang per Anzeigename

---

### Aenderungen 2026-03-13 (2): Dashboard E-Mail Tab — Outlook-Postfach mit Ordner-Navigation + Aktivitaeten-Speicherung

#### Backend: CRM-Service
- `contacts/contacts.service.ts` — Neue Methode `findByEmail(tenantId, email)`: sucht Kontakt anhand E-Mail-Adresse (legacy + multi-value emails)
- `contacts/contacts.controller.ts` — Neuer Endpoint `GET /crm/contacts/lookup?email=xxx` (vor `:id`-Route, damit kein UUID-Konflikt)
- `graph/graph.service.ts` — Neue Methoden: `getMailFolders()` (alle Outlook-Ordner inkl. unreadItemCount), `getMailsByFolder(folderId, days)` (E-Mails mit optionalem Tages-Filter via `$filter receivedDateTime ge`)
- `graph/office365.controller.ts` — Neue Endpoints: `GET /crm/office365/folders`, `GET /crm/office365/folders/:folderId/messages?days=7`

#### Frontend
- `crm/types.ts` — Neue Interfaces: `M365MailFolder` (id, displayName, totalItemCount, unreadItemCount, childFolderCount, wellKnownName), `CrmContactLookup` (id, firstName, lastName, email, companyName)
- `crm/api.ts` — `office365Api.getMailFolders()`, `office365Api.getMailsInFolder(folderId, days)`, `contactsApi.lookupByEmail(email)`
- `crm/hooks.ts` — Neue Hooks: `useOffice365MailFolders()`, `useOffice365MailsInFolder(folderId, days)`, `useContactByEmail(email)` (mit retry: false fuer 404-Faelle)
- `shell/DashboardEmailTab.tsx` — Neue Hauptkomponente mit: Ordner-Sidebar (sortiert nach Prioritaet: Posteingang, Gesendet, Entwuerfe, ...), Zeitfilter-Leiste (1/7/14 Tage/alle), E-Mail-Liste mit Outlook-Link, CRM-Badge fuer bekannte Absender, Aktivitaeten-Modal mit Kommentarfeld
- `shell/DashboardEmailTab.module.css` — Vollstaendiges Styling
- `shell/DashboardPage.tsx` — E-Mail-Tab ersetzt ComingSoonTab durch DashboardEmailTab

---

### Aenderungen 2026-03-13: Office365-Seite + Graph API Bugfixes

#### Backend: CRM-Service — GraphModule erweitert
- `graph/graph.service.ts` — Fix: `$search` + `$orderby` koennen nicht kombiniert werden (Graph API Limitation) → `$orderby` aus Kontakt-E-Mail-Suche entfernt; neue globale Methoden: `getAllEmails`, `getAllCalendarEvents`, `getAllOutlookContacts`, `getTasks` (war schon vorhanden); `attendees` zu Kalender-Abfragen ergaenzt; Fehler werden jetzt geloggt
- `graph/office365.controller.ts` — Neuer Controller mit globalen Office365-Endpoints: `GET /crm/office365/emails`, `GET /crm/office365/calendar`, `GET /crm/office365/contacts`, `GET /crm/office365/tasks`
- `graph/graph.module.ts` — `Office365Controller` registriert

#### Frontend: Office365-Uebersichtsseite
- `crm/office365/Office365Page.tsx` — Neue Seite mit 4 Tabs: E-Mails, Kalender, Outlook-Kontakte, Aufgaben; zeigt alle M365-Daten des eingeloggten Users; Suchfilter fuer Kontakte; "CRM"-Button zum Navigieren in CRM-Kontakte
- `crm/office365/Office365Page.module.css` — Vollstaendiges Styling (Cards, Tabs, Grid, Badges)
- `crm/types.ts` — `M365Email.hasAttachments` und `M365CalendarEvent.attendees` ergaenzt; neues Interface `M365Contact`
- `crm/api.ts` — `office365Api` (getEmails, getCalendar, getContacts, getTasks)
- `crm/hooks.ts` — `useOffice365Emails`, `useOffice365Calendar`, `useOffice365Contacts`, `useOffice365Tasks`
- `shell/App.tsx` — Route `/crm/office365` hinzugefuegt
- `shell/AppLayout.tsx` — NavLink "Office 365" (Grid-Icon) nach Kanban ergaenzt

#### Datenbankfix
- `user_integrations`-Tabelle: Tenant-Membership fuer `t.reitz@xinion.de` in "Xinion GmbH" manuell angelegt (fehlende Zuordnung verursachte 403 auf allen CRM-Endpoints)

---

### Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — Frontend

#### Frontend: MS365 Integration-Tab + Kontakt-Tabs

- `crm/types.ts` — Neue Interfaces: `UserIntegration`, `M365Email`, `M365EmailAddress`, `M365CalendarEvent`, `M365Task`, `M365TaskList`
- `crm/api.ts` — `integrationsApi` (list, disconnectM365, getM365ConnectUrl) + `graphApi` (getContactEmails, getContactCalendar, getContactTasks)
- `crm/hooks.ts` — `useIntegrations`, `useDisconnectM365`, `useContactEmails`, `useContactCalendar`, `useContactTasks`
- `profile/ProfilePage.tsx`:
  - Neuer Tab "Integrationen" (Typ: `ProfileTab = 'personal' | 'expert' | 'password' | 'integrations'`)
  - Oeffnet automatisch wenn `?integration=microsoft-365` URL-Param gesetzt ist
  - Zeigt Erfolgs-/Fehlermeldung aus `?status=success|error` Param
  - "Microsoft 365 verbinden" Button (Link zu `/api/v1/auth/integrations/microsoft-365`)
  - Verbunden-Ansicht: Tenant-ID, Token-Ablauf, "Verbindung trennen" Button
- `crm/contacts/EmailsTab.tsx` — E-Mail-Liste aus MS Graph, ungelesen fett + blauer Rand, Link zu Outlook Web
- `crm/contacts/CalendarTab.tsx` — Kalendertermine (naechste 90 Tage), Online-Meeting-Badge, Link zu Outlook Web
- `crm/contacts/TasksTab.tsx` — Microsoft To Do Aufgaben, gruppiert nach Listen, Status-/Prioritaets-Badges
- `crm/contacts/ContactDetailPage.tsx`:
  - Neuer "Microsoft 365" Abschnitt (Card) am Seitenende (nur wenn Kontakt E-Mail hat)
  - Drei Tabs: E-Mails / Kalender / Aufgaben
  - Ohne MS365-Verbindung: "Verbinden"-Button direkt im Tab
- TypeScript `npx tsc --noEmit`: 0 Fehler

---

### Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — CRM-Service (GraphModule)

- `crm-service/src/graph/graph.service.ts` — Token von Core-Service holen (JWT-Forwarding), Graph-Calls (Emails/Kalender/Tasks), Redis-Cache 5 Min
- `crm-service/src/graph/graph.controller.ts` — GET /crm/contacts/:id/emails|calendar|tasks
- `crm-service/src/graph/graph.module.ts` — Modul-Definition
- `crm-service/src/app.module.ts` — GraphModule registriert
- `crm-service/src/config/env.validation.ts` — `CORE_SERVICE_URL` ergaenzt
- `docker-compose.crm.yml` — `CORE_SERVICE_URL=http://core:3000` hinzugefuegt

---

### Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — Core-Service

- `core-service/prisma/core.schema.prisma` — `UserIntegration` Modell + Relation auf `User`
- `core-service/prisma/migrations/20260312_user_integrations/migration.sql` — Migration fuer `user_integrations` Tabelle
- `core-service/src/config/env.validation.ts` — `AZURE_INTEGRATION_REDIRECT_URI`, `INTEGRATION_ENCRYPTION_KEY`
- `core-service/src/core/auth/sso/entra-id.service.ts` — `getIntegrationAuthUrl`, `handleIntegrationCallback`, `refreshIntegrationToken`
- `core-service/src/core/integrations/integrations.service.ts` — AES-256-GCM Token-Verschluesselung, Token-CRUD, Auto-Refresh
- `core-service/src/core/integrations/integrations.controller.ts` — OAuth-Flow + Token-Endpoints
- `core-service/src/core/integrations/integrations.module.ts`
- `core-service/src/app.module.ts` — IntegrationsModule registriert

---

### Aenderungen 2026-03-12: Kanban-Board (Frontend)

- `frontend/src/crm/deals/KanbanPage.tsx` — Drag-&-Drop Kanban-Board (@dnd-kit)
  - Pipeline-Selektor + Toggle "Abgeschlossene anzeigen"
  - DealCard (useDraggable), KanbanColumn (useDroppable), DragOverlay
  - Optimistisches Update via `localStageMap`; Rollback bei Fehler
- `frontend/src/crm/deals/KanbanPage.module.css` — Styles fuer Board, Spalten, Cards
- `frontend/src/shell/App.tsx` — Route `/crm/kanban`
- `frontend/src/shell/AppLayout.tsx` — NavLink "Kanban" im CRM-Bereich
- `frontend/package.json` — `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities`

---

### Aktueller Sprint: CRM Phase 2 / Vertraege-Modul (Feature-Branch: feature/crm-service)

---

### Aenderungen 2026-03-12: Vertraege-Modul Frontend (ContractsCard)

#### Neue/geaenderte Dateien

- `crm/companies/ContractsCard.tsx` — Vollstaendige Vertraege-Card fuer CompanyDetailPage
  - Tabelle mit Titel, Status-Badge, Laufzeit, Vertragswert
  - "+ Neu" Button oeffnet Create-Modal
  - Stift-Icon (Bearbeiten) + X-Icon (Loeschen) pro Zeile
  - Status-Farben: DRAFT=grau, ACTIVE=gruen, EXPIRED=orange, CANCELLED=rot
  - Wert-Formatierung (Intl.NumberFormat de-DE), Datumsformatierung
- `crm/types.ts` — `CreateContractPayload`, `UpdateContractPayload`, `ContractsQueryParams` ergaenzt
- `crm/api.ts` — `contractsApi` (list, create, update, delete) mit `/crm/companies/:id/contracts`
- `crm/hooks.ts` — `crmKeys.contracts` + `useContracts`, `useCreateContract`, `useUpdateContract`, `useDeleteContract`
- `crm/companies/CompanyDetailPage.tsx` — `contractCount` Prop entfernt (Card laedt selbst)

---

### Aenderungen 2026-03-12: CRM Phase 2 — Forecast, Import, Enrichment (Frontend komplett)

#### Phase 2.3: Forecasting
- `crm/forecast/ForecastPage.tsx` — Prognose-Dashboard unter `/crm/forecast`
  - Pipeline-Filter (Dropdown) + Zeitraum-Toggle (Monat/Quartal/Jahr)
  - Summary-Cards: Offene Vorgaenge, Gesamtwert, Gewichteter Wert, Zeitraum
  - Tabelle: Stage | Wahrscheinlichkeit | Vorgaenge | Gesamtwert | Gewichteter Wert
- `crm/pipelines/PipelinesPage.tsx` — Probability-Feld (0–100%) pro Pipeline-Stage

#### Phase 2.2: CSV/Excel Import
- `crm/import/ImportPage.tsx` — 3-Schritt-Wizard in CRM-Einstellungen → Tab "Import"
  - Schritt 1: Typ-Auswahl (Kontakte/Unternehmen/Vorgaenge) + Datei-Upload (CSV, XLSX, max 10MB)
  - Schritt 2: Spalten-Mapping (Auto-Match + manuell), Duplikat-Strategie, Kopfzeilen-Option
  - Schritt 3: Ergebnis (created/updated/skipped/errors + Fehlerdetails)
- API-Konvertierungen: `PERSON→contact`, mapping `Record→Array [{sourceColumn,targetField}]`

#### Phase 2.4: Datenanreicherung
- `crm/companies/CompanyDetailPage.tsx` — "Anreichern"-Button im Header
  - POST /companies/:id/enrich → Suggestions-Modal
  - Tabelle: Feld | Aktuell | Vorschlag | Quelle + "Uebernehmen"-Button (PATCH)
  - suggestions-Konvertierung: Backend-Record → UI-Array
- `crm/settings/CrmSettingsPage.tsx` — North Data API-Key in Tab "Integrationen"

#### Kontakt-Entity UI/UX Redesign (aus dieser Sprint-Session)
- `components/Drawer.tsx` + `Drawer.module.css` — Wiederverwendbarer Right-Side-Drawer
- `crm/contacts/ContactFormModal.tsx` — Modal → Drawer, Zwei-Tab-Struktur (Allgemein/Details)
- `crm/contacts/ContactDetailPage.tsx`:
  - Header: Name (Unternehmen) in Klammern, Aktiv/Inaktiv-Badge (Pill mit farbigem Dot)
  - Kontaktdaten-Card: immer alle Allgemein-Felder (leere Felder = "—"), 2 Sub-Spalten
  - Lexware-Section entfernt (Kontakte werden nicht separat in Lexware gepflegt)

#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler (alle Commits)

---

### Aktueller Sprint: Sprint 1 (Alpha)

---

### Aenderungen 2026-03-12: CRM UI/UX Redesign – Kontakt-Entitaet

#### Drawer-Komponente (neu)
- `packages/frontend/src/components/Drawer.tsx` – Wiederverwendbares Right-Side-Drawer-Pattern mit Portal, ESC-Key, Backdrop-Click, optionalem Footer
- `packages/frontend/src/components/Drawer.module.css` – Animiertes Slide-In von rechts, sticky Header + Footer, scrollbarer Body

#### ContactFormModal – Redesign (Modal → Drawer + Tabs)
- Gewechselt von `<Modal>` (zentriert) zu `<Drawer>` (von rechts, 540px)
- Zwei-Tab-Struktur:
  - Tab "Allgemein": Vorname, Nachname, Unternehmen (Autocomplete), Position, Abteilung, E-Mail, Telefon, Mobil, LinkedIn
  - Tab "Details & Adresse": Geburtsdatum, Quelle, Status, Website, Adresse, Notizen, Tags, Custom Fields
- "Adresse vom Unternehmen uebernehmen" Checkbox: wird angezeigt wenn Unternehmen verlinkt ist; bei aktivierter Checkbox werden Adressfelder ausgeblendet und die Adresse wird aus dem verlinkten Unternehmen uebernommen
- "Typ"-Feld entfernt (Kontakte sind immer Personen; ORGANIZATION-Typ bleibt intern erhalten)
- Sticky Footer (Abbrechen / Anlegen|Speichern) via HTML5 `form` Attribut
- Adress-Duplikate bereinigt

#### ContactDetailPage – Redesign
- **Header**: Subtitle "Position @ Unternehmen" unter dem Namen (z.B. "Geschaeftsfuehrerin @ team neusta SE"), Status-Dot inline
- **Kontaktdaten-Card**: Zwei Sub-Spalten (Kommunikation links, Kontext rechts), Adresse als volle Breite darunter
  - Entfernt: Vorname/Nachname (im Header), Firma-Duplikat (nur noch "Unternehmen" als Link)
  - Hinzugefuegt: Mobil als `tel:`-Link, LinkedIn mit Icon, Geburtsdatum, Quelle, Abteilung
- **Neue Card "Notizen & Tags"**: Tags als Badges, Notizen-Text, Custom Fields
- **Layout**: Rechte Spalte (Aktivitaeten) auf `minmax(380px, 40%)` verbreitert (min. 1/3 fuer kuenftigen O365-Feed)
- **CSS**: `.infoColumns` (2-spaltig), `.addressRow`, `.subtitle`, `.nameRow` neu

#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler

---

### Aenderungen in dieser Session

#### 1. Projektinitialisierung & Infrastruktur-Definition

**Was wurde gemacht:**

1. **SSH Keys erstellt**
   - Deploy-Key (`.keys/deploy_ed25519`) fuer Server-Zugriff
   - CI/CD-Key (`.keys/cicd_ed25519`) fuer Forgejo Actions Pipeline

2. **Infrastruktur-Definition erstellt** (`docs/INFRASTRUCTURE.md`)
   - ProxmoxVE VM-Spezifikation: Ubuntu 24.04 LTS, 4 vCPU, 8 GB RAM, 60 GB SSD
   - Docker-Netzwerk-Architektur mit 3 isolierten Netzwerken
   - Komplette Service-Landschaft definiert
   - Schritt-fuer-Schritt VM-Setup Anleitung

3. **Zugangsdaten-Dokument erstellt** (`docs/ACCESS.md`)
   - Server-IP: 172.20.10.59 (insight-dev-01)
   - Git-Server: 172.20.10.11 (GAIA-GIT)
   - Alle SSH-Keys, Ports, Befehle dokumentiert

4. **Projektstruktur aufgesetzt** (packages/core-service, packages/frontend, config, .forgejo)

5. **Basis-Konfigurationsdateien** (.gitignore, .env.example, README.md)

#### 2. Forgejo Git-Server Konfiguration

**Was wurde auf dem Git-Server (172.20.10.11) gemacht:**

1. **Docker Engine 29.3 installiert** (fuer Forgejo Actions Runner)
2. **Forgejo Actions aktiviert** (`[actions] ENABLED = true` in app.ini)
3. **Container Registry aktiviert** (`[packages] ENABLED = true` in app.ini)
4. **Forgejo Runner v6.3.1 installiert und registriert**
   - Runner-Name: `insight-runner`
   - Labels: `ubuntu-latest` (docker://node:20)
   - Laeuft als Systemd-Service (`forgejo-runner.service`)
5. **Repository Secrets angelegt:**
   - `SSH_DEPLOY_KEY` - CI/CD Private Key
   - `DEPLOY_HOST` - 172.20.10.59
   - `DEPLOY_USER` - deploy
   - `REGISTRY_USER` - gitadmin
   - `REGISTRY_PASSWORD` - Forgejo Access Token
6. **Branch Protection eingerichtet:**
   - `main`: Kein direkter Push, 1 Approval erforderlich
   - `develop`: Kein direkter Push, 1 Approval erforderlich
7. **Forgejo Setup-Anleitung erstellt** (`docs/FORGEJO_SETUP.md`)

#### 3. Server-Setup (insight-dev-01)

**Was wurde auf dem Entwicklungsserver (172.20.10.59) gemacht:**

1. **SSH Public Keys hinterlegt** in `/home/deploy/.ssh/authorized_keys`
   - Deploy-Key (`insight-deploy@xinion.lan`) - fuer manuellen Zugriff
   - CI/CD-Key (`insight-cicd@xinion.lan`) - fuer Forgejo Actions Pipeline
2. **SSH-Zugang getestet** - Key-basierter Login als `deploy` funktioniert

#### 4. Docker Compose & Service-Konfiguration

**Erstellte Dateien:**

1. **`docker-compose.yml`** - Alle Basis-Services:
   - Traefik 3 (API Gateway, Reverse Proxy, Rate Limiting)
   - PostgreSQL 16-alpine (Performance-Tuning: 1GB shared_buffers, 4GB cache)
   - PgBouncer (Connection Pooling, Transaction Mode)
   - Redis 7-alpine (Cache, Sessions, Token-Revocation)
   - step-ca (Interne Certificate Authority fuer mTLS - geplant)
   - Core-Service (NestJS) mit Traefik-Labels
   - Frontend (React) mit Traefik-Labels
   - 3 isolierte Docker-Netzwerke (insight-web, insight-db, insight-cache)
   - Health-Checks fuer alle Services

2. **`docker-compose.observability.yml`** - Monitoring-Stack:
   - Prometheus (Metrics-Sammlung, 30 Tage Retention)
   - Grafana (Dashboards, automatisch provisionierte Datenquellen)
   - Loki (Log-Aggregation)
   - Promtail (Docker Log-Collector)
   - Tempo (Distributed Tracing, OTLP gRPC)
   - cAdvisor (Container-Metriken)
   - PostgreSQL Exporter (DB-Metriken)

3. **Konfigurationsdateien:**
   - `config/traefik/dynamic/tls.yml` - TLS deaktiviert (Alpha/Dev)
   - `config/traefik/dynamic/middlewares.yml` - Security-Headers, CORS, Compression
   - `config/prometheus/prometheus.yml` - Scrape-Konfiguration
   - `config/loki/loki.yml` - Log-Storage-Konfiguration
   - `config/promtail/promtail.yml` - Docker-Log-Collector
   - `config/tempo/tempo.yml` - Tracing-Backend
   - `config/grafana/provisioning/datasources/datasources.yml` - Auto-Provisioning
   - `config/postgres/init/01-init-extensions.sql` - DB-Extensions (uuid-ossp, pgcrypto, pg_trgm)

#### 5. NestJS Core-Service Implementierung

**Projekt-Setup:**
- `package.json` mit allen Dependencies (NestJS 10, Prisma 6, Passport, JWT, bcrypt, TOTP)
- `tsconfig.json` mit strict: true, noImplicitAny, strictNullChecks
- `Dockerfile` (Multi-Stage: base, deps, development, build, production)
- `nest-cli.json` Konfiguration

**Implementierte Module:**

1. **Auth-Modul** (`src/core/auth/`)
   - `AuthService`: Login (E-Mail/Passwort), Token-Refresh, Logout, Token-Revocation
   - `AuthController`: POST /login, /refresh, /logout
   - `JwtStrategy`: RS256 Passport-Strategy
   - `TotpService`: TOTP 2FA (Google Authenticator kompatibel)
   - `LoginDto`: Validierung mit class-validator
   - Account-Lockout nach 5 Fehlversuchen (15 Min Sperre)
   - Refresh-Token als HttpOnly Cookie (secure/sameSite umgebungsabhaengig)
   - Token-Rotation mit Redis-basierter Familien-Erkennung

2. **Users-Modul** (`src/core/users/`)
   - `UsersService`: CRUD, Bcrypt Cost 12, Passwort-Hashing
   - `UsersController`: GET /me, GET /users, POST /users, PATCH /users/:id
   - DTOs: CreateUserDto, UpdateUserDto
   - Paginierung mit Meta-Informationen

3. **Tenants-Modul** (`src/core/tenants/`)
   - `TenantsService`: CRUD, Member-Management
   - `TenantsController`: CRUD + POST /:id/members, DELETE /:id/members/:userId
   - DTOs: CreateTenantDto, UpdateTenantDto, AddMemberDto
   - Slug-Validierung (URL-freundlich)

4. **Infrastruktur-Module:**
   - `PrismaService`: PostgreSQL-Verbindung (platform_core)
   - `TenantPrismaService`: Dynamische Tenant-DB-Verbindungen mit Caching
   - `RedisService`: Token-Blocklist, Refresh-Token-Familien, generischer Cache
   - `HealthController`: GET /health (DB + Redis Status)

5. **Common (Guards, Decorators, Filter):**
   - `@Public()` Decorator fuer oeffentliche Routen
   - `@Roles()` Decorator fuer rollenbasierte Zugriffskontrolle
   - `@CurrentUser()` Decorator fuer User-Extraktion aus JWT
   - `JwtAuthGuard` (global) mit Token-Revocation-Check
   - `RolesGuard` fuer Rollen-Pruefung
   - `GlobalExceptionFilter` fuer strukturierte Fehlerantworten

6. **Config:**
   - `validateConfig()` mit class-validator fuer Umgebungsvariablen

#### 6. Prisma-Schemas & Migration

1. **`core.schema.prisma`** (platform_core Datenbank):
   - `User` - Plattform-Benutzer (mit Login-Tracking, 2FA)
   - `AuthProvider` - Multi-Provider Auth (LOCAL, MS_SSO, M2M)
   - `Tenant` - Mandanten mit JSON-Settings
   - `TenantMembership` - User-Tenant-Zuordnung (M:N)
   - `Module` - Verfuegbare Plattform-Module
   - `TenantModule` - Module pro Tenant
   - `AuditLog` - Plattform-weites Audit-Log

2. **`tenant.schema.prisma`** (tenant_{slug} Datenbanken):
   - `Contact` - CRM-Kontakte (Person/Organisation)
   - `Activity` - CRM-Aktivitaeten (Notiz, Anruf, E-Mail, Meeting, Task)
   - Referenz-Schema fuer Sprint 2 (CRM-Modul)

3. **Erste Migration erstellt** (`prisma/migrations/20260308000000_init/`)
   - 7 Tabellen, alle Indizes und Foreign Keys
   - `migration_lock.toml` fuer Prisma

4. **Seed-Script erstellt** (`prisma/seed.ts`)
   - Erstellt Platform-Admin: `admin@xinion.de` / `ChangeMe123!`
   - Bcrypt Cost 12, Rolle: PLATFORM_ADMIN

#### 7. React Frontend-Shell

**Projekt-Setup:**
- `package.json` mit React 18, Vite 6, React Router 6, TanStack Query 5, Axios
- `tsconfig.json` mit strict TypeScript
- `vite.config.ts` mit API-Proxy und Path-Aliases
- `Dockerfile` (Multi-Stage: development mit Vite, production mit Nginx)
- `nginx.conf` (SPA-Routing, Security-Headers, Caching)

**Implementierte Komponenten:**

1. **Auth-System** (`src/auth/`)
   - `AuthContext` + `useAuth()` Hook: Login, Logout, Silent Refresh
   - `LoginPage`: E-Mail/Passwort + optionaler TOTP 2FA-Code
   - Access-Token NUR im Memory (kein localStorage!)
   - Automatischer Silent Refresh via HttpOnly Cookie

2. **API-Client** (`src/api/client.ts`)
   - Axios-Instanz mit automatischem Token-Handling
   - Request-Interceptor fuer Authorization-Header
   - Response-Interceptor fuer automatisches Token-Refresh bei 401

3. **App-Shell** (`src/shell/`)
   - `App`: React Router mit PrivateRoute-Guard
   - `AppLayout`: Sidebar-Navigation + Outlet
   - `DashboardPage`: Willkommens-Seite

4. **Admin-Bereich** (`src/admin/`)
   - `AdminUsersPage`: Benutzer-Tabelle mit Paginierung
   - `AdminTenantsPage`: Mandanten-Tabelle mit Member-Count

5. **Styling:**
   - CSS Custom Properties (Farben, Layout, Schatten, Radien)
   - CSS Modules fuer komponentenspezifische Styles
   - Responsive Sidebar-Layout

#### 8. CI/CD Pipelines

1. **`.forgejo/workflows/ci.yml`** - Continuous Integration:
   - Trigger: Push auf alle Branches + Pull Requests
   - Core-Service: npm ci, Prisma Generate, Lint, Type-Check, Test, Build
   - Frontend: npm ci, Lint, Type-Check, Build

2. **`.forgejo/workflows/deploy.yml`** - Deployment:
   - Trigger: Push auf main/develop
   - Build Docker-Images (Core + Frontend)
   - Push in Forgejo Container Registry
   - SSH-Deploy auf insight-dev-01
   - Health-Check Verifizierung

#### 9. IP-basierte Deployment-Anpassung (HTTP statt HTTPS)

**Grund:** Kein DNS-Eintrag vorhanden, Zugriff nur ueber IP 172.20.10.59.

**Geaenderte Dateien:**

1. **`auth.controller.ts`** - Cookie secure/sameSite umgebungsabhaengig
   - `secure: true` -> `secure: process.env.NODE_ENV === 'production'`
   - `sameSite: 'strict'` -> `isProduction ? 'strict' : 'lax'`
   - Betrifft `setRefreshTokenCookie()` und `logout()`

2. **`docker-compose.yml`** - HTTP + IP Umstellung
   - HTTPS-Redirect entfernt
   - TLS-Entrypoint deaktiviert, Port 443 entfernt
   - Alle Host-Rules: `insight-dev.xinion.lan` -> `172.20.10.59`
   - Alle Entrypoints: `websecure` -> `web`
   - URL-Defaults auf `http://172.20.10.59`
   - PostgreSQL Memory reduziert (1GB/4GB/256MB fuer 8GB RAM VM)
   - JWT-Keys Volume-Mount hinzugefuegt: `./keys:/app/keys:ro`

3. **`config/traefik/dynamic/tls.yml`** - TLS-Konfiguration deaktiviert

4. **`config/traefik/dynamic/middlewares.yml`**
   - HSTS-Headers entfernt
   - CSP: `wss://insight-dev.xinion.lan` -> `ws://172.20.10.59`
   - CORS: `https://insight-dev.xinion.lan` -> `http://172.20.10.59`

5. **`main.ts`** - CORS-Fallback auf `http://172.20.10.59`

6. **`env.validation.ts`** - APP_URL Default auf `http://172.20.10.59`

7. **`.env.example`** - Alle URLs auf `http://172.20.10.59`

8. **`package-lock.json`** - Generiert fuer core-service und frontend (npm ci braucht diese)

9. **Dokumentation aktualisiert:**
   - `docs/INFRASTRUCTURE.md` - HTTP statt HTTPS, IP statt DNS
   - `docs/ACCESS.md` - Ports, URLs, Default-Zugangsdaten
   - `README.md` - Setup-Anleitung, URLs, Seed-Befehle

---

### Naechste Schritte

- [x] SSH Deploy Keys auf insight-dev-01 Server hinterlegen
- [x] `docker-compose.yml` erstellen (alle Basis-Services)
- [x] `docker-compose.observability.yml` erstellen
- [x] NestJS Core-Service implementieren (Auth, Users, Tenants)
- [x] Prisma-Schemas erstellen (core + tenant)
- [x] React Frontend-Shell implementieren
- [x] CI/CD Pipelines (.forgejo/workflows/) definieren
- [x] Codebase auf HTTP + IP (172.20.10.59) umstellen
- [x] Seed-Script erstellen (admin@xinion.de)
- [x] Prisma-Migration erstellen (init)
- [x] package-lock.json generieren
- [x] Dokumentation aktualisieren
- [ ] Commit & Push auf develop
- [ ] LVM-Festplatte auf Server erweitern (60GB -> voll nutzbar)
- [ ] Docker + Docker Compose auf insight-dev-01 installieren
- [ ] Repo klonen, .env + JWT-Keys auf Server erstellen
- [ ] Services starten, Migration + Seed ausfuehren
- [ ] Erster End-to-End Test (Login -> Dashboard)

---

### Offene Fragen / Abhaengigkeiten

- DNS-Eintrag `insight-dev.xinion.lan` wird spaeter eingerichtet (dann HTTPS aktivieren)
- LVM auf Server muss erweitert werden (60GB Disk, nur ~56GB sichtbar)
