Files
HiveGrid-Wireframes/hivegrid-cloud/.planning/entity-model.dbml
T

636 lines
18 KiB
Plaintext

// =============================================================================
// HiveGrid 2.0 — ENTITÄTENMODELL (Projekt-zentrisch, AD-20) — ✅ APPROVED & LOCKED 2026-06-15
// · Update 2026-06-16: pipeline_field_visibility NEU (Field-Level-Rechte); projekt_externes_feld → [v2] (KWP komplett v2)
// =============================================================================
// ⚠ entity-model.svg ist NICHT automatisch aktualisiert (dbdiagram.io-Export) — nach diesem Update neu generieren:
// Import → https://dbdiagram.io ("Import → DBML") → Export SVG → entity-model.svg ersetzen.
// Approved-Quelle: HiveGrid_CRM_Datenlogik_UX_Entscheidungen.md · Voll/Prosa: ENTITY-MODEL.md · Laienfassung: ENTITY-MODELL-ERKLAERUNG.pdf
//
// Deckt die KERN-Domäne ab:
// Lead → Projekt (ein Kern) + Pipeline (konfigurierbare Views) + Kontakt + Sub-Areas.
// "Deal" und "Auftrag" sind NUR Pipeline-Typen — KEINE eigenen Entitäten.
//
// STUBS: unveränderte Cross-Cutting-/Modul-Tabellen (Auth, Files, Tasks, Audit, Automation,
// KI, Email, Chat, Kalender, Booking, Forms, HR, Billing, Admin) sind nur als Stub (Schlüssel-
// spalten) referenziert — ihr volles Modell steht in ENTITY-MODEL.md.
//
// Konventionen:
// AD-1 RLS auf allem mit tenant_id.
// AD-2 typisierte Tabellen + CustomFields/FieldConfig (KEIN generischer Entity-Builder).
// Polymorphe Refs (entity_type + entity_id) haben bewusst KEINEN DB-FK.
// [v1] = Launch-Scope · [v2] = nach Launch.
// =============================================================================
// ============================ ENUMS ============================
Enum kontakt_typ {
privatperson
organisation
}
Enum kontakt_status {
kontakt
kunde
bestandskunde
}
// optionales Rechtsform-/Kategorie-Merkmal, NICHT Grundtyp
Enum org_merkmal {
gmbh
kg
oeffentliche_einrichtung
sonstige
}
Enum kontakt_art {
hauptkontakt
sekundaerkontakt
mitarbeiter
sonstige
}
Enum projekt_kontakt_rolle {
auftraggeber
bauherr
rechnungsempfaenger
ansprechpartner
sonstige
}
Enum lead_quelle {
formular
website
social_media
leadliste
csv
manuell
email
anruf
}
Enum lead_status {
new
qualified
converted
lost
spam
}
// "Weiterverarbeitungsfreigabe"
Enum weiterverarbeitung_freigabe {
ja
nein
unklar
}
// typ_hint: nur Label/Default-Verhalten, kein Hard-Type
Enum pipeline_typ_hint {
sales
auftrag
custom
}
// Reporting (Won/Lost); KEIN globaler Projektstatus
Enum stage_kind {
offen
gewonnen
verloren
}
Enum principal_type {
rolle
user
gruppe
}
Enum projekt_prioritaet {
niedrig
normal
hoch
kritisch
}
Enum projekt_mitglied_rolle {
projektleiter
monteur
zustaendig
beobachter
}
Enum field_entity_type {
projekt
kontakt
lead
rechnung
}
Enum custom_field_datentyp {
text
textarea
zahl
waehrung
datum
datum_range
select
multiselect
boolean
email
telefon
datei
}
Enum rechnung_status {
entwurf
gestellt
bezahlt
storniert
}
Enum checkliste_typ {
generisch
maengel
abnahme_doku
}
Enum checklist_item_status {
offen
erledigt
nicht_relevant
}
Enum maengel_klasse {
kritisch
klein
kosmetisch
}
Enum abnahme_signer_rolle {
kunde
monteur
}
Enum externes_system {
kwp
sonstige
}
// ============================ STUBS (anderswo voll modelliert) ============================
Table tenant {
id uuid [pk]
Note: '[v1] Mandant. RLS-Anker für alles unten.'
}
Table app_user {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
Note: '[v1] Mandanten-Nutzer (Mitarbeiter/Vertriebler/Monteur). = User in ENTITY-MODEL.md.'
}
Table file {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
Note: '[v1] Datei (Storage). Volles Modell: M23/Files.'
}
Table tag {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
name varchar
farbe varchar
}
Table hive_number_seq {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
sequence_key varchar [note: 'z.B. projekt:auftrag, rechnung — fortlaufende Nummern pro Tenant']
current bigint
}
// ============================ ADRESSE (wiederverwendbar) ============================
Table adresse {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
strasse varchar
hausnummer varchar
plz varchar
ort varchar
land varchar [note: 'v1 DE-Default']
Note: '[v1] Eigene Tabelle, damit Projekt-/Rechnungs-/Bauher-Adresse getrennt referenzierbar sind.'
}
// ============================ KONTAKT (Oberbegriff Person/Organisation) ============================
Table kontakt {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
kontakt_typ kontakt_typ [note: 'privatperson | organisation — EIN Tabellen-Modell mit Diskriminator']
kontakt_status kontakt_status [note: 'kontakt | kunde | bestandskunde']
// --- Person (wenn typ=privatperson) ---
vorname varchar
nachname varchar
// --- Organisation (wenn typ=organisation) ---
org_name varchar
org_merkmal org_merkmal [note: 'optionales Rechtsform-/Kategorie-Merkmal, kein Grundtyp']
// --- gemeinsam ---
primaer_email varchar
primaer_telefon varchar
adresse_id uuid [ref: > adresse.id]
zustaendiger_user_id uuid [ref: > app_user.id]
archiviert_at timestamp
Note: '''[v1] Kontakt = Oberbegriff (David-Modell). Person- und Org-Felder im selben Table,
App erzwingt Pflichtfelder je kontakt_typ. Mehrfach-Emails/-Telefone via kontakt_email/_telefon.'''
}
Table kontakt_email {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
kontakt_id uuid [ref: > kontakt.id]
email varchar
primaer boolean
}
Table kontakt_telefon {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
kontakt_id uuid [ref: > kontakt.id]
telefon varchar
primaer boolean
}
Table kontakt_relation {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
von_kontakt_id uuid [ref: > kontakt.id, note: 'z.B. Organisation oder Privatperson']
zu_kontakt_id uuid [ref: > kontakt.id, note: 'der verknüpfte Kontakt']
rolle kontakt_art [note: 'hauptkontakt | sekundaerkontakt — "Kontaktart" als ROLLE der Relation']
kontext varchar
Note: '''[v1] Haupt-/Sekundärkontakt-Verknüpfung (self-ref M:N).
Organisation hat >=1 Hauptkontakt (mehrere erlaubt) + Sekundärkontakte;
Privatperson kann selbst Hauptkontakt sein + Sekundärkontakte.'''
}
// ============================ LEAD / LEADINPUT (Vorstufe) ============================
Table lead {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
quelle lead_quelle
quelle_detail varchar [note: 'Formular-Name/IP bzw. Inbound-Mail/Anrufnummer']
status lead_status
weiterverarbeitung_freigabe weiterverarbeitung_freigabe [note: 'ja | nein | unklar — Grenze Lead↔Projekt']
// Roh-/Vorab-Daten (Felder dürfen leer sein, früh Projektfeldern zuordenbar):
vorname varchar
nachname varchar
email varchar
telefon varchar
rohdaten jsonb [note: 'Quell-Payload + vorab gemappte Projekt-Feldwerte']
spam_score numeric
zugewiesen_user_id uuid [ref: > app_user.id]
konvertiert_projekt_id uuid [ref: > projekt.id, note: 'gesetzt bei Weiterverarbeitung → Projekt (E4)']
verknuepfter_kontakt_id uuid [ref: > kontakt.id, note: 'Kontakt separat verknüpft (nicht promotet)']
eingegangen_at timestamp
archiviert_at timestamp
Note: '''[v1] LeadInput = eigenes Tool/Vorstufe. Bei Freigabe → Projekt anlegen, gemappte Daten
vererben, konvertiert_projekt_id setzen. KEIN Lead→Contact-Promotion-Modal mehr (E4).'''
}
// ============================ PIPELINE (konfigurierbare Views) ============================
Table pipeline {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
name varchar [note: '"Deal", "Auftrag", … frei benennbar']
beschreibung text
typ_hint pipeline_typ_hint [note: 'sales | auftrag | custom — steuert Default-Features (Forecast vs. Plantafel)']
reihenfolge int
archiviert boolean
Note: '[v1] Pipeline = View auf Projekte. Admin legt an/benennt/konfiguriert.'
}
Table pipeline_stage {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
pipeline_id uuid [ref: > pipeline.id]
name varchar
reihenfolge int
stage_kind stage_kind [note: 'offen | gewonnen | verloren — nur Reporting, kein globaler Status']
win_wahrscheinlichkeit int
farbe varchar
Note: '[v1] Spalten/Schritte der Pipeline = die Statuswerte des Projekts in dieser Pipeline.'
}
Table pipeline_access {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
pipeline_id uuid [ref: > pipeline.id]
principal_type principal_type [note: 'rolle | user | gruppe']
principal_id uuid [note: 'polymorph: Rolle/User/Gruppe-ID']
kann_sehen boolean
kann_bearbeiten boolean
Note: '[v1] "Wer darf die Pipeline sehen/bearbeiten" (Admin entscheidet).'
}
Table pipeline_field_config {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
pipeline_id uuid [ref: > pipeline.id]
stage_id uuid [ref: > pipeline_stage.id, note: 'NULL = pipeline-weit; gesetzt = Pflicht ab diesem Status']
field_key varchar [note: 'System-Feldschlüssel (z.B. "auftragsvolumen") ODER "custom:<definition_id>"']
sichtbar boolean
pflicht boolean
reihenfolge int
Note: '''[v1] DER Konfig-Layer: pro Pipeline (+Status) welche Felder sichtbar/pflichtig.
field_key überbrückt typisierte Kern-Felder UND Custom-Fields. Nicht sichtbare Felder
bleiben am Projekt erhalten (nur ausgeblendet).'''
}
Table pipeline_field_visibility {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
field_config_id uuid [ref: > pipeline_field_config.id]
principal_type varchar [note: 'rolle | gruppe | user']
principal_id uuid
kann_sehen boolean
kann_bearbeiten boolean
Note: '''[v1] Field-Level-Rechte (Entscheidung 16.06.): pro Feld-Config je Rolle/Gruppe/User
sehen/bearbeiten — z.B. Auftragsvolumen nur fuer Vertrieb. Ohne Eintrag = fuer alle sichtbar.'''
}
// ============================ PROJEKT (zentraler Kern) ============================
Table projekt {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_nummer varchar [note: 'aus hive_number_seq']
projektname varchar
pipeline_id uuid [ref: > pipeline.id, note: 'genau EINE Pipeline (AD-20)']
stage_id uuid [ref: > pipeline_stage.id, note: '= Status (pipeline-intern)']
projekttyp varchar
quelle varchar
promotion varchar
prioritaet projekt_prioritaet
start_datum date
deadline date
auftragsvolumen numeric [note: '"Auftrag"-Welt']
potenzieller_auftragswert numeric [note: '"Deal"-Welt']
zustaendige_person_id uuid [ref: > app_user.id, note: 'Vertriebler/zuständig']
projektleiter_id uuid [ref: > app_user.id]
projekt_adresse_id uuid [ref: > adresse.id]
rechnungsadresse_id uuid [ref: > adresse.id, note: 'separat, optional']
bauheradresse_id uuid [ref: > adresse.id, note: 'separat, optional']
kommunikation_email varchar [note: 'projektbezogene Kommunikationsdaten']
kommunikation_telefon varchar
kommunikation_fax varchar
konvertiert_aus_lead_id uuid [ref: > lead.id]
archiviert_at timestamp
Note: '''[v1] EIN Projekt-Kern (Deal+Auftrag verschmolzen). Typisierte Superset-Spalten;
branchen-/pipelinespezifische Zusatzfelder via custom_field_value. Monteure (mehrere)
via projekt_mitglied. KWP-Felder via projekt_externes_feld.'''
}
Table projekt_mitglied {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
user_id uuid [ref: > app_user.id]
rolle projekt_mitglied_rolle [note: 'monteur (mehrere) | beobachter | …']
Note: '[v1] Mehrfach-Zuordnung (v.a. Monteure). projektleiter/zustaendig sind zusätzlich Direkt-FKs am Projekt.'
}
Table projekt_kontakt {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
kontakt_id uuid [ref: > kontakt.id]
rolle projekt_kontakt_rolle [note: 'auftraggeber | bauherr | rechnungsempfaenger | ansprechpartner']
primaer boolean
Note: '[v1] Verknüpfung Projekt↔Kontakt(e) mit Rolle. Inline-Edit am Kontakt schreibt zurück.'
}
// ============================ CUSTOM FIELDS (typisiert-erweiterbar, AD-2) ============================
Table custom_field_definition {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
entity_type field_entity_type
key varchar
label varchar
datentyp custom_field_datentyp
optionen jsonb [note: 'Werte für select/multiselect']
archiviert boolean
Note: '[v1] Eigen-Felder pro Entity-Typ. SHK-Branchen-Felder als Seed-Template (AD-12).'
}
Table custom_field_value {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
definition_id uuid [ref: > custom_field_definition.id]
entity_type field_entity_type
entity_id uuid [note: 'polymorph (projekt/kontakt/…), KEIN FK']
wert jsonb
Note: '[v1] Wert je (Definition, Entität). Von pipeline_field_config via "custom:<id>" referenzierbar.'
}
// ============================ SUB-AREAS am Projekt ============================
Table termin {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
kontakt_id uuid [ref: > kontakt.id]
titel varchar
start_at timestamp
ende_at timestamp
ort varchar
Note: '[v1] Projektbezogene Termine (Kalender-Sync M25 dockt an).'
}
Table rechnung {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
rechnungsnummer varchar [note: 'aus hive_number_seq']
betrag numeric
status rechnung_status
datum date
faellig_am date
datei_id uuid [ref: > file.id, note: 'Rechnungs-PDF']
quelle externes_system [note: 'manuell | kwp (v2-Sync)']
notiz text
Note: '[v1 NEU — E3] Einfaches Rechnungs-Sub-Entity am Projekt (Liste/Anlegen/Verknüpfen).'
}
Table checkliste {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
name varchar [note: 'z.B. "Mängel", "Abnahme-Doku"']
typ checkliste_typ [note: 'generisch | maengel | abnahme_doku']
Note: '[v1] Generisches Checklisten-Konstrukt. "Mängel" = Checkliste mit typ=maengel (E2).'
}
Table checklist_item {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
checkliste_id uuid [ref: > checkliste.id]
text varchar
status checklist_item_status
maengel_klasse maengel_klasse [note: 'nur bei Mängel-Checkliste: kritisch | klein | kosmetisch']
foto_file_id uuid [ref: > file.id, note: 'Foto je Mangel']
reihenfolge int
}
Table abnahme_protokoll {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
maengel_checkliste_id uuid [ref: > checkliste.id, note: 'verknüpfte Mängel-Checkliste']
datum date
anwesende text
druckprobe_bestanden boolean
druckprobe_wert varchar
funktionspruefung text
zaehlerstaende jsonb
pdf_file_id uuid [ref: > file.id]
gesperrt_at timestamp [note: 'nach Signatur immutable']
Note: '''[v1 — E2] Abnahme als eigene immutable Entität (rechtlich bindend, 6 J., offline-fähig).
Bindet Mängel-Checkliste ein; "Mängel = Checkliste" bleibt erfüllt.'''
}
Table abnahme_signatur {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
abnahme_id uuid [ref: > abnahme_protokoll.id]
signer_name varchar
signer_rolle abnahme_signer_rolle [note: 'kunde | monteur']
signatur_file_id uuid [ref: > file.id, note: 'Touch-Unterschrift als Bild']
signiert_at timestamp
}
Table projekt_externes_feld {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
projekt_id uuid [ref: > projekt.id]
system externes_system [note: 'kwp']
feld_key varchar [note: 'KW-Projektnummer | Abteilungsnummer | Sachbearbeiter | Auftragsstatus | …']
wert varchar
Note: '''[v2] KWP/API-Felder — Entscheidung 16.06.: komplett v2, in v1 NICHT vorhanden
(kein Feld-Halter). KWP-Sync ab v2 (API-Zugang ~19.07.); Konflikt-Regel "KWP gewinnt".'''
}
// ============================ CROSS-CUTTING (polymorph, Stub) ============================
Table aufgabe {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
titel varchar
status varchar
faellig_am date
assignee_user_id uuid [ref: > app_user.id]
linked_entity_type varchar [note: 'projekt | kontakt | lead | … (polymorph, KEIN FK)']
linked_entity_id uuid
quelle varchar [note: 'manuell | chat | form | automation']
Note: '[v1] Task (M26) — wiederverwendbar, polymorph an jede Entität.'
}
Table aktivitaet {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
typ varchar
actor_user_id uuid [ref: > app_user.id]
entity_type varchar [note: 'projekt | kontakt | lead | … (polymorph)']
entity_id uuid
daten jsonb
created_at timestamp
Note: '[v1] Activity-Stream (append-only, Event-Sourcing). Speist Inline-Edit-Verlauf.'
}
Table file_entity_link {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
file_id uuid [ref: > file.id]
entity_type varchar [note: 'projekt | kontakt | rechnung | abnahme | … (polymorph)']
entity_id uuid
rolle varchar
Note: '[v1] Dokumente an jede Entität hängen (M23).'
}
Table entity_tag {
id uuid [pk]
tenant_id uuid [ref: > tenant.id]
tag_id uuid [ref: > tag.id]
entity_type varchar [note: 'projekt | kontakt | lead | … (polymorph)']
entity_id uuid
}
// ============================ GRUPPIERUNG (geordnete Darstellung) ============================
// Cluster nach Achse — rendert als Boxen (dbml-renderer + dbdiagram.io).
TableGroup Kern_Kette {
lead
projekt
kontakt
}
TableGroup Pipeline_Views_und_Konfig {
pipeline
pipeline_stage
pipeline_access
pipeline_field_config
}
TableGroup Kontakt_Detail {
kontakt_email
kontakt_telefon
kontakt_relation
projekt_kontakt
}
TableGroup Projekt_Beteiligte_und_Adresse {
projekt_mitglied
adresse
}
TableGroup Sub_Areas_am_Projekt {
termin
rechnung
checkliste
checklist_item
abnahme_protokoll
abnahme_signatur
projekt_externes_feld
}
TableGroup Custom_Fields {
custom_field_definition
custom_field_value
}
TableGroup Cross_Cutting {
aufgabe
aktivitaet
file_entity_link
entity_tag
}
TableGroup Stubs {
tenant
app_user
file
tag
hive_number_seq
}