---
name: project-cleanup-timeline
description: Cleanup-Sprint plato-customer Mai 2026 — Status nach BFF-Umbau und nächste Themen (User-DB, Postgres).
metadata:
  node_type: memory
  type: project
  originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---

10-PT-Aufräum-Sprint auf `development`. Linie: [[feedback-priorisierung]].

**Status Ende 2026-05-22 (Fr):**
- ✅ Storage-Layout (Subdirs + `.crypt`) + AES → Base64 (`94d19a5`)
- ✅ Auto-Migration `plato5/crm.sqlite` bei Init (`ee57b86`)
- ✅ IdP-Anbindung verifiziert (siehe [[project-idp-witglobal]])
- ✅ **Auth-Umbau auf BFF abgeschlossen** (`00e2c37` "activate odic login" + weitere Commits): Dummy-JWT raus, `passport-jwt`+`jwks-rsa` raus, `openid-client`+`express-session` rein, `client_secret` server-seitig (nicht mehr im Browser), Session-Cookie-basiert. Siehe [[project-idp-witglobal]] und [[project-run-commands]].
- ✅ User `ex08802409` in `user.json` ergänzt (provisorisch, ohne echten Datensatz)
- ⏳ Test-Rig `scripts/oidc-test.mjs` uncommitted (separater Scope, war Pre-BFF-Verifikation)

**Nächste Themen (am 2026-05-22 angerissen, Implementierung steht aus):**
- **User-DB**: aktuell statisch aus `user.json` via `UserRegistry` (Konstruktor `readFileSync`). Soll eine richtige DB werden — Marcus hat das explizit als nächstes Thema benannt nachdem die User-Mapping-Lücke beim Login auffiel.
- **Kunden-Persistenz: SQLite → Postgres**: aktuell SQLite via TypeORM in `data/customer.db` (PVC-mounted im Container). Bedeutet Single-Pod, kein Rolling-Deploy. Plan: separater Postgres-Pod (StatefulSet, im Gitops-Repo zu definieren), TypeORM-DataSource auf `postgres` umstellen, Migrations neu generieren, lokal Compose o.ä. — kein Managed-Service bei Würth (außer SQL Server).
- **Infrastruktur-Repo** existiert ("haben wir") — noch nicht im Detail angeschaut.

**Plan 2026-05-26 (Di) — "alles fertig":**
- Übergabe an Kollegen zum Testen
- Test-Automatisierung
- QS-Übergabe
- **Blocker für Auth-Rollout**: Windows-Kollege braucht funktionierenden Weg zum IdP. Solange nicht geklärt, kann der BFF-Stand nicht "scharf" für ihn werden — er kommt sonst nicht in die App.

**Stand 2026-05-27 (Mi) — Deployment-Cleanup-Runde (siehe Commits `f6d7b6d`, `acafac5`, `b6856e9`):**
- ✅ OIDC_CLIENT_SECRET + SESSION_SECRET aus Image, via Sealed-Secret im GitOps-Repo (`plato-customer-secrets`), `envFrom` im Deployment-Patch `backend.yaml`
- ✅ `trust proxy` für Session-Cookies hinter OCP-Router
- ✅ Proxy-ENVs (`HTTPS_PROXY` etc.) aus Image, in Deployment-`env:`-Block; undici-`ProxyAgent` für Node-22-fetch
- ✅ Fail-Fast in `main.ts` und `oidc.service.ts` wenn Secrets fehlen (Crash statt stille Dev-Fallbacks)
- ✅ Alte GHCR-Versionen mit eingebackenem Secret gelöscht (manuell via Web-UI), aktuelle Version 0.3.0
- ⏸️ Session-Store-Umzug (MemoryStore → Redis) verschoben: replicas=1, kein akuter Bedarf, kein Redis im Namespace deploybar

