Zum Hauptinhalt springen

Systemübersicht & Datenflüsse

Diese Seite ist die Master-Referenz für alle Datenflüsse und Modulverbindungen in Fortuna Softec. Sie zeigt, wie jede Aktion in einem Modul Auswirkungen auf andere Module hat.


Der Hauptkreislauf

 Angebot/LV ──► Budget (Herzstück) ──► Aufgaben ──► Einsatzplanung


Leistungsstand ◄── Rechnungen ◄── Herzstück ◄── Genehmigung ◄── Bautagebuch ◄── Worker-App

├── Kosten (projectExpenses + actualCost)
└── Compliance (ArbZG + PDF-Archiv)

Kurzversion

SchrittModulErgebnis
1Angebot-KonstruktorLeistungsverzeichnis mit SOLL-Preisen
2Herzstück (Budget-Import)Budget-Strukturen, Gewerke, Positionen
3EinsatzplanungAufgaben mit Worker-Zuweisungen
4Worker-AppBautagebuch-Eintrag (Zeiten, Fotos, Material, Maschinen, Unterschrift)
5GenehmigungszentraleGeprüfter Eintrag mit Stundensatz und Materialpreisen
6Herzstück (Kosten)IST-Kosten in projectExpenses + actualCost auf Budget-Task
7RechnungenAbschlags-/Schlussrechnung mit Budget-Verknüpfung
8LeistungsstandKumulative Abrechnungsmatrix pro Position und Rechnung

Detaillierte Datenflüsse

1. Von Angebot zu Budget

Beteiligte Systeme: Angebot-Konstruktor (eigenständige App) --> Fortuna Softec Herzstück

SchrittBeschreibungTechnisch
Angebot erstellenKonstruktor erstellt LV aus Excel, GAEB, Voice oder manuellExterne App (fortuna-angebotstool)
ExportBlueprint JSON oder Excel-ExportKonstruktor-Export
Import ins HerzstückPOST /api/offer-importErstellt projectBudgetStructures + projectBudgetTrades + projectBudgetTasks
GewerkeAutomatisch aus LV-Struktur erzeugtprojectBudgetTrades mit Nachlass-, Baustrom-, Versicherungs- und Sonstige-Prozenten
PositionenJede LV-Zeile wird zu einem Budget-TaskprojectBudgetTasks mit totalPrice, unitPrice, quantity, unit
Import-Modi"Ersetzen" (vorherige Struktur wird superseded) oder "Anhängen" (zusätzlicher Auftrag)budgetVersion + status (draft -> active -> superseded)

Datenmodell nach Import:

Budget-Struktur (projectBudgetStructures)
├── status: "active"
├── vatRate: 19 (Standard)
├── defaultDiscountPercent, defaultRetentionPercent

├── Gewerk 1 (projectBudgetTrades)
│ ├── discountPercent, clientUtilitiesPercent, insurancePercent, miscPercent
│ ├── retentionPercent, vatRateOverride
│ ├── totalPlannedNet (aggregiert)
│ ├── totalInvoicedNet, totalPaidNet (vom InvoiceBudgetSyncService)
│ │
│ ├── Position 1.1 (projectBudgetTasks, isHeader: false)
│ │ ├── totalPrice, unitPrice, quantity, unit
│ │ ├── actualCost (IST, vom BudgetProgressService)
│ │ ├── manualActualCost (manuelle Überschreibung)
│ │ ├── invoicedPercent, paidPercent (vom InvoiceBudgetSyncService)
│ │ └── progressPercent
│ │
│ └── Position 1.2 ...

└── Gewerk 2 ...

Referenz: Herzstück - Übersicht | GAEB-Import


2. Von Budget zu Einsatzplanung

Beteiligte Services: Herzstück UI --> Aufgaben-System --> Einsatzplanung --> Notification-Service

SchrittBeschreibungTechnisch
Aufgabe erstellenAus Budget-Position "Aufgabe erstellen"Erstellt projectTasks mit budgetTaskId-Verknüpfung
Worker zuweisenEinsatzplanung-Kalender: Worker auf Zeitslot ziehenErstellt taskAssignments mit date, startTime, endTime
Einsatz veröffentlichenStatus planned -> publishedPush-Notification an Worker via NotificationService
KI-EinsatzplanerExperimentelles Feature: AI schlägt optimale Zuweisungen vorEinsatzplanService in lib/services/einsatzplan-service.ts

KI-Einsatzplaner (experimentell):

API: /api/projects/[id]/einsatzplan/{context, propose, apply}
Service: lib/services/einsatzplan-service.ts

