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
| Schritt | Modul | Ergebnis |
|---|---|---|
| 1 | Angebot-Konstruktor | Leistungsverzeichnis mit SOLL-Preisen |
| 2 | Herzstück (Budget-Import) | Budget-Strukturen, Gewerke, Positionen |
| 3 | Einsatzplanung | Aufgaben mit Worker-Zuweisungen |
| 4 | Worker-App | Bautagebuch-Eintrag (Zeiten, Fotos, Material, Maschinen, Unterschrift) |
| 5 | Genehmigungszentrale | Geprüfter Eintrag mit Stundensatz und Materialpreisen |
| 6 | Herzstück (Kosten) | IST-Kosten in projectExpenses + actualCost auf Budget-Task |
| 7 | Rechnungen | Abschlags-/Schlussrechnung mit Budget-Verknüpfung |
| 8 | Leistungsstand | Kumulative Abrechnungsmatrix pro Position und Rechnung |
Detaillierte Datenflüsse
1. Von Angebot zu Budget
Beteiligte Systeme: Angebot-Konstruktor (eigenständige App) --> Fortuna Softec Herzstück
| Schritt | Beschreibung | Technisch |
|---|---|---|
| Angebot erstellen | Konstruktor erstellt LV aus Excel, GAEB, Voice oder manuell | Externe App (fortuna-angebotstool) |
| Export | Blueprint JSON oder Excel-Export | Konstruktor-Export |
| Import ins Herzstück | POST /api/offer-import | Erstellt projectBudgetStructures + projectBudgetTrades + projectBudgetTasks |
| Gewerke | Automatisch aus LV-Struktur erzeugt | projectBudgetTrades mit Nachlass-, Baustrom-, Versicherungs- und Sonstige-Prozenten |
| Positionen | Jede LV-Zeile wird zu einem Budget-Task | projectBudgetTasks 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
| Schritt | Beschreibung | Technisch |
|---|---|---|
| Aufgabe erstellen | Aus Budget-Position "Aufgabe erstellen" | Erstellt projectTasks mit budgetTaskId-Verknüpfung |
| Worker zuweisen | Einsatzplanung-Kalender: Worker auf Zeitslot ziehen | Erstellt taskAssignments mit date, startTime, endTime |
| Einsatz veröffentlichen | Status planned -> published | Push-Notification an Worker via NotificationService |
| KI-Einsatzplaner | Experimentelles Feature: AI schlägt optimale Zuweisungen vor | EinsatzplanService 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)
| Schritt | Beschreibung |
|---|---|
| Veröffentlichte Einsätze | Erscheinen in Worker-App unter "Heute" (/worker/dashboard) |
| Anzeige | Projekt, Aufgabe, Zeitraum, Beschreibung (automatisch übersetzt) |
| Dokumentation starten | Worker tippt auf Einsatz --> Dokumentations-Wizard |
| Ohne Einsatz | Worker kann auch eigene Einträge erstellen ("Stundenlohn / Eigener Einsatz") |
Worker-App Seiten:
| Route | Funktion |
|---|---|
/worker/dashboard | Heutige Einsätze, Alerts, Pending-Status |
/worker/diary | Archiv aller eigenen Einträge |
/worker/diary/edit/[id] | Eintrag bearbeiten (nur vor Genehmigung) |
/worker/report | Stundenberichte |
/worker/acknowledge | Unterweisungsdokumente bestätigen |
/worker/more | Einstellungen, 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:
| Schritt | Name | Inhalt | Pflicht |
|---|---|---|---|
| 1 | Projekt | Projekt auswählen | Ja |
| 2 | Aufgabe | Aufgabe/Einsatz auswählen | Ja |
| 3 | Arbeitszeit | Start, Ende, Pause (mit reportedWorkSessions) | Ja |
| 4 | Bericht | Arbeitsbeschreibung (Text) | Ja |
| 5 | Extras | Material, Maschinen (QR-Scan), Fotos, Sprachnotizen | Nein |
| 6 | Unterschrift | Selbst-Signatur oder Externe Signatur | Ja |
| 7 | Absenden | Eintrag wird erstellt | Ja |
Eintrag enthält nach Absenden:
| Feld | Beschreibung | Quelle |
|---|---|---|
reportedWorkSessions | Arbeitszeitblöcke (Start, Ende, Pause) | Wizard Schritt 3 |
actualStartTime, actualEndTime, actualBreakMinutes | Aggregierte Zeiten | Berechnet aus Sessions |
images | Fotos (mit GPS-Daten) | Wizard Schritt 5 |
materials | Array: {name, quantity, unit, cost} | Wizard Schritt 5 |
machines | Array: {name, machineId, operatingHours, hourlyRate, qrCode} | Wizard Schritt 5 (QR-Scan) |
signatures | Array mit {signatureType, signerUserId, workerTimeAttestation} | Wizard Schritt 6 |
workContentLocked | true bei externer Signatur | Automatisch |
approvalStatus | "submitted" oder "approved" (bei Dual-Signatur sofort) | Status-Logik |
taskAssignmentId | Verknüpfung zum Einsatz | Falls von Einsatz gestartet |
budgetTaskId | Verknüpfung zur Budget-Position | Falls 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üfschritt | Details |
|---|---|
| Zeitangaben plausibel? | Start, Ende, Pause |
| Fotos aussagekräftig? | Visuelle Kontrolle |
| Material korrekt? | Mengen und Arten |
| Materialpreise zuweisen | Preis pro Einheit festlegen |
| Stundensatz festlegen | Fallback-Kette (s. unten) |
| Budget-Position zuordnen | Falls 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)
| Schritt | Aktion | Service |
|---|---|---|
| 1 | Diary-Entry auf approved setzen, isFinalized: true | Direkt in Transaktion |
| 2 | Alte diary-verknüpfte Expenses löschen (Idempotenz) | deleteDiaryLinkedExpenses() |
| 3 | Personalkosten-Expense erstellen | Direkt in Transaktion |
| 4 | Material-Expenses erstellen (pro Material-Position) | Direkt in Transaktion |
| 5 | Manager-ergänzte Material-Expenses erstellen | Direkt in Transaktion |
| 6 | Time-Entries auf approved setzen + budgetTaskId synchronisieren | setDiaryTimeEntriesStatus() |
| 7 | Aggregierten diary-Material-Expense verhindern (Anti-Doppelbuchung) | syncDiaryMaterialsExpense() |
| 8 | Budget-Kosten synchronisieren | syncBudgetFromDiaryEntry() |
Nach der Transaktion
| Schritt | Aktion | Service |
|---|---|---|
| 9 | Alle betroffenen Budget-Tasks neu berechnen | syncBudgetTask() pro Task |
| 10 | Verknüpften Einsatz auf completed setzen | taskAssignments Update |
| 11 | PDF generieren (§16 ArbZG) | generateAndStoreTimesheetPdf() |
| 12 | Worker benachrichtigen | NotificationService.create() |
| 13 | Fotos synchronisieren | syncDiaryEntryPhotos() (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)actualCostaufprojectBudgetTasks= Aggregat (Summe aller Kosten für diesen Task)- Diese sind Spiegel, nicht separate Quellen -- niemals addieren!
laborNetist ein Infofeld -- NICHT Teil vonactualNetim Herzstueck-Financials-Service
Ast 2 -- Compliance
| Prüfung | Gesetz | Details |
|---|---|---|
| Max. 10h/Tag | §3 ArbZG | Inkl. aller genehmigten Einträge desselben Tags |
| Min. Pausenzeiten | §4 ArbZG | 30 Min. nach 6h, 45 Min. nach 9h |
| Min. 11h Ruhezeit | §5 ArbZG | Zwischen Ende letzter Schicht und Beginn neuer |
| Max. 48h/Woche (Durchschnitt) | §3 ArbZG | 24-Wochen-Referenzzeitraum |
| Nachtarbeit-Compliance | §6 ArbZG | 23:00-06:00, max. 8h für Nachtarbeitnehmer |
| Mindestlohn-Validierung | MiLoG | Stundensatz >= 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_pdfStatus + Inngest Retry-Job
7. Von Herzstück zu Rechnungen
Beteiligte Services: Rechnungs-UI --> InvoiceBudgetSyncService
| Schritt | Beschreibung | Technisch |
|---|---|---|
| Positionen aus Budget | Budget-Positionen als Rechnungspositionen übernehmen | invoiceItems.budgetTaskId -> projectBudgetTasks.id |
| Rechnung erstellen | Entwurf mit Positionen, Texten, Rabatten | invoices Tabelle, Status draft |
| Finalisieren/Versenden | Automatische Rechnungsnummer (RE-YYYY-NNNN) | Status draft -> finalized -> sent |
| Budget-Sync (Versand) | invoicedPercent auf Budget-Tasks aktualisieren | InvoiceBudgetSyncService.syncTaskInvoicedPercent() |
| Budget-Sync (Zahlung) | paidPercent auf Budget-Tasks aktualisieren | InvoiceBudgetSyncService.syncTaskPaidPercent() |
| Trade-Aggregation | totalInvoicedNet und totalPaidNet auf Gewerk-Ebene | InvoiceBudgetSyncService.syncTradeInvoicedTotals() |
Rechnungstypen:
| Typ | DB-Wert | Beschreibung |
|---|---|---|
| Rechnung | standard | Standard-Rechnung |
| Abschlagsrechnung | partial | Teilrechnung nach Baufortschritt |
| Anzahlungsrechnung | advance | Vorauszahlung |
| Schlussrechnung | final | Abschlussrechnung (zieht alle Abschläge ab) |
| Gutschrift | credit | Teilweise 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
| Schritt | Beschreibung | Technisch |
|---|---|---|
| Finalisierte Rechnung | Jede versendete Rechnung erzeugt eine Spalte | AbrechnungColumn mit Label (AZ I, AZ II, SZ) |
| Matrix aufbauen | Pro Budget-Task x Rechnung: kumulativer Betrag | MatrixCell mit cumulativePercent und cumulativeAmount |
| Zusammenfassung | Hauptauftrag + Nachträge - Abzüge + MwSt. = Brutto | LeistungsstandSummary 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 (mittradeSummariesBreakdown)
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
| Kostenart | Quelle | Expense-Typ | costCategory | Fließt wohin |
|---|---|---|---|---|
| Personal | Arbeitszeit x Stundensatz (Bautagebuch) | labor | labor_internal | projectExpenses -> actualCost -> Soll-Ist |
| Material (Worker) | Materialposition (Bautagebuch) | material | material_delivery | projectExpenses -> actualCost -> Soll-Ist |
| Material (Manager) | Manager-ergänztes Material | material | material_delivery | projectExpenses -> actualCost -> Soll-Ist |
| Maschinen | Betriebsstunden x Maschinensatz (Bautagebuch + Katalog) | Auto-Expense | equipment | projectExpenses -> actualCost -> Soll-Ist |
| Nachunternehmer | Manuelle Eingabe | subcontractor | subcontractor | projectExpenses -> actualCost -> Soll-Ist |
| Sonstiges | Manuelle Eingabe | other | misc | projectExpenses -> actualCost -> Soll-Ist |
| Materiallieferung | Manuelle Eingabe | material | material_delivery | projectExpenses -> actualCost -> Soll-Ist |
| Materiallager | Manuelle Eingabe | material | material_stock | projectExpenses -> actualCost -> Soll-Ist |
Auto-generierte Expenses (BudgetProgressService):
| autoSource | Beschreibung | Wird gefiltert im Herzstueck-Financials |
|---|---|---|
time_entries | Spiegel der Zeitkosten aus Time-Entries | Ja (sonst Doppelzählung mit laborNet) |
diary_materials | Spiegel der Materialkosten aus Diary | Ja (sonst Doppelzählung mit actualCost) |
diary_machines | Spiegel der Maschinenkosten aus Diary | Ja (sonst Doppelzählung mit equipmentNet) |
Stundensatz-Hierarchie
Die Fallback-Kette für den Stundensatz wird bei der Genehmigung aufgelöst:
| Priorität | Quelle | Bedingung | Code-Referenz |
|---|---|---|---|
| 1 (höchste) | Manuell vom Manager | Bei Genehmigung eingegeben | hourlyRate aus Request-Body |
| 2 | Vorheriger Stundensatz | Korrektur-Re-Approval | entry.hourlyRate (gespeichert) |
| 3 | Budget-Task unitPrice | Wenn Unit ein Stunden-Typ ist (h, std, stunde, hour etc.) | projectBudgetTasks.unitPrice |
| 4 (niedrigste) | Worker-Standardsatz | Aus Benutzerprofil | users.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-SummeplannedNetAdjusted-- überschreibt die berechnete Netto-AdjustedplannedGross-- überschreibt die Brutto-Berechnung
Diese stammen aus dem GAEB/Excel-Import und dienen als "Controlling-Parität" mit externen Systemen.
Immutabilitäts-Regeln
| Was | Regel | Ausnahme |
|---|---|---|
| Arbeitszeit (Start/Ende/Pause) | IMMUTABLE nach Unterschrift | Nur über Korrektur-Workflow |
| Arbeitsinhalt (Text, Fotos, Material-Mengen) | IMMUTABLE nach externer Signatur | Manager kann bis Genehmigung ergänzen (bei Selbst-Signatur) |
| Materialpreise | EDITIERBAR auch nach Genehmigung | Pricing-Update |
| Stundensatz | EDITIERBAR auch nach Genehmigung | Pricing-Update |
| Budget-Position-Zuordnung | EDITIERBAR auch nach Genehmigung | Re-Assignment |
| Genehmigte Inhalte (Fotos, Materialien) | IMMUTABLE nach Genehmigung | Keine 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
| Rolle | Globale Rechte | Projekt-Rechte |
|---|---|---|
| Admin | manage_company, manage_users, manage_all_projects | ALLE (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
| Service | Datei | Verantwortung |
|---|---|---|
| BudgetProgressService | lib/services/budget-progress-service.ts | Kostenberechnung: Diary -> Expenses -> actualCost -> Kaskade |
| HerzstueckFinancialsService | lib/services/herzstueck-financials-service.ts | SOLL/IST pro Gewerk: plannedNet, actualNet, Varianz |
| InvoiceBudgetSyncService | lib/services/invoice-budget-sync-service.ts | Rechnung -> invoicedPercent/paidPercent auf Tasks + Trades |
| LeistungsstandService | lib/services/leistungsstand-service.ts | Kumulative Abrechnungsmatrix pro Position/Rechnung |
| DiaryApprovalHooks | lib/services/diary-approval-hooks.ts | Post-Approval: PDF, Budget-Sync, Time-Entry-Status (mit Retry) |
| DiaryExpenseSync | lib/services/diary-expense-sync.ts | Material-Expenses aus Diary synchronisieren |
| DiaryApprovalAccounting | lib/services/diary-approval-accounting.ts | Time-Entry-Status setzen, alte Expenses löschen |
| TimeComplianceService | lib/services/time-compliance.ts | ArbZG-Prüfung (§3/§4/§5/§6/§9) |
| EmergencyService | lib/services/emergency-service.ts | Notfall-Orchestrierung |
| EinsatzplanService | lib/services/einsatzplan-service.ts | KI-Einsatzplanung (Kontext, Vorschlag, Anwendung) |
| NotificationService | lib/services/notification-service.ts | Push/In-App-Benachrichtigungen mit Deduplizierung |
| TimesheetPdfService | lib/services/timesheet-pdf-service.ts | §16 ArbZG PDF-Generierung |
| PhotoFilesSyncService | lib/services/photo-files-sync-service.ts | Fotos in Hetzner S3 synchronisieren |
| BudgetAggregationService | lib/services/budget-aggregation-service.ts | Task -> Trade -> Structure Kaskaden-Aggregation |
Schlüssel-Regeln (Zusammenfassung)
Kosten
actualCostundprojectExpensessind SPIEGEL -- niemals addierenmanualActualCostüberschreibtactualCostüberall:COALESCE(manualActualCost, actualCost, 0)laborNetist ein Infofeld -- NICHT Teil vonactualNet- 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: falsewerden in Summen einbezogen - Nachträge nur wenn
changeOrderStatus: 'approved' manualTotalPriceüberschreibttotalPrice,manualQuantityüberschreibtquantity
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)totalInvoicedNetauf 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
| Tabelle | Modul | Beschreibung |
|---|---|---|
projectBudgetStructures | Herzstück | Budget-Versionen (Original, Nachträge) |
projectBudgetTrades | Herzstück | Gewerke mit Abzügen und Finanzkennzahlen |
projectBudgetTasks | Herzstück | Budget-Positionen (SOLL + IST) |
projectBudgetVariances | Herzstück | Abweichungs-Records bei Kostenüberschreitung |
projectExpenses | Herzstück | Einzelne Kostenbuchungen (Material, Personal, Maschinen) |
diaryEntries | Bautagebuch | Arbeitsdokumentation (Zeiten, Fotos, Material, Unterschriften) |
timeEntries | Zeiterfassung | Arbeitszeiteinträge (approved/auto_approved/pending) |
invoices | Rechnungen | Ausgangs- und Eingangsrechnungen |
invoiceItems | Rechnungen | Rechnungspositionen mit Budget-Verknüpfung |
taskAssignments | Einsatzplanung | Worker-Zuweisungen zu Aufgaben |
projectTasks | Aufgaben | Projekt-Aufgaben (inkl. Notfall-Einsätze) |
machines | Maschinenpark | Maschinen-Katalog mit QR-Codes und Stundensätzen |
notifications | Kommunikation | In-App-Benachrichtigungen |
channels / channelMembers | Kommunikation | Projekt-Chat-Kanäle |
projects / projectMembers | Projektverwaltung | Projekte und Mitgliedschaften |
users | Benutzerverwaltung | Benutzer mit Rollen, Stundensätzen, Gewerk |
clients | Kundenverwaltung | Kunden mit Adressen |
Verwandte Dokumentation
| Seite | Beschreibung |
|---|---|
| Einführung | Was ist Fortuna Softec? |
| Schnellstart | Erste Schritte |
| Herzstück - Übersicht | Budget-Management im Detail |
| GAEB-Import | Budget importieren |
| Soll-Ist-Vergleich | Finanz-Cockpit |
| Leistungsstand | Abrechnungsmatrix |
| Rechnungen | Rechnungsverwaltung |
| Berichte | Finanzberichte |
| Einsatzplanung | Kalender und Zuweisungen |
| Team-Zuweisung | Worker zuweisen |
| Übersetzung | Mehrsprachige Aufgaben |
| Worker-App | Mobile App für Mitarbeiter |
| Dokumentations-Wizard | 7-Schritte-Erfassung |
| Offline-Modus | Arbeiten ohne Internet |
| Foto-Zeitstrahl | Foto-Dokumentation |
| Bautagebuch | Management-Sicht auf Einträge |
| Genehmigungszentrale | Prüf- und Freigabeprozess |
| Y-Split | Zwei Genehmigungspfade |
| Signaturen | Selbst- vs. Externe Signatur |
| Nachträge | Change Orders |
| Rollen - Übersicht | Berechtigungssystem |
| Maschinenpark | Geräte und QR-Codes |
| Notfall-System | Notfall-Einsätze |
| Service-Portal | Externe QR-Zugänge |
| IFC/BIM-Viewer | 3D-Modelle |
| KI-Assistent | AI-Funktionen |
| Dashboard | Übersichts-Dashboard |
| Kommunikation | Projekt-Chat |