Skip to content

04 – Projekte

Reifegrad: 🟢 produktiv · Schicht: projects + project_personen + project_team, @work7/domain (Project-Funktionen), src/app/projects/*, src/app/api/projects/*

Zweck

Bauvorhaben/Aufträge als zentrale Klammer: verbindet Kunde (Organisation/Person), internes Team, Termine, Zeiterfassung, Rapporte, Angebote und Rechnungen.

Datenmodell

  • projects: name, description, status (Default active), type (Default Neubau), phase (Default Planung), customer_id (→ kontakte), project_manager_id (→ users), project_number (UNIQUE), Termine (start_date, end_date, deadline), Kalkulation (estimated_hours, actual_hours, budget, actual_costs, completion_percentage 0–100), Standort (address, postal_code, city, geo_coordinates), notes (JSONB). actual_hours und completion_percentage sind abgeleitet (siehe unten) und in der UI schreibgeschützt.
  • project_personen (Migration 036): beteiligte externe Personen/Kontakte, role ∈ {lead, member}, UNIQUE (project_id, person_id).
  • project_team (Migration 037): internes Team aus eigenen Usern, role ∈ {lead, member}, UNIQUE (project_id, user_id).

Wichtig: Es gibt zwei Beteiligten-Dimensionen – externe Kontakt­personen (Auftraggeber- Seite) und internes Personal. Sie sind bewusst getrennt modelliert.

Domain-Funktionen (@work7/domain)

listProjects, getProject, createProject, updateProject sowie Team-/Personen-Verknüpfung: linkUserToProject, listTeamForProject, unlinkUserFromProject, linkPersonToProject, listPersonsForProject, unlinkPersonFromProject. Helper ensureProjectExists schützt referenzierende Operationen.

API-Endpunkte

EndpointFunktion
GET/POST /api/projectsListe / anlegen
GET/PUT/DELETE /api/projects/[id]Detail / ändern / löschen
GET/POST/DELETE /api/projects/[id]/teamInternes Team (User) verwalten
GET/POST/DELETE /api/projects/[id]/personenExterne Kontaktpersonen verwalten

Routenauflösung per Slug/ID über src/lib/load-project-by-route.ts.

UI

  • /projects (Liste), /projects/new (Anlage), /projects/[id] (Detail mit Team, Personen, Terminen, Zeiten, Rapporten), /projects/[id]/edit.
  • Komponenten: src/components/projects/*. AI-Unterstützung: Projektbeschreibung kann über /api/ai/summarize (Task project_description) generiert/zusammengefasst werden (siehe 12-ai-speech-realtime).

Verknüpfungen zu anderen Features

  • Zeiterfassung: Projektzeiten in time_logs (entry_type='project', project_id) – siehe 08-zeiterfassung.
  • Rapporte/Termine/Angebote/Rechnungen referenzieren project_id (alle ON DELETE SET NULL).
  • WhatsApp-Agent nutzt das „sticky active project" pro Session, um Rapport-/Foto-Eingaben einem Projekt zuzuordnen (siehe 11-whatsapp-agent).

Fortschritt (abgeleitet)

packages/domain/src/project-progress.ts ist der einzige Schreibpfad für actual_hours und completion_percentage. syncProjectProgress(companyId, projectId) rechnet beide neu und läuft bei jeder Zeitbuchung/-änderung (Web-API, Agent, Domain-Tool time_entries.project_add_hours) sowie nach Änderungen an estimated_hours, phase oder sub_phase in updateProject.

Regel für den Fertigstellungsgrad:

  1. Mit geschätzten Stunden: actual_hours ÷ estimated_hours, gedeckelt auf 100 %.
  2. Ohne Schätzung: Position der Projektphase in der Phasenliste des Betriebs (Trade-Config). Phase i von n belegt die Spanne [i/n, (i+1)/n]; angezeigt wird deren Mitte — bei den 5 Standardphasen also Planung 10 %, Ausschreibung 30 %, Ausführung 50 %, Abnahme 70 %, Gewährleistung 90 %. Eine gesetzte Unterphase verfeinert innerhalb der Spanne (z. B. Ausführung 40–60 % → 43/50/57 % bei drei Unterphasen).
  3. Weder Schätzung noch bekannte Phase: 0 %.

completion_percentage steht deshalb nicht mehr in der Allow-List von updateProject und ist im Web (FortschrittSectionFields, ProjectForm) sowie im WhatsApp-Agenten (project.preview_update) nicht editierbar. Wer den Wert bewegen will, pflegt geschätzte Stunden oder die Phase.

Einmaliger Backfill für Altprojekte (bleiben sonst auf dem DB-Default 0 %): npx tsx --env-file=.env.local scripts/backfill-project-progress.ts [--dry-run] [companyId]

Bekannte Befunde / offene Punkte

  • actual_costs wird weiterhin manuell gepflegt — es gibt keine Kostenerfassung, die den Wert fortschreibt.
  • Geo-Koordinaten als VARCHAR(100) (kein PostGIS).

Verwandte Notes

Work7 · Software für Handwerksbetriebe