Aggregiert:
- Offene/unzugewiesene Aufgaben mit Prioritäts-Scoring
- Worker-Verfügbarkeit (Projektmitglieder, ArbZG über alle Projekte)
- Historische Gewerk-Inferenz aus vergangenen Einsätzen/Tagebüchern
- ArbZG-Compliance-Constraints (max. 10h/Tag, min. Ruhezeit)

Referenz: Einsatzplanung - Übersicht | Team-Zuweisung


3. Von Einsatzplanung zur Worker-App

Beteiligte Systeme: Einsatzplanung-Backend --> Worker-App (PWA)

SchrittBeschreibung
Veröffentlichte EinsätzeErscheinen in Worker-App unter "Heute" (/worker/dashboard)
AnzeigeProjekt, Aufgabe, Zeitraum, Beschreibung (automatisch übersetzt)
Dokumentation startenWorker tippt auf Einsatz --> Dokumentations-Wizard
Ohne EinsatzWorker kann auch eigene Einträge erstellen ("Stundenlohn / Eigener Einsatz")

Worker-App Seiten:

RouteFunktion
/worker/dashboardHeutige Einsätze, Alerts, Pending-Status
/worker/diaryArchiv aller eigenen Einträge
/worker/diary/edit/[id]Eintrag bearbeiten (nur vor Genehmigung)
/worker/reportStundenberichte
/worker/acknowledgeUnterweisungsdokumente bestätigen
/worker/moreEinstellungen, Profil

Referenz: Worker-App - Übersicht | PWA-Installation | Übersetzungsfunktion


4. Von Worker-App zum Bautagebuch

Beteiligte Systeme: Dokumentations-Wizard --> Bautagebuch (diaryEntries)

Der Dokumentations-Wizard führt in 7 Schritten durch die Erfassung:

SchrittNameInhaltPflicht
1ProjektProjekt auswählenJa
2AufgabeAufgabe/Einsatz auswählenJa
3ArbeitszeitStart, Ende, Pause (mit reportedWorkSessions)Ja
4BerichtArbeitsbeschreibung (Text)Ja
5ExtrasMaterial, Maschinen (QR-Scan), Fotos, SprachnotizenNein
6UnterschriftSelbst-Signatur oder Externe SignaturJa
7AbsendenEintrag wird erstelltJa

Eintrag enthält nach Absenden:

FeldBeschreibungQuelle
reportedWorkSessionsArbeitszeitblöcke (Start, Ende, Pause)Wizard Schritt 3
actualStartTime, actualEndTime, actualBreakMinutesAggregierte ZeitenBerechnet aus Sessions
imagesFotos (mit GPS-Daten)Wizard Schritt 5
materialsArray: {name, quantity, unit, cost}Wizard Schritt 5
machinesArray: {name, machineId, operatingHours, hourlyRate, qrCode}Wizard Schritt 5 (QR-Scan)
signaturesArray mit {signatureType, signerUserId, workerTimeAttestation}Wizard Schritt 6
workContentLockedtrue bei externer SignaturAutomatisch
approvalStatus"submitted" oder "approved" (bei Dual-Signatur sofort)Status-Logik
taskAssignmentIdVerknüpfung zum EinsatzFalls von Einsatz gestartet
budgetTaskIdVerknüpfung zur Budget-PositionFalls Aufgabe Budget hat

Referenz: Dokumentations-Wizard | Bautagebuch - Übersicht


5. Vom Bautagebuch zur Genehmigung

Beteiligte Systeme: Bautagebuch --> Genehmigungszentrale

Zwei Wege zum "Genehmigt":

                    Signatur-Typ?
╱ ╲
Selbst-Signatur Externe Signatur
│ │
▼ ▼
Genehmigungszentrale Sofort genehmigt
(Manager prüft) (Inhalt gesperrt)
│ │
▼ ▼
Drei Optionen: onDiaryApproved()
- Genehmigen │
- Korrektur anfordern ▼
- Ablehnen Y-Split (s. Punkt 6)

Manager-Prüfung (bei Selbst-Signatur):

PrüfschrittDetails
Zeitangaben plausibel?Start, Ende, Pause
Fotos aussagekräftig?Visuelle Kontrolle
Material korrekt?Mengen und Arten
Materialpreise zuweisenPreis pro Einheit festlegen
Stundensatz festlegenFallback-Kette (s. unten)
Budget-Position zuordnenFalls nicht schon verknüpft

Manager kann ergänzen:

  • Material hinzufügen (mit addedBy-Audit-Trail)
  • Maschinen hinzufügen (Katalog-Referenz wird aufgelöst)
  • Fotos hinzufügen (z. B. Lieferscheine)
  • Belege per Drag & Drop zuordnen

