# Plan: UI-Vollständigkeit + Workflows — Phase O **Ziel**: Alle implementierten Backend-Features im UI zugänglich machen + v3-Workflows vollständig verdrahten. --- ## Betroffene Dateien | Datei | Änderung | |-------|----------| | `frontend/src/components/layout/Layout.tsx` | Upload-Link hinzufügen | | `frontend/src/pages/Admin.tsx` | OutputType-Tabelle: Workflow-Dropdown | | `frontend/src/pages/AssetLibrary.tsx` | NEU: Asset Library Management UI | | `frontend/src/api/asset_libraries.ts` | NEU: API-Client | | `frontend/src/pages/ProductDetail.tsx` | Mesh-Attribute-Anzeige | | `frontend/src/pages/Upload.tsx` | Sanity-Check-Dialog nach Import | | `frontend/src/api/imports.ts` | NEU: import_validation API | | `frontend/src/App.tsx` | Route /asset-libraries | | `backend/app/api/routers/notification_configs.py` | NEU: notification_configs CRUD | | `backend/app/main.py` | notification_configs router registrieren | | `backend/app/api/routers/orders.py` | dispatch_renders → dispatch_render_with_workflow | | `backend/app/api/routers/output_types.py` | workflow_definition_id im PATCH | | `backend/app/schemas/output_type.py` | workflow_definition_id im Schema | | `backend/app/domains/rendering/tasks.py` | K3: apply_asset_library_materials_task | | `backend/app/tasks/step_tasks.py` | OCC sharp edge extraction in render_step_thumbnail | | `render-worker/scripts/still_render.py` | mark_sharp / UV seams support | | `render-worker/scripts/blender_render.py` | mark_sharp / UV seams support | | `backend/app/services/step_processor.py` | extract_mesh_edge_data() für sharp edges | --- ## Tasks ### Task 1: Upload-Link in Sidebar [QUICK WIN] - **Datei**: `frontend/src/components/layout/Layout.tsx` - **Was**: `Upload`-Icon + NavLink zu `/upload` in der Sidebar für alle eingeloggten User - **Akzeptanzkriterium**: Upload-Link sichtbar in Sidebar ### Task 2: notification_configs Backend-Router [Phase I] - **Datei**: `backend/app/api/routers/notification_configs.py` (NEU), `backend/app/main.py` - **Was**: REST-Endpoints für `notification_configs` Tabelle (044 bereits migriert): - `GET /api/notification-configs` — gibt configs für aktuellen User zurück (mit Defaults falls keine Zeilen) - `PUT /api/notification-configs/{event_type}/{channel}` — setzt enabled=true/false - `POST /api/notification-configs/reset` — löscht alle configs des Users → Defaults gelten wieder - Response: `[{event_type, channel, enabled}]` - Auth: `get_current_user` (jeder kann seine eigenen Configs verwalten) - **Akzeptanzkriterium**: NotificationSettings.tsx zeigt Toggle-Matrix und speichert korrekt ### Task 3: OutputType → WorkflowDefinition — Schema + API - **Datei**: `backend/app/schemas/output_type.py`, `backend/app/api/routers/output_types.py` - **Was**: - `OutputTypeOut` + `OutputTypePatch`: `workflow_definition_id: uuid.UUID | None` hinzufügen - PATCH-Handler: `workflow_definition_id` setzen wenn in body - `OutputTypeOut` soll `workflow_name: str | None` als convenience field enthalten - **Akzeptanzkriterium**: `PATCH /api/output-types/{id}` mit `{"workflow_definition_id": "..."}` funktioniert ### Task 4: Workflow-Dispatch Integration - **Datei**: `backend/app/api/routers/orders.py` - **Was**: In `dispatch_renders()` (Zeile 910): - Statt `dispatch_order_line_render.delay(str(line.id))` aufrufen: - `from app.domains.rendering.dispatch_service import dispatch_render_with_workflow` - `dispatch_render_with_workflow(str(line.id))` aufrufen - Das dispatch_service lädt OutputType.workflow_definition_id und nutzt Celery Canvas falls verknüpft; fällt auf Legacy zurück wenn nicht. - **Akzeptanzkriterium**: Dispatch nutzt neuen Pfad; Legacy-Fallback bleibt erhalten ### Task 5: Asset Library API-Client (Frontend) - **Datei**: `frontend/src/api/asset_libraries.ts` (NEU) - **Was**: ```typescript export interface AssetLibrary { id, name, description, original_filename, catalog: {materials: string[], node_groups: string[]}, is_active, created_at } export async function listAssetLibraries(): Promise export async function uploadAssetLibrary(name: string, file: File, description?: string): Promise export async function refreshLibraryCatalog(id: string): Promise export async function deleteAssetLibrary(id: string): Promise export async function updateAssetLibrary(id: string, data: Partial): Promise ``` - **Akzeptanzkriterium**: TypeScript kompiliert fehlerfrei ### Task 6: Asset Library Management Page (K2) - **Datei**: `frontend/src/pages/AssetLibrary.tsx` (NEU) - **Was**: Seite `/asset-libraries` (admin/PM): - Liste der Asset Libraries als Karten: Name, Filename, Badge-Grid mit Materialien/Node-Groups aus `catalog` - Upload-Button: Datei-Input für `.blend` + Name-Feld → `uploadAssetLibrary()` - "Refresh Catalog" Button je Library → `refreshLibraryCatalog(id)` → Toast - Toggle `is_active` → `updateAssetLibrary()` - Delete-Button → `deleteAssetLibrary()` - Leer-Zustand: "No asset libraries yet — upload a .blend file" - **Akzeptanzkriterium**: Libraries hochladen, Katalog anzeigen, löschen ### Task 7: Asset Library Route + Sidebar-Link - **Datei**: `frontend/src/App.tsx`, `frontend/src/components/layout/Layout.tsx` - **Was**: - App.tsx: Route `/asset-libraries` → `` (AdminRoute) - Layout.tsx: Sidebar-Link "Asset Libraries" mit `Library`-Icon (admin/PM) - **Abhängigkeiten**: Task 6 ### Task 8: OutputType Workflow-Dropdown (Frontend) - **Datei**: `frontend/src/pages/Admin.tsx` (OutputTypeTable-Bereich) - **Was**: In der OutputType-Tabelle eine neue Spalte "Workflow": - Dropdown mit allen WorkflowDefinitions (aus `GET /api/workflows`) + "— None —" - Bei Änderung: `PATCH /api/output-types/{id}` mit `{workflow_definition_id: ...}` - Wenn kein Workflow: zeige "Legacy" Badge; wenn Workflow: zeige Workflow-Name als grünes Badge - **Akzeptanzkriterium**: Workflow kann pro OutputType zugewiesen werden ### Task 9: Excel Sanity-Check Backend (Phase H) - **Datei**: `backend/app/domains/imports/sanity_check.py` (NEU), `backend/app/domains/imports/router.py` - **Was**: - Sync-Funktion `run_sanity_check(import_validation_id: str)`: - Lädt ImportValidation-Record - Iteriert über `rows` (ParsedRows aus Excel) - Für jede Zeile: prüft ob `name_cad_modell` eine CadFile zugeordnet hat (`cad_files.original_name ILIKE`) - Prüft ob `cad_part_materials` alle Materialien in `materials`-Tabelle (via Alias-Lookup) auflösbar sind - Erstellt `summary: {total_rows, rows_with_cad, rows_without_cad, material_gaps: [{product, missing_material}]}` - Status → 'completed' - Celery-Task `validate_excel_import_task(import_validation_id)` Queue `step_processing` - Endpoint `GET /api/imports/{id}/validation` — gibt ImportValidation zurück - Endpoint `POST /api/imports/{id}/add-alias` — schnell einen Alias hinzufügen (part_name → material) - ImportValidation DB-Zugriif: sync SQLAlchemy (Celery-kompatibel) - **Akzeptanzkriterium**: Nach Excel-Upload wird Import-Validierung automatisch gequeuet; `summary` liefert Material-Lücken ### Task 10: Upload.tsx — Sanity-Check-Dialog (Phase H) - **Datei**: `frontend/src/pages/Upload.tsx` - **Was**: Nach erfolgreichem Excel-Upload: - `GET /api/imports/{id}/validation` pollen (alle 3s, max 30s) - Wenn status='completed': Ampel-Dialog anzeigen: - Grün-Badge: "X Produkte mit STEP-Datei" - Gelb-Badge: "Y Produkte ohne STEP-Datei" - Rote Liste: Material-Lücken (Part-Name → fehlendes Material, mit "Add Alias" Button) - "Proceed" Button schließt Dialog - Import API erweitern: `api/imports.ts` mit `getImportValidation(id)`, `addMaterialAlias()` - **Akzeptanzkriterium**: Nach Upload erscheint Dialog mit Produktions-Readiness ### Task 11: Mesh-Attribute Anzeige in ProductDetail (Phase D) - **Datei**: `frontend/src/pages/ProductDetail.tsx` - **Was**: Im CAD-File-Bereich, nach dem Status-Badge: - Wenn `product.cad_file.mesh_attributes` vorhanden: kleine Info-Karte - Felder: `volume_cm3` (aus `mesh_attributes.volume_mm3 / 1000` → "12.5 cm³"), `surface_area_cm2`, `bounding_box` ("W×H×D mm"), `sharp_angle_deg` (aus `suggested_smooth_angle`) - Label "Geometry" mit `Ruler`-Icon - **API-Änderung**: Product-API gibt `cad_file.mesh_attributes` zurück (prüfen ob vorhanden) - **Akzeptanzkriterium**: Volumen, Oberfläche, BBox in ProductDetail sichtbar (wenn vorhanden) ### Task 12: OCC Edge-Analyse → mesh_attributes (Sharp/Seam) - **Datei**: `backend/app/services/step_processor.py` - **Was**: Neue Funktion `extract_mesh_edge_data(step_path: str) -> dict`: - Öffnet STEP via OCC - Iteriert über alle Faces und deren Edges - Berechnet Winkel zwischen adjazenten Faces per Edge (Dihedralwinkel) - Sammelt: - `suggested_smooth_angle`: Median-Winkel aller Kanten wo Winkel > 5° (typisch 30–60°) - `has_mechanical_edges`: bool (True wenn mehrere Kanten mit Winkel > 60° → Lagerkante) - `sharp_edge_midpoints`: Liste von `[x,y,z]` mm-Koordinaten der scharfen Kanten-Mittelpunkte (max 500 Stück, für Winkel > 45°) - Integriert in `extract_cad_metadata()`: nach `_extract_step_objects()` aufrufen, Ergebnis in `mesh_attributes` mergen - Fallback: bei OCC-Fehler gracefully `{}` zurückgeben - **Akzeptanzkriterium**: `cad_files.mesh_attributes` enthält `suggested_smooth_angle` nach Verarbeitung ### Task 13: Blender-Scripts — mark_sharp + UV-Seams - **Dateien**: `render-worker/scripts/still_render.py`, `render-worker/scripts/blender_render.py` - **Was**: Nach STL-Import, vor dem Render: 1. Wenn `mesh_attributes.suggested_smooth_angle` vorhanden: diesen Winkel statt globalem `smooth_angle` nutzen 2. Neue Funktion `_mark_sharp_edges(obj, smooth_angle_deg, sharp_edge_midpoints=None)`: - Setzt `obj.data.auto_smooth_angle = math.radians(smooth_angle_deg)` - Wählt Kanten aus: `bpy.ops.mesh.edges_select_sharp(sharpness=math.radians(smooth_angle_deg))` - Ruft `bpy.ops.mesh.mark_sharp()` auf - Wenn `sharp_edge_midpoints` vorhanden: KD-Tree matching → zusätzliche Kanten markieren 3. Neue Funktion `_create_uv_seams_from_sharps(obj)`: - Startet Edit-Mode - Selektiert alle Sharp-Kanten: `[e for e in mesh.edges if e.use_edge_sharp]` - Markiert diese als Seams: `edge.use_seam = True` - Ruft `bpy.ops.uv.smart_project(angle_limit=math.radians(smooth_angle_deg))` auf 4. Beide Funktionen nach `_import_stl()` aufrufen (Mode A + Mode B) - **Akzeptanzkriterium**: Gerenderte Bilder zeigen korrekte Kanten für Lager (30° Winkel scharf sichtbar) ### Task 14: K3 — apply_asset_library_materials_task - **Datei**: `backend/app/domains/rendering/tasks.py` - **Was**: Neuer Celery-Task: ```python @celery_app.task(name="...apply_asset_library_materials_task", queue="thumbnail_rendering") def apply_asset_library_materials_task(order_line_id: str, asset_library_id: str) -> dict: # Lädt OrderLine, CadFile, AssetLibrary # Prüft ob asset_library.blend_file_path existiert # Ruft Blender subprocess auf mit asset_library.py: # blender --background --python asset_library.py -- --stl_path X --asset_library_blend Y --material_map '{...}' # Returns {'status': 'applied', 'materials_count': N} ``` Skript `render-worker/scripts/asset_library.py` existiert bereits. - **Akzeptanzkriterium**: Task läuft ohne Fehler wenn Blender verfügbar ### Task 15: K4/K5 — export_gltf + export_blend via Blender - **Datei**: `backend/app/domains/rendering/tasks.py` - **Was**: `export_gltf_for_order_line_task` und `export_blend_for_order_line_task` überarbeiten: - Statt trimesh: Blender subprocess mit `export_gltf.py` / `export_blend.py` - Asset Library path aus LinkedAssetLibrary (via OutputType) übergeben falls vorhanden - GLB → MinIO `production-exports/{cad_file_id}/{order_line_id}.glb` - .blend → MinIO `production-exports/{cad_file_id}/{order_line_id}.blend` - MediaAsset erstellen mit `gltf_production` / `blend_production` type - **Akzeptanzkriterium**: Export-Tasks produzieren GLB/BLEND-Dateien in MinIO --- ## Abhängigkeiten ``` Sofort (parallel): Task 1 (Upload Link) Task 2 (Notification Config Backend) Task 3 (OutputType Schema) Task 5 (Asset Library API) Task 9 (Sanity Check Backend) Task 12 (OCC Edge Analyse) Nach Task 3: Task 4 (Dispatch Integration) Task 8 (OutputType Workflow Dropdown) Nach Task 5+6: Task 6 (Asset Library Page) — braucht Task 5 Task 7 (Route + Sidebar) — braucht Task 6 Nach Task 9: Task 10 (Upload Sanity Dialog) Nach Task 11: Task 11 (Mesh Display) — unabhängig Nach Task 12: Task 13 (Blender Scripts) Nach Task 14: Task 15 (K4/K5 Exports) ``` ## Migrations-Check Alle benötigten Migrationen existieren bereits: - 043: import_validations ✅ - 044: notification_configs ✅ - 045: asset_libraries ✅ Keine neue Migration nötig.