Files
CapaKraken/plan.md
T
Hartmut 47b2aeec72 feat: prevent duplicate resource-project assignments
Engine (packages/engine):
- New checkDuplicateAssignment() pure function: detects same resource
  assigned to same project with overlapping dates
- 15 unit tests covering: overlap, no-overlap, cancelled, self-exclude,
  string dates, PROPOSED status

Application layer (packages/application):
- createAssignment: throws CONFLICT before DB write if duplicate found
- fillDemandRequirement: same check before entering transaction

AI Assistant (packages/api/router/assistant-tools.ts):
- create_allocation: checks before creating, returns helpful error message
- fill_demand: same check using demand's projectId

UI (apps/web):
- AllocationModal: amber warning when resource already assigned to
  selected project with overlapping dates (non-blocking)

Database cleanup:
- Found and merged 1 duplicate: Wong Wong on Porsche Taycan Sport Film
  (2 overlapping PROPOSED assignments merged into 1)

Regression: 298 engine tests pass (283 + 15 new). TypeScript clean.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-23 08:51:49 +01:00

8.5 KiB

Duplicate Assignment Prevention — Plan

Anforderungsanalyse

Problem: Ressourcen koennen demselben Projekt mehrfach zugewiesen werden mit ueberlappenden Zeitraeumen. Beispiel: Wong Wong ist zweimal fuer "Porsche Taycan Sport Film" am 15. April eingetragen.

Ursache: Weder die Application-Layer-Funktionen (createAssignment, fillDemandRequirement) noch die API-Router pruefen, ob dieselbe Resource bereits eine aktive Zuweisung zum selben Projekt im selben Zeitraum hat. Die bestehende validateAvailability prueft nur die Gesamt-Stunden (Overbooking), nicht Projekt-Duplikate.

Loesung: Duplicate-Check an 3 Stellen einfuegen (defense-in-depth):

  1. Application LayercheckDuplicateAssignment() Funktion im Engine-Paket
  2. API Layer — Validierung in den Mutations vor dem Create
  3. AI Assistantcreate_allocation und fill_demand Tools pruefen vor Ausfuehrung

Scope: Betrifft packages/engine, packages/application, packages/api, UI (Warnmeldung).


Betroffene Pakete & Dateien

Paket Dateien Art der Aenderung
packages/engine src/allocation/duplicate-check.ts create — Pure Funktion checkDuplicateAssignment()
packages/engine src/index.ts edit — Export hinzufuegen
packages/application src/use-cases/allocation/create-assignment.ts edit — Duplicate-Check vor DB-Write
packages/application src/use-cases/allocation/fill-demand-requirement.ts edit — Duplicate-Check vor DB-Write
packages/api src/router/allocation.ts edit — Duplicate-Check in create, createAssignment Mutations
packages/api src/router/assistant-tools.ts edit — Check in create_allocation, fill_demand Tools
packages/api src/router/timeline.ts edit — Check in batchShiftAllocations (falls Shift Duplikat erzeugt)
apps/web src/components/allocations/AllocationModal.tsx edit — Warning anzeigen wenn Duplikat erkannt
apps/web src/components/staffing/StaffingPanel.tsx edit — Warning im Assign-Formular
packages/engine src/__tests__/duplicate-check.test.ts create — Unit Tests

Task-Liste

Phase 1: Engine — Pure Duplicate-Check Funktion

  • Task 1: Duplicate-Check Funktion erstellen → packages/engine/src/allocation/duplicate-check.ts

    interface ExistingAssignment {
      id: string;
      resourceId: string;
      projectId: string;
      startDate: Date;
      endDate: Date;
      status: string; // nur CONFIRMED, ACTIVE, PROPOSED zaehlen
    }
    
    interface DuplicateCheckResult {
      isDuplicate: boolean;
      conflictingAssignment?: ExistingAssignment;
      message?: string; // z.B. "Resource Wong Wong is already assigned to Porsche Taycan (2026-03-01 to 2026-06-30)"
    }
    
    export function checkDuplicateAssignment(
      resourceId: string,
      projectId: string,
      startDate: Date,
      endDate: Date,
      existingAssignments: ExistingAssignment[],
      excludeAssignmentId?: string, // fuer Updates: eigene ID ausschliessen
    ): DuplicateCheckResult
    
    • Prueft: Gibt es in existingAssignments eine Zuweisung mit gleicher resourceId + gleicher projectId deren Zeitraum sich mit [startDate, endDate] ueberschneidet?
    • Ignoriert: CANCELLED Status, eigene ID (bei Updates)
    • Overlap-Logik: existingStart <= newEnd && existingEnd >= newStart
  • Task 2: Unit Tests → packages/engine/src/__tests__/duplicate-check.test.ts

    • Kein Duplikat: verschiedene Projekte
    • Kein Duplikat: gleicher Projekt, aber nicht ueberlappend (vor/nach)
    • Duplikat: gleicher Projekt, vollstaendig ueberlappend
    • Duplikat: gleicher Projekt, teilweise ueberlappend
    • Kein Duplikat: gleicher Projekt, aber CANCELLED
    • Kein Duplikat: Update der eigenen Zuweisung (excludeAssignmentId)
  • Task 3: Export → packages/engine/src/index.ts