Berechtigungsprüfung: Nur Rollen mit canApproveTime oder canEditDiary Capability:

  • Admin: Alle Capabilities
  • Office (Backoffice): canApproveTime + canEditDiary
  • Projektleiter: canApproveTime + canEditDiary
  • Worker: Keine Genehmigungsrechte

Referenz: Genehmigungszentrale - Übersicht | Y-Split | Signaturen


6. Von Genehmigung zum Herzstück (Y-Split)

Beteiligte Services: approve/route.ts --> diary-approval-hooks.ts --> budget-progress-service.ts --> diary-expense-sync.ts --> diary-approval-accounting.ts

Dies ist der kritischste Datenfluss im System. Bei Genehmigung passieren mehrere Dinge atomar in einer Transaktion:

Transaktions-Schritte (approve/route.ts)

SchrittAktionService
1Diary-Entry auf approved setzen, isFinalized: trueDirekt in Transaktion
2Alte diary-verknüpfte Expenses löschen (Idempotenz)deleteDiaryLinkedExpenses()
3Personalkosten-Expense erstellenDirekt in Transaktion
4Material-Expenses erstellen (pro Material-Position)Direkt in Transaktion
5Manager-ergänzte Material-Expenses erstellenDirekt in Transaktion
6Time-Entries auf approved setzen + budgetTaskId synchronisierensetDiaryTimeEntriesStatus()
7Aggregierten diary-Material-Expense verhindern (Anti-Doppelbuchung)syncDiaryMaterialsExpense()
8Budget-Kosten synchronisierensyncBudgetFromDiaryEntry()

Nach der Transaktion

SchrittAktionService
9Alle betroffenen Budget-Tasks neu berechnensyncBudgetTask() pro Task
10Verknüpften Einsatz auf completed setzentaskAssignments Update
11PDF generieren (§16 ArbZG)generateAndStoreTimesheetPdf()
12Worker benachrichtigenNotificationService.create()
13Fotos synchronisierensyncDiaryEntryPhotos() (Inngest)

Ast 1 -- Kosten-Berechnung

Personalkosten:

Arbeitszeit x Stundensatz = Personalkosten
-> Expense: type="labor", costCategory="labor_internal"
-> metadata.sourceType = "diary_entry_labor"

Materialkosten (pro Material-Position):

Menge x Preis pro Einheit = Materialkosten
-> Expense: type="material", costCategory="material_delivery"
-> metadata.sourceType = "diary_entry_material"
-> Optional: Beleg-Foto (documentUrl/documentKey)

Maschinenkosten (Katalog-verknüpft):

Betriebsstunden x Maschinen-Stundensatz = Maschinenkosten
-> Berechnet im BudgetProgressService (recalculateBudgetTask)
-> Auto-Expense: autoSource="diary_machines"

Budget-Task-Aktualisierung (BudgetProgressService):

actualCost = materialCost + expenseCost + machineCost + uncoveredTimeCost
-> COALESCE(manualActualCost, actualCost, 0) wird überall verwendet
-> Kaskade: Task -> Trade -> Structure (aggregateTradeFromTasks -> aggregateStructureFromTrades)
-> Varianz-Evaluierung bei Kostenüberschreitung

Wichtig -- Spiegel-Prinzip:

  • projectExpenses = Einzelposten (jede Material-Position, jede Arbeitszeitbuchung)
  • actualCost auf projectBudgetTasks = Aggregat (Summe aller Kosten für diesen Task)
  • Diese sind Spiegel, nicht separate Quellen -- niemals addieren!
  • laborNet ist ein Infofeld -- NICHT Teil von actualNet im Herzstueck-Financials-Service

Ast 2 -- Compliance

PrüfungGesetzDetails
Max. 10h/Tag§3 ArbZGInkl. aller genehmigten Einträge desselben Tags
Min. Pausenzeiten§4 ArbZG30 Min. nach 6h, 45 Min. nach 9h
Min. 11h Ruhezeit§5 ArbZGZwischen Ende letzter Schicht und Beginn neuer
Max. 48h/Woche (Durchschnitt)§3 ArbZG24-Wochen-Referenzzeitraum
Nachtarbeit-Compliance§6 ArbZG23:00-06:00, max. 8h für Nachtarbeitnehmer
Mindestlohn-ValidierungMiLoGStundensatz >= Mindestlohn

ArbZG-Prüfung findet VOR der Genehmigung statt. Verstoesse blockieren die Genehmigung mit detaillierten Fehlermeldungen.

PDF-Generierung:

  • Arbeitszeitnachweis gemaess §16 ArbZG
  • Wird in Storage (Hetzner S3 / MinIO) archiviert
  • 2-Jahre-Aufbewahrungspflicht
  • Bei Fehler: approved_pending_pdf Status + Inngest Retry-Job