**Plan 2026-05-28 (Do) — umgestellt:**
- **Vormittag (statt Postgres-Backbone):** Migration + Import-Handling. Postgres-Backbone wurde verschoben. Code-Bereich: `apps/plato-customer-be/src/migration/` (Controller `/import/sqlite` + `/import/excel`, `SqliteImportService`/`MsExcelImportService`, `CustomerRehydrator`, `DuplicateFinderUtils`, SQL-Selects, `SQliteDb`); shared `xlImport2Customer`+`excel2json`; FE `migrationUploadClient` + `legacyMigration` (`./plato5/crm.sqlite`). Import liefert nur `MigrationCheck` (Vorschau, BE persistiert nicht selbst — Persistenz FE über `persistentStorage`/Outbox bzw. `CustomerCoreService.createOrUpdateList`).
- Offene Schwachstellen im Import (28.05. analysiert): (1) `DuplicateFinderUtils.fuzzyFinds` (Kunden ohne customer_number) wird gesammelt aber nie ins Ergebnis/Report übernommen; `getByCustomerFuzzy` existiert im Core-Service, wird aber nicht aufgerufen — angefangenes Feature. (2) `SQliteDb.executeSelect` als `Promise<string>` typisiert, liefert aber Row-Array. (3) Import schreibt nichts BE-seitig.
- **Architektur-Klarstellung Marcus (28.05.):** SQLite-Migration ist ein EINMALIGER Prozess beim ersten App-Start — `main.js` Boot-Schritt 5.5 ruft `legacyMigration.migrateIfPresent()` nur `if (manifest.created)` (Cache-Verzeichnis frisch). Ab dann wird mit SAP-Daten gearbeitet. Die manuellen Header-Buttons `⇆ SQLite`/`⇆ Excel` (`CustomerHeaderActions.vue`) widersprechen dem → auf Marcus' Wunsch **disabled** (nicht gelöscht, reaktivierbar). Wie es mit dem Import-Pfad weitergeht, will Marcus noch besprechen.
- **SAP-Einzel-Flow umgebaut (28.05., erledigt, typecheck grün):** SAP-Nummer ist führend, Eingabe über `SapImportDialog` (bleibt). `CustomerStore.fetchFromSap` sucht lokal nach `customer_number`; gefunden → hart aus SAP überschreiben (8 Stammdaten-Felder, Identität bleibt), nicht gefunden → neu anlegen. Verdrängte alte Email → geretteter Kontakt (`overwriteWithSap` + `guessNameFromEmail` in `sap-adapter.ts`), Name aus Email-Local-Part geraten, Match per Email. Bestehende Kontakte/Adressen bleiben beim Überschreiben erhalten; SAP-Partner-Kontakt/-Adresse kommen nur bei Neuanlage rein.
- **SAP-Mock ins BE integriert (28.05., erledigt, typecheck grün) — Variante B, bewusst Notlösung & komplett removable:** Marcus' Vorgabe war „muss ohne großes Aufhebens vollständig rausnehmbar sein". Neues self-contained Modul `apps/plato-customer-be/src/sap-mock/` (Controller `@Get('CustomerSet')`, Service liest `crm.sqlite` via `better-sqlite3`, Query/XML 1:1 aus altem Express-Mock portiert). In `app.module.ts` **flag-gegatet** via `SAP_MOCK_ENABLED=true`. Dockerfile bekommt eine `COPY apps/sap-mock/crm.sqlite`-Zeile (30 MB). Begründung B statt eigenem Pod: SAP-Call kommt aus dem Browser (`sapClient`→`VITE_SAP_URL`), eigener Pod bräuchte Route+HTTPS+CORS+FE-Build-URL; B = ein Deployment, same-origin. **Rausnehmen** = Ordner löschen + 2 Zeilen in app.module.ts + COPY-Zeile + `VITE_SAP_URL` aufs echte SAP. Standalone `apps/sap-mock/` bleibt unangetastet (Quelle der crm.sqlite).
- **SAP-Mock Container-Build + Runtime VERIFIZIERT (28.05., podman):** `better-sqlite3` baut auf `node:22-alpine` ohne Build-Tools (prebuilt musl-binary), Modul wird vom nx-Webpack externalisiert, Image ~853 MB. Container bootet mit `SAP_MOCK_ENABLED=true` + Dummy-`OIDC_CLIENT_SECRET`/`SESSION_SECRET` (OIDC-Discovery-Fehler crasht NICHT). `/CustomerSet` liefert OData-XML aus `/app/apps/sap-mock/crm.sqlite`. Lokal: `docker` nicht da (kein Daemon) → `podman` nutzen.
- **SAP-Mock Query an neuen Flow angepasst (28.05.):** Lookup jetzt per `customer_number` (war interne `customer_id`) — die SAP-Nummer ist führend, und der FE-Adapter mappt `d:CustomerID`→`customer_number`, also gibt der Query auch `cu.customer_number as CustomerID` aus. Contacts/Addresses-Joins von `INNER` auf `LEFT OUTER` umgestellt, damit auch Kunden ohne Kontakt/Adresse auflösen (404 nur noch bei unbekannter Nummer). **OFFEN (Marcus klärt):** `CustomerToplevelNumber` = `customer_number` (→ FE `verband_nummer` = Kundennummer), weil Legacy-DB keine eigene Verband-Spalte hat — evtl. auf leer setzen.
- **Postgres-Backbone — am 2026-05-28 abend LIVE auf DEV gebracht (war "verschoben"):** Zweiter Pod `plato-postgres` im GitOps-Repo (`base/postgres.yaml`: Deployment RHEL-Image `registry.redhat.io/rhel9/postgresql-16` + ClusterIP-Service `plato-postgres:5432`, KEINE Route). Creds via Sealed-Secret `plato-postgres-secrets` (User `plato_admin`, DB `Plato`). **Bewusst OHNE PVC = ephemer** (Daten weg bei Restart). Backend-DataSource umschaltbar gemacht (`DB_TYPE=postgres` → `pg`, sonst sqlite-Default), `synchronize:true` für Postgres (keine PG-Migrations, da eh ephemer). Im DEV-Backend-Deployment via `envFrom plato-postgres-secrets` + `DB_TYPE=postgres` aktiviert. Verifiziert: Pod 0.6.1 Running, mit Postgres verbunden, Tabellen (customer/contact/address/contact_addresses) per synchronize angelegt, DB leer. **Stolperstein gefixt:** Entities hatten hartes `type:'datetime'` (SQLite-only) → auf Dialekt-Default umgestellt (kein Typ bei `@CreateDateColumn`/`@UpdateDateColumn`), sonst `DataTypeNotSupportedError` auf Postgres. Namespace `1401-plato-development`, RedHat-Image pullt im Cluster.
- **PVC konfiguriert (2026-05-28 abend):** StorageClass-Frage geklärt — Plattform-Team: PVC selbst als K8s-Objekt anlegen, CSI-Treiber legt PV automatisch an; **thin-csi** = Default/OLTP (für DB), trident-nas nur für File-RWX. In `base/postgres.yaml` ergänzt: PVC `plato-postgres-data` (thin-csi, RWO, **50Gi** — Marcus' Wahl quota-sparsam: 400 Nutzer × 50MB ≈ 20GB Rohdaten + DB-Overhead; nicht die beantragten 100GB), gemountet an `/var/lib/pgsql/data`, dazu `strategy: Recreate` (sonst RWO-Mount-Deadlock beim Redeploy). Kustomize-Build grün. **Noch nicht committet/deployed** (Stand Reden). `data/customer.db` bleibt uncommitted, sqlite-Daten werden NICHT nach Postgres migriert (leerer Start gewollt).
- **synchronize → PG-Migrations umgestellt (2026-05-28, Weg „B" hand-portiert):** Marcus' Entscheidung — `synchronize` raus, weil mit PVC persistent; „wenn der Pod dann deterministisch crasht, ist offensichtlich was an der Migration falsch" (Crash+Log = Diagnose, [[feedback-keine-zwischenstufen]]). Befund: die einzige bestehende Migration ist rein SQLite (`datetime`, Temp-Table-Tanz) → läuft auf PG nie. Daher neue PG-Migration `libs/customer/customer-be/src/lib/db/1780000000000-…-customer-pg-migration.js` hand-portiert aus den Entities (uuid-PK `gen_random_uuid()`, `TIMESTAMP/now()`, FKs per ALTER, Constraint-/Index-Namen 1:1 aus M000). `customer-data-source.ts`: Postgres `synchronize:false` + eigener Glob `*customer-pg-migration.js` (disjunkt vom SQLite-Glob `*customer-migration.js`). Asset-Copy (`apps/plato-customer-be/project.json`: `**/db/**/*migration.js`) erfasst beide; Prod-Build verifiziert, beide JS landen in dist, Globs trennen sauber. Deploy-Hinweis: Backend gegen ALTEN synchronize-Pod → `CREATE TABLE … already exists`; gitops-PVC-Change zuerst deployen (Pod neu = leeres PVC), dann läuft Migration clean.
- **Migrations-Features angefragt (2026-05-28, noch offen, Rückfragen unbeantwortet):** (1) Bei Import zusätzlich per `customer_number` prüfen ob Kunde existiert → wenn ja, "aus SAP updaten" und an Client zurückgeben (offen: BE-seitig SAP-Mock rufen + sap-adapter-Logik BE-portieren, oder FE macht `fetchFromSap`?). (2) Neuer FE-Dialog: aus ALLEN Kunden (`CustomerCoreService.findAll` aus Postgres) auswählen, welche in den lokalen Stand geladen werden.

**Plan 2026-05-29 (Fr):**
- **Outbox / Inflight / BadBox** Pattern + eine weitere Sync-Schnittstelle. Marcus will erst "genau anschauen" bevor implementiert wird.

**Why:** Marcus hat den Sprint explizit als "10 PT" geframet mit Test-/QS-Übergabe Di 26.05.2026.

**How to apply:** Wenn am Montag/Dienstag oder später eine neue Session startet, hier reinschauen für aktuellen Stand, statt aus git log zu rekonstruieren. Beim Aufgreifen der User-DB oder Postgres-Themen: Marcus wollte beide Sachen aufgreifen, hatte aber explizit das Postgres-Thema vor sich her geschoben (Kunden-DB zuerst). Reihenfolge mit ihm abklären.