Phase 2: Application Layer — Integration in Create-Flows

  • Task 4: createAssignment erweitern → packages/application/src/use-cases/allocation/create-assignment.ts

    • Nach dem Laden von existingBookings (Zeile 101-106): checkDuplicateAssignment() aufrufen
    • Bei isDuplicate: true: throw new TRPCError({ code: "CONFLICT", message: result.message })
    • Bestehende Bookings bereits vorhanden — nur filtern auf gleichen projectId
  • Task 5: fillDemandRequirement erweitern → packages/application/src/use-cases/allocation/fill-demand-requirement.ts

    • Vor dem Assignment-Create: gleicher Check
    • DemandRequirement hat bereits projectId — diesen nutzen

Phase 3: API + AI Assistant

  • Task 6: AI Assistant Tools erweitern → packages/api/src/router/assistant-tools.ts
    • create_allocation Tool: Vor createAssignment Call, bestehende Assignments pruefen
    • fill_demand Tool: Gleicher Check
    • Bei Duplikat: Tool gibt klare Fehlermeldung zurueck statt Exception: "Cannot assign: Wong Wong is already assigned to Porsche Taycan Sport Film from 2026-01-15 to 2026-06-30. Use update_allocation_status to modify the existing assignment instead."

Phase 4: UI Warnungen

  • Task 7: AllocationModal Warning → apps/web/src/components/allocations/AllocationModal.tsx

    • Wenn User Resource + Project + Dates auswaehlt: pruefen ob Duplikat existiert
    • Query: trpc.allocation.listView({ projectId }) — bereits geladen
    • Gelbe Warning-Box: "This resource is already assigned to this project from X to Y"
    • Submit-Button nicht blockieren (Warning, nicht Error) — User kann bewusst doppelt buchen
  • Task 8: StaffingPanel Assign Warning → apps/web/src/components/staffing/StaffingPanel.tsx

    • Im AssignForm: nach Project-Auswahl pruefen ob Resource bereits dort zugewiesen
    • Gleiche Warning-Box wie AllocationModal

Phase 5: Bereinigung bestehender Duplikate

  • Task 9: Cleanup-Script → packages/db/scripts/deduplicate-assignments.ts
    • Findet alle Duplikate: gleiche resourceId + projectId mit ueberlappenden Dates
    • Merged sie: behaelt die aeltere Zuweisung, entfernt die neuere (oder merged Zeitraeume)
    • Dry-run Modus: zeigt was geaendert wuerde ohne zu aendern
    • Kann via pnpm --filter @planarchy/db exec tsx scripts/deduplicate-assignments.ts ausgefuehrt werden

Abhaengigkeiten

Task 1 (Engine Funktion) → Task 2 (Tests) → Task 3 (Export)
                        ↘
Task 3 → Task 4 + Task 5 (parallel, Application Layer)
Task 3 → Task 6 (AI Assistant)
Task 3 → Task 7 + Task 8 (parallel, UI Warnungen)
Task 9 (Cleanup) ist unabhaengig, kann jederzeit ausgefuehrt werden
  • Tasks 4+5 koennen parallel (verschiedene Dateien)
  • Tasks 6, 7, 8 koennen parallel (verschiedene Dateien)
  • Task 9 sollte nach den anderen Tasks laufen (damit neue Duplikate verhindert werden)

Akzeptanzkriterien

  • pnpm test:unit laeuft gruen (inkl. neue duplicate-check Tests)
  • pnpm --filter @planarchy/web exec tsc --noEmit — keine neuen Errors
  • API: createAssignment wirft CONFLICT wenn Resource bereits zum gleichen Projekt zugewiesen
  • API: fillDemandRequirement wirft CONFLICT bei Duplikat
  • AI Assistant: create_allocation gibt klare Fehlermeldung bei Duplikat
  • AI Assistant: fill_demand gibt klare Fehlermeldung bei Duplikat
  • UI: AllocationModal zeigt gelbe Warning bei erkanntem Duplikat
  • UI: StaffingPanel AssignForm zeigt Warning bei Duplikat
  • Cleanup: Bestehende Duplikate in der DB bereinigt
  • Timeline: Wong Wong hat keine doppelten Strips mehr am 15. April

Risiken & offene Fragen

Risiken

  • False Positives: Legitime Doppelbuchungen (z.B. verschiedene Rollen auf demselben Projekt) werden blockiert → Mitigation: Warning im UI, Error nur im API — User kann override-en, AI Assistant gibt Hinweis
  • Race Condition: Zwei gleichzeitige Requests koennten beide den Check passieren → Mitigation: DB-Level unique constraint ist nicht moeglich (flexible Zeitraeume), aber Transaction-Isolation schuetzt

Offene Fragen

  1. Soll der Check nur warnen oder blockieren? → Empfehlung: API blockiert (CONFLICT), UI warnt (gelbe Box, Submit moeglich), AI blockiert
  2. Was passiert bei Updates/Shifts? → excludeAssignmentId nutzen um die eigene Zuweisung auszuschliessen
  3. Welche Status zaehlen als "aktiv"? → CONFIRMED, ACTIVE, PROPOSED — nicht CANCELLED, DRAFT
  4. Sollen verschiedene Rollen erlaubt sein? → Vorschlag: Ja, aber mit Warning. Gleiche Rolle + gleiches Projekt = Block, verschiedene Rolle = Warning only