Referenz: Y-Split | Nachträge


7. Von Herzstück zu Rechnungen

Beteiligte Services: Rechnungs-UI --> InvoiceBudgetSyncService

SchrittBeschreibungTechnisch
Positionen aus BudgetBudget-Positionen als Rechnungspositionen übernehmeninvoiceItems.budgetTaskId -> projectBudgetTasks.id
Rechnung erstellenEntwurf mit Positionen, Texten, Rabatteninvoices Tabelle, Status draft
Finalisieren/VersendenAutomatische Rechnungsnummer (RE-YYYY-NNNN)Status draft -> finalized -> sent
Budget-Sync (Versand)invoicedPercent auf Budget-Tasks aktualisierenInvoiceBudgetSyncService.syncTaskInvoicedPercent()
Budget-Sync (Zahlung)paidPercent auf Budget-Tasks aktualisierenInvoiceBudgetSyncService.syncTaskPaidPercent()
Trade-AggregationtotalInvoicedNet und totalPaidNet auf Gewerk-EbeneInvoiceBudgetSyncService.syncTradeInvoicedTotals()

Rechnungstypen:

TypDB-WertBeschreibung
RechnungstandardStandard-Rechnung
AbschlagsrechnungpartialTeilrechnung nach Baufortschritt
AnzahlungsrechnungadvanceVorauszahlung
SchlussrechnungfinalAbschlussrechnung (zieht alle Abschläge ab)
GutschriftcreditTeilweise Rückerstattung

InvoiceBudgetSyncService -- Berechnungslogik:

invoicedPercent pro Task:
1. Alle invoiceItems mit diesem budgetTaskId finden
2. Nur gültige Rechnungen (finalized/sent/paid/partially_paid/overdue)
3. Summe der netAmounts / taskTotal * 100
4. SET (nicht addieren!) auf projectBudgetTasks.invoicedPercent

paidPercent pro Task:
1. Nur paid/partially_paid Rechnungen
2. paidRatio = invoice.paidAmount / invoice.grossTotal
3. totalPaid = Summe(invoiceItemNetTotal * paidRatio) -- Netto für Konsistenz
4. SET auf projectBudgetTasks.paidPercent

Referenz: Rechnungen


8. Von Rechnungen zum Leistungsstand

Beteiligte Services: LeistungsstandService in lib/services/leistungsstand-service.ts

SchrittBeschreibungTechnisch
Finalisierte RechnungJede versendete Rechnung erzeugt eine SpalteAbrechnungColumn mit Label (AZ I, AZ II, SZ)
Matrix aufbauenPro Budget-Task x Rechnung: kumulativer BetragMatrixCell mit cumulativePercent und cumulativeAmount
ZusammenfassungHauptauftrag + Nachträge - Abzüge + MwSt. = BruttoLeistungsstandSummary pro Spalte

Leistungsstand-Matrix:

                    │ AZ I    │ AZ II   │ SZ
─────────────────────┼─────────┼─────────┼──────
Position 1.01 │ 30% │ 65% │ 100%
Position 1.02 │ 50% │ 80% │ 100%
─────────────────────┼─────────┼─────────┼──────
Hauptauftrag Netto │ 15.000 │ 32.500 │ 50.000
- Nachlass 3% │ -450 │ -975 │ -1.500
Netto abzgl. Nachl. │ 14.550 │ 31.525 │ 48.500
+ Nachträge Netto │ 2.000 │ 4.000 │ 5.000
= Netto gesamt │ 16.550 │ 35.525 │ 53.500
+ 19% MwSt. │ 3.145 │ 6.750 │ 10.165
= Brutto │ 19.695 │ 42.275 │ 63.665

Scope-Modi:

  • trade: Einzelnes Gewerk (mit seinen Positionen)
  • all-trades: Alle Gewerke aggregiert (mit tradeSummaries Breakdown)

Spalten-Labels:

  • Abschlagsrechnungen: AZ I, AZ II, AZ III ... (automatisch römische Nummerierung)
  • Schlussrechnung: SZ
  • Anzahlung: Abschl. {Rechnungsnummer}
  • Deduplizierung bei gleichen Labels

Referenz: Leistungsstand


Nebenkreisläufe

Notfall-System

Service: EmergencyService in lib/services/emergency-service.ts

Worker meldet Notfall (Worker-App)


