feat: project cover art with AI generation, branding rename, RBAC fix, computation graph
- Add DALL-E cover art generation for projects (Azure OpenAI + standard OpenAI)
- CoverArtSection component with generate/upload/remove/focus-point controls
- Client-side image compression (10MB input → WebP/JPEG, max 1920px)
- DALL-E settings in admin panel (deployment, endpoint, API key)
- MCP assistant tools for cover art (generate_project_cover, remove_project_cover)
- Rename "Planarchy" → "plANARCHY" across all UI-facing text (13 files)
- Fix hardcoded canEdit={true} on project detail page — now checks user role
- Computation graph visualization (2D/3D) for calculation rules
- OG image and OpenGraph metadata
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,123 +1,397 @@
|
||||
# Plan: Budget per Role / Demand
|
||||
# Enterprise Notification & Task Management System
|
||||
|
||||
## Anforderungsanalyse
|
||||
|
||||
Jede Staffing-Demand (Rolle) in einem Projekt soll ein eigenes Budget bekommen. Aktuell gibt es nur ein einziges `budgetCents` auf Projektebene. Ziel:
|
||||
### Was wird gebaut?
|
||||
Ein mehrstufiges Notification- und Task-Management-System, das die bestehende Notification-Infrastruktur (Prisma-Model, Bell-Icon, SSE, SMTP) zu einem vollwertigen Enterprise-System ausbaut. Vier Kernfähigkeiten:
|
||||
|
||||
1. **DemandRequirement** bekommt ein `budgetCents` Feld (wie viel Budget ist dieser Rolle zugewiesen)
|
||||
2. **StaffingRequirement** (JSONB auf Project) bekommt ein optionales `budgetCents` Feld fuer den Wizard
|
||||
3. **Project Wizard Step 3** zeigt Budget-Input pro Rolle + verbleibendes unverteiltes Projekt-Budget
|
||||
4. **Project Detail Page** zeigt pro Demand: zugewiesenes Budget vs. gebuchtes Budget (aus Assignments berechnet)
|
||||
5. **Fill Demand Modal** zeigt verbleibendes Rollen-Budget beim Zuweisen von Ressourcen
|
||||
1. **Personal Reminders** — User legen eigene Erinnerungen an (Datum/Zeit, optionale Wiederholung, verknüpft mit Entity)
|
||||
2. **Targeted Notifications** — Admins/Manager senden Notifications an User, Rollen, Projektbeteiligte, OrgUnits
|
||||
3. **Task Management** — Actionable Tasks mit Status-Tracking, Dashboard-Widget, Entity-Verknüpfung
|
||||
4. **AI Assistant Integration** — Assistent liest offene Tasks, führt sie aus (Urlaub genehmigen, Allokation erstellen, etc.)
|
||||
|
||||
### Architektur-Entscheidung
|
||||
### Bestehende Infrastruktur (wiederverwendbar)
|
||||
| Komponente | Status | Datei |
|
||||
|------------|--------|-------|
|
||||
| `Notification` Prisma-Model | Vorhanden (einfach) | `packages/db/prisma/schema.prisma:1291` |
|
||||
| Notification tRPC-Router | list, unreadCount, markRead, create | `packages/api/src/router/notification.ts` |
|
||||
| NotificationBell + Drawer | Bell-Icon mit Badge, Dropdown-Panel | `apps/web/src/components/notifications/NotificationBell.tsx` |
|
||||
| SSE EventBus (Redis Pub/Sub) | `NOTIFICATION_CREATED` Event | `packages/api/src/sse/event-bus.ts` |
|
||||
| SMTP Email | `sendEmail()` + SystemSettings | `packages/api/src/lib/email.ts` |
|
||||
| AI Assistant Tools | `list_notifications`, `mark_notification_read` | `packages/api/src/router/assistant-tools.ts` |
|
||||
| Dashboard Widget-Registry | 8 Widgets, Pattern etabliert | `apps/web/src/components/dashboard/widgets/` |
|
||||
|
||||
`budgetCents` als **explizite Spalte** auf `DemandRequirement` (nicht in `metadata` JSONB), weil:
|
||||
- Typsicher, indizierbar, aggregierbar via SQL
|
||||
- Konsistent mit dem Muster auf `Project.budgetCents`
|
||||
- Default `0` = kein Budget zugewiesen (abwaertskompatibel)
|
||||
### Betroffene Pakete
|
||||
- **packages/db** — Schema-Erweiterung (Notification -> Task-Felder, neues Broadcast-Model)
|
||||
- **packages/shared** — Enums, Typen, Zod-Schemas
|
||||
- **packages/api** — Router-Erweiterung (notification.ts, assistant-tools.ts), Targeting-Logik, Scheduler
|
||||
- **apps/web** — UI (Task-Widget, Reminder-UI, Notification-Center, Admin-Panel)
|
||||
|
||||
---
|
||||
|
||||
## Datenmodell-Design
|
||||
|
||||
### Erweiterung des bestehenden `Notification`-Models
|
||||
Das bestehende Model wird um Task-/Reminder-/Targeting-Felder erweitert. Kein neues Model nötig — ein einheitliches System für Notifications + Tasks + Reminders.
|
||||
|
||||
```prisma
|
||||
model Notification {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
|
||||
// -- Typ & Kategorie --
|
||||
category NotificationCategory @default(NOTIFICATION) // NEU
|
||||
type String // z.B. "VACATION_REQUESTED", "TASK_ASSIGNED", "REMINDER"
|
||||
priority NotificationPriority @default(NORMAL) // NEU
|
||||
|
||||
// -- Inhalt --
|
||||
title String
|
||||
body String?
|
||||
entityId String?
|
||||
entityType String?
|
||||
link String? // NEU: Deep-Link zur relevanten Seite
|
||||
|
||||
// -- Task-Felder (nur fuer category TASK / APPROVAL) --
|
||||
taskStatus TaskStatus? // NEU: OPEN / IN_PROGRESS / DONE / DISMISSED
|
||||
taskAction String? // NEU: maschinenlesbare Aktion z.B. "approve_vacation:clxyz123"
|
||||
assigneeId String? // NEU: wem der Task zugewiesen ist
|
||||
dueDate DateTime? // NEU: Faelligkeitsdatum
|
||||
completedAt DateTime? // NEU: Zeitpunkt der Erledigung
|
||||
completedBy String? // NEU: wer hat erledigt (User-ID, oder "ai-assistant")
|
||||
|
||||
// -- Reminder-Felder --
|
||||
remindAt DateTime? // NEU: wann soll erinnert werden
|
||||
recurrence String? // NEU: "daily" | "weekly" | "monthly" | null
|
||||
nextRemindAt DateTime? // NEU: naechster Erinnerungszeitpunkt (berechnet)
|
||||
|
||||
// -- Targeting-Metadaten (fuer Bulk-Sends) --
|
||||
sourceId String? // NEU: Referenz auf die urspruengliche Broadcast-Nachricht
|
||||
senderId String? // NEU: wer hat die Notification erstellt (User-ID)
|
||||
channel String @default("in_app") // NEU: "in_app" | "email" | "both"
|
||||
|
||||
// -- Timestamps --
|
||||
readAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt // NEU
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
assignee User? @relation("taskAssignee", fields: [assigneeId], references: [id])
|
||||
sender User? @relation("notificationSender", fields: [senderId], references: [id])
|
||||
|
||||
@@index([userId, readAt])
|
||||
@@index([userId, category, taskStatus]) // NEU: Task-Queries
|
||||
@@index([nextRemindAt]) // NEU: Reminder-Scheduler
|
||||
@@index([assigneeId, taskStatus]) // NEU: Assigned-Tasks
|
||||
@@map("notifications")
|
||||
}
|
||||
|
||||
enum NotificationCategory {
|
||||
NOTIFICATION // System/Admin-Benachrichtigung (read-only)
|
||||
REMINDER // Persoenliche Erinnerung (self-created)
|
||||
TASK // Actionable Task mit Status-Tracking
|
||||
APPROVAL // Genehmigungsworkflow (approve/reject)
|
||||
}
|
||||
|
||||
enum NotificationPriority {
|
||||
LOW
|
||||
NORMAL
|
||||
HIGH
|
||||
URGENT
|
||||
}
|
||||
|
||||
enum TaskStatus {
|
||||
OPEN
|
||||
IN_PROGRESS
|
||||
DONE
|
||||
DISMISSED
|
||||
}
|
||||
```
|
||||
|
||||
### Broadcast-Model (fuer Gruppen-Notifications)
|
||||
|
||||
```prisma
|
||||
model NotificationBroadcast {
|
||||
id String @id @default(cuid())
|
||||
senderId String
|
||||
title String
|
||||
body String?
|
||||
link String?
|
||||
category NotificationCategory @default(NOTIFICATION)
|
||||
priority NotificationPriority @default(NORMAL)
|
||||
channel String @default("in_app")
|
||||
|
||||
// -- Targeting --
|
||||
targetType String // "user" | "role" | "project" | "orgUnit" | "all"
|
||||
targetValue String? // Role-Name, Project-ID, OrgUnit-ID, oder null fuer "all"
|
||||
|
||||
// -- Scheduling --
|
||||
scheduledAt DateTime? // null = sofort
|
||||
sentAt DateTime?
|
||||
recipientCount Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
sender User @relation(fields: [senderId], references: [id])
|
||||
|
||||
@@index([senderId])
|
||||
@@index([scheduledAt, sentAt])
|
||||
@@map("notification_broadcasts")
|
||||
}
|
||||
```
|
||||
|
||||
### Task-Action Registry (Enterprise-Pattern)
|
||||
|
||||
Maschinenlesbare Aktionen ermoeglichen dem AI-Assistenten, Tasks direkt zu erledigen:
|
||||
|
||||
```typescript
|
||||
// packages/api/src/lib/task-actions.ts
|
||||
const TASK_ACTION_REGISTRY: Record<string, TaskActionHandler> = {
|
||||
"approve_vacation": { permission: "manageVacations", execute: ... },
|
||||
"reject_vacation": { permission: "manageVacations", execute: ... },
|
||||
"fill_demand": { permission: "manageAllocations", execute: ... },
|
||||
"confirm_allocation":{ permission: "manageAllocations", execute: ... },
|
||||
"review_budget": { permission: "manageProjects", execute: ... },
|
||||
};
|
||||
```
|
||||
|
||||
Format: `"action_name:entity_id"` — einfach parsbar, erweiterbar.
|
||||
|
||||
---
|
||||
|
||||
## Betroffene Pakete & Dateien
|
||||
|
||||
| Paket | Dateien | Art der Aenderung |
|
||||
|-------|---------|-----------------|
|
||||
| `packages/db` | `prisma/schema.prisma` | **edit** — `budgetCents` auf DemandRequirement |
|
||||
| `packages/shared` | `src/types/project.ts` | **edit** — `budgetCents?` auf StaffingRequirement |
|
||||
| `packages/shared` | `src/types/allocation.ts` | **edit** — `budgetCents` auf DemandRequirementRecord |
|
||||
| `packages/shared` | `src/schemas/allocation.schema.ts` | **edit** — `budgetCents` in CreateDemandRequirementSchema |
|
||||
| `packages/api` | `src/router/allocation.ts` | **edit** — budgetCents durchreichen in create/update |
|
||||
| `packages/api` | `src/router/project.ts` | **edit** — bei Demand-Erstellung aus Wizard budgetCents uebernehmen |
|
||||
| `apps/web` | `src/components/projects/ProjectWizard.tsx` | **edit** — Step 3: Budget-Input pro Rolle + Restbudget-Anzeige |
|
||||
| `apps/web` | `src/components/projects/ProjectDemandsTable.tsx` | **edit** — Spalten: Allocated Budget, Booked Budget |
|
||||
| `apps/web` | `src/components/allocations/FillOpenDemandModal.tsx` | **edit** — Rollen-Budget-Anzeige |
|
||||
| Paket | Dateien | Art |
|
||||
|-------|---------|-----|
|
||||
| `packages/db` | `prisma/schema.prisma` | edit — Notification erweitern, Enums, Broadcast-Model |
|
||||
| `packages/shared` | `src/types/notification.ts` | create — Typen, Enums, Zod-Schemas |
|
||||
| `packages/shared` | `src/types/enums.ts` | edit — re-exportieren |
|
||||
| `packages/api` | `src/router/notification.ts` | edit — Task-CRUD, Reminder-CRUD, Broadcast, Targeting |
|
||||
| `packages/api` | `src/router/index.ts` | edit — ggf. neuen Router registrieren |
|
||||
| `packages/api` | `src/router/assistant-tools.ts` | edit — neue Tools: list_tasks, execute_task_action, etc. |
|
||||
| `packages/api` | `src/router/assistant.ts` | edit — TOOL_PERMISSION_MAP + System-Prompt |
|
||||
| `packages/api` | `src/sse/event-bus.ts` | edit — neue Event-Types |
|
||||
| `packages/api` | `src/lib/email.ts` | edit — Notification-Email-Templates |
|
||||
| `packages/api` | `src/lib/notification-targeting.ts` | create — Recipient-Aufloesung |
|
||||
| `packages/api` | `src/lib/task-actions.ts` | create — Action-Registry |
|
||||
| `packages/api` | `src/lib/reminder-scheduler.ts` | create — Reminder-Dispatcher |
|
||||
| `apps/web` | `src/components/notifications/NotificationBell.tsx` | edit — Tabs, Task-Badge |
|
||||
| `apps/web` | `src/components/notifications/NotificationCenter.tsx` | create — Full-Page |
|
||||
| `apps/web` | `src/components/notifications/ReminderModal.tsx` | create |
|
||||
| `apps/web` | `src/components/notifications/BroadcastModal.tsx` | create |
|
||||
| `apps/web` | `src/components/notifications/TaskCard.tsx` | create |
|
||||
| `apps/web` | `src/components/dashboard/widgets/TaskWidget.tsx` | create |
|
||||
| `apps/web` | `src/app/(app)/notifications/page.tsx` | create |
|
||||
| `apps/web` | `src/app/(app)/admin/notifications/page.tsx` | create |
|
||||
| `apps/web` | `src/components/layout/AppShell.tsx` | edit — Nav-Links |
|
||||
| `apps/web` | `src/hooks/useTimelineSSE.ts` | edit — Task-Events |
|
||||
|
||||
---
|
||||
|
||||
## Task-Liste
|
||||
## Task-Liste (atomare Schritte)
|
||||
|
||||
### Task 1: Prisma Schema — `budgetCents` auf DemandRequirement
|
||||
### Phase N.1 — Datenmodell & Shared Types
|
||||
|
||||
Datei: `packages/db/prisma/schema.prisma`
|
||||
- [ ] **Task 1:** Shared-Typen erstellen -> `packages/shared/src/types/notification.ts`
|
||||
- NotificationCategory, NotificationPriority, TaskStatus Enums
|
||||
- CreateReminderSchema, CreateBroadcastSchema, UpdateTaskStatusSchema (Zod)
|
||||
- TaskAction Interface
|
||||
|
||||
- [ ] `budgetCents Int @default(0)` auf DemandRequirement hinzufuegen
|
||||
- [ ] `pnpm db:push` ausfuehren (generiert Prisma Client)
|
||||
- [ ] Dev-Server neustarten (`.next/` Cache loeschen)
|
||||
- [ ] **Task 2:** Prisma-Schema erweitern -> `packages/db/prisma/schema.prisma`
|
||||
- Notification-Model: category, priority, taskStatus, taskAction, assigneeId, dueDate, completedAt, completedBy, remindAt, recurrence, nextRemindAt, sourceId, senderId, channel, link, updatedAt
|
||||
- Enums: NotificationCategory, NotificationPriority, TaskStatus
|
||||
- Model: NotificationBroadcast
|
||||
- User-Relations: taskAssignee, notificationSender, broadcasts
|
||||
- Indexes: [userId, category, taskStatus], [nextRemindAt], [assigneeId, taskStatus]
|
||||
|
||||
### Task 2: Shared Types aktualisieren
|
||||
- [ ] **Task 3:** `pnpm db:push` + Dev-Server neu starten
|
||||
|
||||
Dateien:
|
||||
- `packages/shared/src/types/project.ts` — `budgetCents?: number` auf `StaffingRequirement`
|
||||
- `packages/shared/src/types/allocation.ts` — `budgetCents: number` auf `DemandRequirementRecord`
|
||||
- `packages/shared/src/schemas/allocation.schema.ts` — `budgetCents: z.number().int().min(0).default(0)` in `CreateDemandRequirementBaseSchema`
|
||||
### Phase N.2 — API: Router + Targeting + Scheduler
|
||||
|
||||
### Task 3: API — budgetCents durchreichen
|
||||
- [ ] **Task 4:** SSE Event-Types erweitern -> `packages/api/src/sse/event-bus.ts`
|
||||
- TASK_ASSIGNED, TASK_COMPLETED, TASK_STATUS_CHANGED, REMINDER_DUE, BROADCAST_SENT
|
||||
- Emit-Helper: emitTaskAssigned(), emitTaskCompleted(), emitReminderDue()
|
||||
|
||||
Datei: `packages/api/src/router/allocation.ts`
|
||||
- [ ] **Task 5:** Notification-Router erweitern -> `packages/api/src/router/notification.ts`
|
||||
- list: Filter nach category, taskStatus, priority
|
||||
- listTasks (protectedProcedure): offene Tasks + zugewiesene Tasks
|
||||
- taskCounts (protectedProcedure): Counts nach Status
|
||||
- updateTaskStatus (protectedProcedure): OPEN->IN_PROGRESS->DONE/DISMISSED
|
||||
- createReminder (protectedProcedure): eigene Erinnerung anlegen
|
||||
- updateReminder / deleteReminder (protectedProcedure)
|
||||
- createBroadcast (managerProcedure): Targeted Notification an Gruppe
|
||||
- listBroadcasts (managerProcedure)
|
||||
- createTask (managerProcedure): Task fuer User/Gruppe
|
||||
- assignTask (managerProcedure): Task zuweisen
|
||||
- delete (protectedProcedure): eigene Notifications loeschen
|
||||
|
||||
- [ ] `createDemandRequirement` — `budgetCents` aus Input an Prisma create weitergeben
|
||||
- [ ] `updateDemandRequirement` — `budgetCents` updatebar machen
|
||||
- [ ] `checkResourceAvailability` — optional: gebuchte Kosten vs. Rollen-Budget zurueckgeben
|
||||
- [ ] **Task 6:** Broadcast-Targeting -> `packages/api/src/lib/notification-targeting.ts` (create)
|
||||
- resolveRecipients(targetType, targetValue, db): User-IDs aufloesen
|
||||
- "user" -> einzelner User
|
||||
- "role" -> alle User mit SystemRole
|
||||
- "project" -> Ressourcen mit aktiver Allokation -> verknuepfte User
|
||||
- "orgUnit" -> Ressourcen in OrgUnit -> verknuepfte User
|
||||
- "all" -> alle aktiven User
|
||||
|
||||
Datei: `packages/api/src/router/project.ts`
|
||||
- [ ] **Task 7:** Email-Templates -> `packages/api/src/lib/email.ts` (edit)
|
||||
- sendNotificationEmail(userId, notification): HTML mit Title, Body, Deep-Link
|
||||
- sendTaskEmail(userId, task): Template mit Task-Details + Action-Link
|
||||
|
||||
- [ ] Bei Projekt-Erstellung mit StaffingReqs: wenn `staffingReq.budgetCents` vorhanden, an DemandRequirement weitergeben
|
||||
- [ ] **Task 8:** Task-Action-Registry -> `packages/api/src/lib/task-actions.ts` (create)
|
||||
- Registry-Pattern: action_name -> { permission, execute(entityId, ctx) }
|
||||
- Initiale Actions: approve_vacation, reject_vacation, fill_demand, confirm_allocation
|
||||
|
||||
### Task 4: Project Wizard Step 3 — Budget-Input pro Rolle
|
||||
- [ ] **Task 9:** Reminder-Scheduler -> `packages/api/src/lib/reminder-scheduler.ts` (create)
|
||||
- Intervall (60s): WHERE nextRemindAt <= NOW()
|
||||
- Fuer jeden faelligen Reminder: In-App Notification + optional Email
|
||||
- nextRemindAt neu berechnen oder null setzen
|
||||
- Catch-up bei Start (ueberfaellige sofort ausloesen)
|
||||
|
||||
Datei: `apps/web/src/components/projects/ProjectWizard.tsx`
|
||||
### Phase N.3 — AI Assistant Integration
|
||||
|
||||
- [ ] Pro StaffingRequirement-Karte: neues Feld "Role Budget (EUR)" (Input, konvertiert zu Cents)
|
||||
- [ ] Oben im Step: Anzeige "Project Budget: X EUR | Allocated: Y EUR | Remaining: Z EUR"
|
||||
- [ ] Farbkodierung: gruen wenn alles verteilt, amber wenn Rest, rot wenn ueberallokiert
|
||||
- [ ] Budget-Wert wird in `state.staffingReqs[i].budgetCents` gespeichert
|
||||
- [ ] **Task 10:** Neue Tool-Definitionen -> `packages/api/src/router/assistant-tools.ts`
|
||||
- list_tasks: offene Tasks/Approvals mit Filter
|
||||
- get_task_detail: Details inkl. verknuepfter Entity
|
||||
- update_task_status: Status aendern
|
||||
- execute_task_action: maschinenlesbare Aktion ausfuehren
|
||||
- create_reminder: Erinnerung anlegen
|
||||
- create_task_for_user: Task fuer anderen User (Manager-only)
|
||||
- send_broadcast: Notification an Gruppe (Manager-only)
|
||||
|
||||
### Task 5: Project Detail Page — Budget-Spalten pro Demand
|
||||
- [ ] **Task 11:** Tool-Executors implementieren -> `packages/api/src/router/assistant-tools.ts`
|
||||
- execute_task_action: parst taskAction-String, dispatcht an Action-Registry
|
||||
- Permission-Check pro Action (nicht pauschal)
|
||||
|
||||
Datei: `apps/web/src/components/projects/ProjectDemandsTable.tsx`
|
||||
- [ ] **Task 12:** Permission-Map + Prompt -> `packages/api/src/router/assistant.ts`
|
||||
- TOOL_PERMISSION_MAP erweitern
|
||||
- System-Prompt: Tasks/Reminders als Faehigkeit beschreiben
|
||||
|
||||
- [ ] Neue Spalte "Allocated Budget" — zeigt `demand.budgetCents` formatiert als EUR
|
||||
- [ ] Neue Spalte "Booked Budget" — berechnet: Summe der `dailyCostCents * Arbeitstage` aller Assignments dieses Demands
|
||||
- Hinweis: Die Demand-Daten vom Server enthalten `assignments[]` — daraus berechnen
|
||||
- [ ] Neue Spalte "Remaining" — Allocated minus Booked
|
||||
- [ ] Farbkodierung: gruen wenn unter Budget, rot wenn ueber Budget
|
||||
### Phase N.4 — Frontend
|
||||
|
||||
### Task 6: Fill Demand Modal — Rollen-Budget anzeigen
|
||||
- [ ] **Task 13:** NotificationBell erweitern -> `apps/web/src/components/notifications/NotificationBell.tsx`
|
||||
- Zweiter Badge: Task-Count (orange) neben Notification-Count (rot)
|
||||
- Tabs: "Alle" | "Tasks" | "Erinnerungen"
|
||||
- Task-Items mit Quick-Actions (Done/Dismiss)
|
||||
- Link zu "/notifications"
|
||||
|
||||
Datei: `apps/web/src/components/allocations/FillOpenDemandModal.tsx`
|
||||
- [ ] **Task 14:** TaskCard-Komponente -> `apps/web/src/components/notifications/TaskCard.tsx` (create)
|
||||
- Titel, Body, Due-Date, Priority-Badge, Entity-Link
|
||||
- Aktionen: "Start" / "Done" / "Dismiss"
|
||||
- Approval-Variante: "Approve" / "Reject"
|
||||
- Priority-farbcodiert (URGENT=rot, HIGH=orange, NORMAL=blau, LOW=grau)
|
||||
|
||||
- [ ] Im Demand-Summary oben: "Role Budget: X EUR | Booked: Y EUR | Remaining: Z EUR"
|
||||
- [ ] Beim Hinzufuegen einer Ressource zum Plan: geschaetzte Kosten anzeigen (LCR * verfuegbare Stunden)
|
||||
- [ ] Warnung wenn geplante Kosten das Rollen-Budget ueberschreiten
|
||||
- [ ] **Task 15:** ReminderModal -> `apps/web/src/components/notifications/ReminderModal.tsx` (create)
|
||||
- Titel, Body, Datum/Uhrzeit, Wiederholung (keine/taeglich/woechentlich/monatlich)
|
||||
- Optional: Entity-Verknuepfung (Projekt/Ressource Dropdown)
|
||||
|
||||
- [ ] **Task 16:** BroadcastModal -> `apps/web/src/components/notifications/BroadcastModal.tsx` (create)
|
||||
- Manager/Admin-only
|
||||
- Targeting: Dropdown (Alle/Rolle/Projekt/OrgUnit) + Wert-Auswahl
|
||||
- Inhalt: Titel, Body, Priority, Kategorie
|
||||
- Kanal: In-App / Email / Beides
|
||||
- Scheduling: Sofort / Zeitgesteuert
|
||||
- Vorschau: "Wird an X Empfaenger gesendet"
|
||||
|
||||
- [ ] **Task 17:** NotificationCenter -> `apps/web/src/app/(app)/notifications/page.tsx` (create)
|
||||
- Tabs: Alle | Notifications | Tasks | Reminders | Approvals
|
||||
- Filter: Status, Priority, Zeitraum
|
||||
- Bulk: "Alle lesen", "Alle erledigt"
|
||||
- "Neue Erinnerung" Button
|
||||
|
||||
- [ ] **Task 18:** TaskWidget -> `apps/web/src/components/dashboard/widgets/TaskWidget.tsx` (create)
|
||||
- Kompakte Liste offener Tasks (max 5-7)
|
||||
- Sortiert: Priority -> Due-Date
|
||||
- Quick-Actions: Done/Dismiss
|
||||
- Footer: "X offene Tasks — Alle anzeigen"
|
||||
- In Widget-Registry eintragen
|
||||
|
||||
- [ ] **Task 19:** Admin Broadcast-Seite -> `apps/web/src/app/(app)/admin/notifications/page.tsx` (create)
|
||||
- Liste gesendeter Broadcasts
|
||||
- "Neue Benachrichtigung senden" Button
|
||||
- Statistiken: gesendet/gelesen pro Broadcast
|
||||
|
||||
- [ ] **Task 20:** AppShell Navigation -> `apps/web/src/components/layout/AppShell.tsx` (edit)
|
||||
- "Notifications" fuer alle Rollen
|
||||
- "Broadcast" unter Admin (ADMIN/MANAGER)
|
||||
|
||||
- [ ] **Task 21:** SSE-Hook -> `apps/web/src/hooks/useTimelineSSE.ts` (edit)
|
||||
- Auf TASK_ASSIGNED, TASK_COMPLETED, REMINDER_DUE reagieren
|
||||
- React-Query invalidieren: notification.listTasks, notification.taskCounts
|
||||
|
||||
### Phase N.5 — Auto-Tasks & Audit
|
||||
|
||||
- [ ] **Task 22:** Automatische Task-Erzeugung bei Business-Events
|
||||
- vacation.create -> Task "Urlaubsantrag genehmigen" an Manager (APPROVAL)
|
||||
- Ueberallokation -> Task "Ueberallokation aufloesen" an Manager
|
||||
- Projekt-Deadline < 30 Tage + offene Demands -> Task "Demands besetzen"
|
||||
- demand.create -> Task "Demand besetzen" an Manager
|
||||
|
||||
- [ ] **Task 23:** Audit-Trail -> `packages/api/src/lib/audit.ts` (create)
|
||||
- logTaskAction(taskId, userId, action, details)
|
||||
- completedBy: "ai-assistant" fuer AI-erledigte Tasks
|
||||
|
||||
---
|
||||
|
||||
## Abhaengigkeiten
|
||||
|
||||
- **Task 1 → Task 2 → Task 3** (sequentiell: Schema → Types → API)
|
||||
- **Task 4** benoetigt Task 2 (StaffingRequirement-Typ mit budgetCents)
|
||||
- **Task 5** benoetigt Task 1+3 (budgetCents auf DemandRequirement + API liefert es)
|
||||
- **Task 6** benoetigt Task 5 (gleiche Berechnung)
|
||||
- Task 4 und Task 5 koennen **parallel** nach Task 3
|
||||
```
|
||||
Task 1 (Shared Types) ---+
|
||||
Task 2 (Schema) ---------+--> Task 3 (db:push)
|
||||
|
|
||||
Task 3 --> Task 4 (SSE) |
|
||||
Task 3 --> Task 5 (Router)|
|
||||
Task 3 --> Task 6 (Targeting)
|
||||
Task 3 --> Task 7 (Email) |
|
||||
Task 3 --> Task 8 (Actions)|
|
||||
|
|
||||
Task 5 + 6 --> Task 9 (Scheduler)
|
||||
Task 5 + 8 --> Task 10-12 (AI)
|
||||
|
|
||||
Task 5 --> Task 13-21 (Frontend, parallel moeglich)
|
||||
Task 5 --> Task 22 (Auto-Tasks)
|
||||
```
|
||||
|
||||
**Parallel:**
|
||||
- Task 4 + 5 + 6 + 7 + 8 (verschiedene Dateien)
|
||||
- Task 13-21 (verschiedene Dateien, 13+14 vor 17+18 empfohlen)
|
||||
|
||||
**Sequentiell:**
|
||||
- Task 1 -> 2 -> 3 (Schema)
|
||||
- Task 5 -> 10 -> 11 (Router -> Tools -> Executors)
|
||||
|
||||
---
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] `pnpm db:push` laeuft ohne Fehler
|
||||
- [ ] `pnpm db:push` ohne Fehler
|
||||
- [ ] `pnpm --filter @planarchy/api exec tsc --noEmit` — 0 Errors
|
||||
- [ ] `pnpm --filter @planarchy/web exec tsc --noEmit` — 0 Errors
|
||||
- [ ] `pnpm test:unit` — alle Tests gruen
|
||||
- [ ] `pnpm --filter @planarchy/web exec tsc --noEmit` — keine neuen Errors
|
||||
- [ ] Project Wizard Step 3: Budget-Input pro Rolle sichtbar, Restbudget wird live berechnet
|
||||
- [ ] Project Detail `/projects/[id]`: Demands-Tabelle zeigt Allocated / Booked / Remaining Budget
|
||||
- [ ] Fill Demand Modal: Rollen-Budget und geschaetzte Kosten sichtbar
|
||||
- [ ] Bestehende Projekte/Demands funktionieren weiterhin (budgetCents default 0)
|
||||
- [ ] User kann eigene Erinnerung anlegen (Datum, Wiederholung, Entity)
|
||||
- [ ] Admin/Manager kann Broadcast an Rolle/Projekt/OrgUnit senden
|
||||
- [ ] Broadcast erzeugt individuelle Notifications pro Empfaenger
|
||||
- [ ] Tasks im Dashboard-Widget, sortiert nach Priority + Due-Date
|
||||
- [ ] Task-Status aenderbar ueber UI (Open -> In Progress -> Done/Dismissed)
|
||||
- [ ] AI-Assistent kann list_tasks aufrufen und offene Tasks anzeigen
|
||||
- [ ] AI-Assistent kann execute_task_action ausfuehren (z.B. Urlaub genehmigen)
|
||||
- [ ] Erledigte Tasks zeigen completedBy (User oder "AI-Assistent")
|
||||
- [ ] Email-Versand bei channel "email" oder "both"
|
||||
- [ ] SSE-Events invalidieren React-Query-Caches
|
||||
- [ ] Reminder-Scheduler erzeugt puenktlich Notifications
|
||||
- [ ] RBAC: User sehen nur eigene; Manager zugewiesene; Admin Broadcasts
|
||||
|
||||
---
|
||||
|
||||
## Risiken & offene Fragen
|
||||
|
||||
1. **Abwaertskompatibilitaet:** `@default(0)` stellt sicher, dass bestehende Demands kein Budget haben (0 = nicht gesetzt). UI sollte "Not set" anzeigen wenn 0.
|
||||
2. **Budget-Berechnung Booked:** `dailyCostCents` ist pro Tag. Gebuchte Kosten = `dailyCostCents * Anzahl Arbeitstage im Zeitraum`. Diese Berechnung existiert bereits im Engine-Paket (`computeBudgetStatus`).
|
||||
3. **StaffingReqs JSONB:** Die `staffingReqs` auf Project sind JSONB. Aeltere Projekte haben kein `budgetCents` darin — der Wizard muss `budgetCents ?? 0` defaulten.
|
||||
4. **Budget-Ueberschreitung:** Soll weiterhin erlaubt sein (Warnung, kein Block) — konsistent mit dem bestehenden Ansatz bei Projekt-Budget.
|
||||
### Risiken
|
||||
1. **Reminder-Scheduler Zuverlaessigkeit**: Node.js-setInterval kann bei Restart verpassen. Mitigation: Catch-up bei Start (alle ueberfaelligen sofort ausloesen).
|
||||
2. **Broadcast-Skalierung**: "An alle" mit 500 Usern = 500 Rows. Mitigation: Batch-Insert (createMany).
|
||||
3. **Task-Action-Sicherheit**: Permissions pro Action pruefen, nicht pauschal. Mitigation: Action-Registry mit Permission pro Handler.
|
||||
4. **Schema-Migration**: Neue Felder nullable oder mit Default -> bestehende Notifications funktionieren weiter.
|
||||
|
||||
### Offene Fragen
|
||||
1. **Scheduler**: setInterval im SSE-Handler oder separater Worker/Cron? Empfehlung: setInterval (reicht fuer <1000 User)
|
||||
2. **Task-Delegation**: User duerfen Tasks an andere weiterdelegieren? Empfehlung: Ja (Manager-only)
|
||||
3. **Retention**: Wie lange alte Notifications aufbewahren? Empfehlung: 90 Tage Auto-Cleanup
|
||||
4. **Recurring Tasks**: Tasks wiederkehrend wie Reminders? Empfehlung: Phase 2
|
||||
5. **Approval-Chains**: Mehrstufige Genehmigung? Empfehlung: Phase 2, erstmal einstufig
|
||||
|
||||
Reference in New Issue
Block a user