EmergencyService.createEmergency() -- alles in einer Transaktion:
├── Client finden oder erstellen
├── "Notfälle - {Kundenname}" Sammel-Projekt finden oder erstellen
│ ├── projectType: "notfall"
│ ├── Chat-Kanal automatisch erstellt
│ └── Admin/Office/PL automatisch als Mitglieder
├── Notfall-Aufgabe erstellen (projectTasks, taskType: "notfall_einsatz")
├── Worker sich selbst zuweisen (taskAssignments)
└── Push-Notifications an Office/Admin


Worker dokumentiert (normaler Dokumentations-Wizard)


Normaler Genehmigungsprozess --> Kosten im Herzstück


Abrechnung aus Notfall-Übersicht

Referenz: Notfall-System


Maschinenpark

Admin erstellt Maschine (machines-Katalog)
├── name, type, status, hourlyRate
└── qrCode (automatisch generiert)


Einsatzplanung: Maschine einem Einsatz zuweisen (optional)


Worker-App: QR-Code scannen
├── Katalog-Daten werden aufgelöst (Name, Typ, Stundensatz)
└── Worker erfasst Betriebsstunden


Genehmigung: Manager sieht Maschine mit Katalog-Referenz


BudgetProgressService:
├── machineCost = operatingHours x hourlyRate (nur Katalog-Maschinen)
├── Auto-Expense: autoSource="diary_machines"
└── Betriebsstunden auf Maschine aggregiert

Referenz: Maschinenpark


Nachträge (Change Orders)

Budget: Nachtrag-Position erstellen
├── isChangeOrder: true
├── changeOrderStatus: "pending" -> "approved"
└── Wird in eigener Budget-Struktur oder als Position im bestehenden Gewerk angelegt


Aufgabe erstellen -> Worker dokumentiert -> Genehmigung


Kosten fliessen in Nachtrag-Bereich:
├── Herzstueck-Financials: nachtragTasks separat aggregiert
├── Nachlass/Baustrom/Versicherung gelten NUR für Hauptauftrag, NICHT für Nachträge
└── Leistungsstand: Nachträge als separate Zeilen + eigene Netto-Summe


Auch bei Genehmigung: Manager kann Freifeld/Nachtrag-Position direkt erstellen
├── budgetTaskId: "__NEW__" -> Position wird on-the-fly angelegt
├── Freifeld: positionNumber "FF001" etc.
└── Nachtrag: positionNumber "NT001" etc., isChangeOrder: true, changeOrderStatus: "approved"

Referenz: Nachträge


Kommunikation

Projekt wird erstellt
└── Automatisch: Chat-Kanal für Projekt-Team


Projekt-Chat:
├── @Mentions -> Push-Notifications
├── Aufgaben aus Nachrichten erstellen
└── Automatische Übersetzung für internationale Teams

Referenz: Kommunikation


Service-Portal (Elektro)

Admin erstellt Service-Point im Projekt
├── QR-Code (öffentlich zugänglich)
└── Verknüpft mit Projekt


Externer scannt QR-Code
├── Sieht Service-Historie des Standorts
├── Prüfungen nach DGUV V3
└── Wartungs-Erinnerungen

Referenz: Service-Portal


IFC/BIM-Viewer

IFC-Datei hochladen
└── 3D-Viewer (components/3d/ifc-viewer.tsx)


Elemente mit Budget-Positionen verknüpfen
├── ifc-task-linker.tsx: Budget-Task -> IFC-Element
├── ifc-properties-panel.tsx: Eigenschaften anzeigen
└── ifc-model-tree.tsx: Baumstruktur navigieren


DIN 276 Klassifizierung
└── .mmc Export/Import für BIM-LV Austausch

Referenz: IFC/BIM-Viewer


Kostenarten und ihre Quellen

KostenartQuelleExpense-TypcostCategoryFließt wohin
PersonalArbeitszeit x Stundensatz (Bautagebuch)laborlabor_internalprojectExpenses -> actualCost -> Soll-Ist
Material (Worker)Materialposition (Bautagebuch)materialmaterial_deliveryprojectExpenses -> actualCost -> Soll-Ist
Material (Manager)Manager-ergänztes Materialmaterialmaterial_deliveryprojectExpenses -> actualCost -> Soll-Ist
MaschinenBetriebsstunden x Maschinensatz (Bautagebuch + Katalog)Auto-ExpenseequipmentprojectExpenses -> actualCost -> Soll-Ist
NachunternehmerManuelle EingabesubcontractorsubcontractorprojectExpenses -> actualCost -> Soll-Ist
SonstigesManuelle EingabeothermiscprojectExpenses -> actualCost -> Soll-Ist
MateriallieferungManuelle Eingabematerialmaterial_deliveryprojectExpenses -> actualCost -> Soll-Ist
MateriallagerManuelle Eingabematerialmaterial_stockprojectExpenses -> actualCost -> Soll-Ist

Auto-generierte Expenses (BudgetProgressService):

autoSourceBeschreibungWird gefiltert im Herzstueck-Financials
time_entriesSpiegel der Zeitkosten aus Time-EntriesJa (sonst Doppelzählung mit laborNet)
diary_materialsSpiegel der Materialkosten aus DiaryJa (sonst Doppelzählung mit actualCost)
diary_machinesSpiegel der Maschinenkosten aus DiaryJa (sonst Doppelzählung mit equipmentNet)

Stundensatz-Hierarchie

Die Fallback-Kette für den Stundensatz wird bei der Genehmigung aufgelöst:

PrioritätQuelleBedingungCode-Referenz
1 (höchste)Manuell vom ManagerBei Genehmigung eingegebenhourlyRate aus Request-Body
2Vorheriger StundensatzKorrektur-Re-Approvalentry.hourlyRate (gespeichert)
3Budget-Task unitPriceWenn Unit ein Stunden-Typ ist (h, std, stunde, hour etc.)projectBudgetTasks.unitPrice
4 (niedrigste)Worker-StandardsatzAus Benutzerprofilusers.defaultHourlyRate

Wenn KEIN Stundensatz gefunden wird und Arbeitszeit erfasst ist:

  • Genehmigung wird blockiert mit Fehlercode HOURLY_RATE_REQUIRED
  • Verhindert stille 0-EUR-Buchungen

Herzstueck-Financials-Service (Single Source of Truth)

Datei: lib/services/herzstueck-financials-service.ts

Berechnungslogik pro Gewerk

SOLL (geplant):
plannedNet = Summe aller Tasks (manualTotalPrice ?? totalPrice)
├── hauptauftragNet = Tasks mit isChangeOrder: false
└── nachtragNet = Tasks mit isChangeOrder: true, changeOrderStatus: "approved"

Abzüge (nur auf Hauptauftrag, NICHT auf Nachträge):
adjustmentAmount = hauptauftragNet x (Nachlass% + Baustrom% + Versicherung% + Sonstige%)

plannedNetAdjusted = (hauptauftragNet - adjustmentAmount) + nachtragNet

MwSt. = plannedNetAdjusted x vatRate%
plannedGross = plannedNetAdjusted + MwSt.
Mängelrückhalt = plannedNetAdjusted x retentionPercent%

IST (tatsächlich):
actualFromTasks = Summe aller Tasks (manualActualCost ?? actualCost)

actualFromExpenses = Summe manueller Expenses (ohne autoSource-Expenses!)
├── materialDeliveryNet
├── materialStockNet
├── subcontractorNet
├── equipmentNet
├── laborNetFromExpenses (manuelle Arbeitskosten-Expenses)
└── miscNet

laborNetFromTimeEntries = Summe timeEntries.totalAmount (approved/auto_approved)

laborNet = laborNetFromTimeEntries + laborNetFromExpenses

actualNet = actualFromExpenses > 0
? actualFromExpenses + laborNetFromTimeEntries
: actualFromTasks

Varianz = plannedNetAdjusted - actualNet
Varianz% = (Varianz / plannedNetAdjusted) x 100

Netto-Umsatz BV = plannedNetAdjusted - (materialDelivery + materialStock + labor + subcontractor + equipment + misc)

Kontrollwerte (Override)

Jedes Gewerk kann in metadata.control.totals Überschreibungswerte haben:

  • plannedNet -- überschreibt die Task-Summe
  • plannedNetAdjusted -- überschreibt die berechnete Netto-Adjusted
  • plannedGross -- überschreibt die Brutto-Berechnung

Diese stammen aus dem GAEB/Excel-Import und dienen als "Controlling-Parität" mit externen Systemen.


Immutabilitäts-Regeln

WasRegelAusnahme
Arbeitszeit (Start/Ende/Pause)IMMUTABLE nach UnterschriftNur über Korrektur-Workflow
Arbeitsinhalt (Text, Fotos, Material-Mengen)IMMUTABLE nach externer SignaturManager kann bis Genehmigung ergänzen (bei Selbst-Signatur)
MaterialpreiseEDITIERBAR auch nach GenehmigungPricing-Update
StundensatzEDITIERBAR auch nach GenehmigungPricing-Update
Budget-Position-ZuordnungEDITIERBAR auch nach GenehmigungRe-Assignment
Genehmigte Inhalte (Fotos, Materialien)IMMUTABLE nach GenehmigungKeine neuen Inhalte nachträglich

Signatur-Konsequenzen:

  • Selbst-Signatur: Inhalt editierbar bis Genehmigung, danach gesperrt
  • Externe Signatur: Inhalt sofort gesperrt (workContentLocked: true), Zeiten + Preise weiter korrigierbar

Rollen-Berechtigungen

Datei: lib/role-capabilities.ts

RolleGlobale RechteProjekt-Rechte
Adminmanage_company, manage_users, manage_all_projectsALLE (implizit)
Office (Backoffice)--view_project, diary_view, diary_edit, documents_view, documents_upload, time_entries_view, time_entries_edit, time_entries_approve, team_manage, ai_tools
Projektleiter--view_project, diary_view, diary_edit, time_entries_view, time_entries_edit, time_entries_approve, team_manage, ai_tools
Worker--view_project, diary_view, diary_edit

Wichtig: Projektleiter haben KEINEN Zugriff auf documents_view und documents_upload -- Budget und Dokumente sind Office/Admin vorbehalten.


Schlüssel-Services und ihre Dateien

ServiceDateiVerantwortung
BudgetProgressServicelib/services/budget-progress-service.tsKostenberechnung: Diary -> Expenses -> actualCost -> Kaskade
HerzstueckFinancialsServicelib/services/herzstueck-financials-service.tsSOLL/IST pro Gewerk: plannedNet, actualNet, Varianz
InvoiceBudgetSyncServicelib/services/invoice-budget-sync-service.tsRechnung -> invoicedPercent/paidPercent auf Tasks + Trades
LeistungsstandServicelib/services/leistungsstand-service.tsKumulative Abrechnungsmatrix pro Position/Rechnung
DiaryApprovalHookslib/services/diary-approval-hooks.tsPost-Approval: PDF, Budget-Sync, Time-Entry-Status (mit Retry)
DiaryExpenseSynclib/services/diary-expense-sync.tsMaterial-Expenses aus Diary synchronisieren
DiaryApprovalAccountinglib/services/diary-approval-accounting.tsTime-Entry-Status setzen, alte Expenses löschen
TimeComplianceServicelib/services/time-compliance.tsArbZG-Prüfung (§3/§4/§5/§6/§9)
EmergencyServicelib/services/emergency-service.tsNotfall-Orchestrierung
EinsatzplanServicelib/services/einsatzplan-service.tsKI-Einsatzplanung (Kontext, Vorschlag, Anwendung)
NotificationServicelib/services/notification-service.tsPush/In-App-Benachrichtigungen mit Deduplizierung
TimesheetPdfServicelib/services/timesheet-pdf-service.ts§16 ArbZG PDF-Generierung
PhotoFilesSyncServicelib/services/photo-files-sync-service.tsFotos in Hetzner S3 synchronisieren
BudgetAggregationServicelib/services/budget-aggregation-service.tsTask -> Trade -> Structure Kaskaden-Aggregation

Schlüssel-Regeln (Zusammenfassung)

Kosten

  • actualCost und projectExpenses sind SPIEGEL -- niemals addieren
  • manualActualCost überschreibt actualCost überall: COALESCE(manualActualCost, actualCost, 0)
  • laborNet ist ein Infofeld -- NICHT Teil von actualNet
  • Auto-generierte Expenses (autoSource) werden im Herzstueck-Financials-Service herausgefiltert -- sie existieren nur für die projectExpenses-Ansicht
  • Nachlass/Baustrom/Versicherung gelten nur für Hauptauftrag, nicht für Nachträge

Budget

  • Nur Budget-Strukturen mit status: 'active' werden aggregiert (kein Versions-Doppelzählen)
  • Nur Tasks mit isHeader: false werden in Summen einbezogen
  • Nachträge nur wenn changeOrderStatus: 'approved'
  • manualTotalPrice überschreibt totalPrice, manualQuantity überschreibt quantity

Genehmigung

  • Externe Signatur = sofort genehmigt + Inhalt gesperrt (workContentLocked: true)
  • Selbst-Signatur = Genehmigungszentrale, Inhalt editierbar bis Genehmigung
  • Arbeitszeiten sind IMMER immutable nach Unterschrift (nur Korrektur-Workflow)
  • ArbZG-Prüfung blockiert Genehmigung bei Verstoessen
  • Ohne gültigen Stundensatz (> 0) kann bei erfasster Arbeitszeit nicht genehmigt werden
  • Amendments (Nachträge zu Einträgen) erstellen KEINE separaten Arbeitskosten-Expenses

Rechnungen

  • invoicedPercent = idempotent neuberechnet (SET, nicht ADD)
  • totalInvoicedNet auf Gewerk-Ebene = aggregiert aus Task-Daten
  • Leistungsstand zeigt nur Rechnungen die tatsächlich Items für die gewählten Budget-Tasks haben

Architektur-Diagramm

┌─────────────────────────────────────────────────────────────────────┐
│ FORTUNA SOFTEC │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Konstruktor │───▶│ Herzstück │◀──▶│ Rechnungen │ │
│ │ (Angebote) │ │ (Budget) │ │ (Invoices) │ │
│ └──────────────┘ └──────┬───────┘ └────────┬─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Einsatzplan │◀───│ Aufgaben │ │ Leistungsstand │ │
│ │ (Kalender) │ │ (Tasks) │ │ (Matrix) │ │
│ └──────┬───────┘ └──────────────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Worker-App │───▶│ Bautagebuch │───▶│ Genehmigungszentrale │ │
│ │ (PWA) │ │ (Diary) │ │ (Approval) │ │
│ └──────────────┘ └──────────────┘ └────────┬─────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Kosten │ │ Compliance│ │
│ │ (Y-AST) │ │ (ArbZG) │ │
│ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Maschinenpark│ │ Notfall- │ │ Kommunikation │ │
│ │ (QR/Katalog) │ │ System │ │ (Projekt-Chat) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Service- │ │ IFC/BIM- │ │ KI-Einsatz- │ │
│ │ Portal (QR) │ │ Viewer (3D) │ │ planer (AI) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Datenbank (PostgreSQL) │ │
│ │ projectBudgetStructures | projectBudgetTrades | ...Tasks │ │
│ │ diaryEntries | timeEntries | projectExpenses | invoices │ │
│ │ taskAssignments | projectTasks | machines | notifications │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Storage (Hetzner S3 + MinIO Cache) │ │
│ │ Fotos | PDFs (Stundenzettel) | Dokumente | IFC-Dateien │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

Datenbank-Tabellen und ihre Module

TabelleModulBeschreibung
projectBudgetStructuresHerzstückBudget-Versionen (Original, Nachträge)
projectBudgetTradesHerzstückGewerke mit Abzügen und Finanzkennzahlen
projectBudgetTasksHerzstückBudget-Positionen (SOLL + IST)
projectBudgetVariancesHerzstückAbweichungs-Records bei Kostenüberschreitung
projectExpensesHerzstückEinzelne Kostenbuchungen (Material, Personal, Maschinen)
diaryEntriesBautagebuchArbeitsdokumentation (Zeiten, Fotos, Material, Unterschriften)
timeEntriesZeiterfassungArbeitszeiteinträge (approved/auto_approved/pending)
invoicesRechnungenAusgangs- und Eingangsrechnungen
invoiceItemsRechnungenRechnungspositionen mit Budget-Verknüpfung
taskAssignmentsEinsatzplanungWorker-Zuweisungen zu Aufgaben
projectTasksAufgabenProjekt-Aufgaben (inkl. Notfall-Einsätze)
machinesMaschinenparkMaschinen-Katalog mit QR-Codes und Stundensätzen
notificationsKommunikationIn-App-Benachrichtigungen
channels / channelMembersKommunikationProjekt-Chat-Kanäle
projects / projectMembersProjektverwaltungProjekte und Mitgliedschaften
usersBenutzerverwaltungBenutzer mit Rollen, Stundensätzen, Gewerk
clientsKundenverwaltungKunden mit Adressen

Verwandte Dokumentation

SeiteBeschreibung
EinführungWas ist Fortuna Softec?
SchnellstartErste Schritte
Herzstück - ÜbersichtBudget-Management im Detail
GAEB-ImportBudget importieren
Soll-Ist-VergleichFinanz-Cockpit
LeistungsstandAbrechnungsmatrix
RechnungenRechnungsverwaltung
BerichteFinanzberichte
EinsatzplanungKalender und Zuweisungen
Team-ZuweisungWorker zuweisen
ÜbersetzungMehrsprachige Aufgaben
Worker-AppMobile App für Mitarbeiter
Dokumentations-Wizard7-Schritte-Erfassung
Offline-ModusArbeiten ohne Internet
Foto-ZeitstrahlFoto-Dokumentation
BautagebuchManagement-Sicht auf Einträge
GenehmigungszentralePrüf- und Freigabeprozess
Y-SplitZwei Genehmigungspfade
SignaturenSelbst- vs. Externe Signatur
NachträgeChange Orders
Rollen - ÜbersichtBerechtigungssystem
MaschinenparkGeräte und QR-Codes
Notfall-SystemNotfall-Einsätze
Service-PortalExterne QR-Zugänge
IFC/BIM-Viewer3D-Modelle
KI-AssistentAI-Funktionen
DashboardÜbersichts-Dashboard
KommunikationProjekt-Chat