Files
ICON-CRM/docs/02-datenmodell.md
T

8.0 KiB
Raw Blame History

02 Datenmodell

Eine Datenbank, icon_CRM, alle Tabellen mit dem Präfix icon_. Das Schema steht als idempotentes SQL in server/src/schema.sql und wird beim Start des Backends angewandt; spätere Feldänderungen liegen als Migrationen in server/src/db.js.

Überblick

erDiagram
    icon_users ||--o{ icon_companies : "erstellt / zustaendig"
    icon_users ||--o{ icon_contacts : "erstellt / zustaendig"
    icon_users ||--o{ icon_history : "hat getan"
    icon_companies ||--o{ icon_contacts : "beschaeftigt"
    icon_companies ||--o{ icon_employments : "Stationen"
    icon_contacts ||--o{ icon_employments : "Werdegang"
    icon_contacts ||--o{ icon_interviews : "Gespraeche"
    icon_contacts ||--o{ icon_connections : "kennt"
    icon_companies ||--o{ icon_tenders : "Kunde"
    icon_tenders ||--o{ icon_tender_contacts : "zugewiesen"
    icon_contacts ||--o{ icon_tender_contacts : "zugewiesen"
    icon_companies ||--o{ icon_projects : "Kunde"
    icon_contacts ||--o{ icon_projects : "Experte"
    icon_companies ||--o{ icon_leads : "Ziel"
    icon_contacts ||--o{ icon_leads : "Ansprechpartner"
    icon_leads ||--o{ icon_lead_todos : "Checkliste"
    icon_contacts ||--o{ icon_cv_imports : "wurde daraus"

icon_mentions, icon_options und icon_settings hängen an keiner Fremdschlüsselbeziehung und stehen deshalb nicht im Bild.

Die Tabellen im Einzelnen

icon_users

Feld Typ Bedeutung
username, email VARCHAR, je eindeutig Anmeldung geht mit beidem
name VARCHAR Anzeigename
role ENUM admin,user siehe 01 Architektur
password_hash VARCHAR(255) bcrypt; Altbestand SHA-256, wird beim Login umgeschrieben
color VARCHAR(9) persönliche Farbe für Avatar und Historie
avatar MEDIUMTEXT Foto als Data-URL
theme, theme_mode VARCHAR / ENUM Design und hell/dunkel, gilt geräteübergreifend
is_active TINYINT deaktiviert = sofort ausgesperrt

icon_companies

name, group_name (Konzern), industry (aus den Stammdaten), employees, website, address, logo (Data-URL), rating (0–5 Sterne), is_customer, customer_state (aktiv/alumni), freelancers (unbekannt/ja/nein), notes, created_by, owner_id.

Ein Kunde ist kein eigener Datentyp, sondern ein Unternehmen mit is_customer = 1. Der Umschalter in der Oberfläche filtert nur darauf – es gibt keine zweite Liste, die auseinanderlaufen könnte.

icon_contacts

Feld Bedeutung
first_name, last_name, position Person
email, phone, linkedin, avatar Kontaktdaten und Foto
company_id aktueller Arbeitgeber
kind Art: Experte, Ansprechpartner, Lead – seit einer Migration VARCHAR statt ENUM, weil die Liste in den Stammdaten wächst
status Interessiert, Interview, Gebucht, Inaktiv
lead_source woher der Kontakt kam
contact_state unbekannt / persönlich / kein Kontakt
freelancers arbeitet freiberuflich
bio Notiz, darf @-Erwähnungen enthalten
tags Schlagworte, kommagetrennt
rate_onsite, rate_remote Stundensätze in Euro, NULL = nicht hinterlegt
cv_url, cv_file Link bzw. hochgelegter Lebenslauf

Experten und Ansprechpartner liegen in einer Tabelle. Ein Ansprechpartner, der die Firma wechselt und plötzlich als Experte interessant wird, ist derselbe Mensch und behält seine Historie.

rate_onsite/rate_remote stammen aus hourlyRateOnsite/hourlyRateRemote des alten CRM und sind Stundensätze. Die Oberfläche schreibt die Einheit überall mit (125,00 €/h), und die erweiterte Suche weiß es ebenfalls.

icon_employments

Werdegang: contact_id, company_id, position, started_on, ended_on, is_current. Daraus speist sich der Zweig Verlassen im Stammbaum eines Unternehmens. Ein Arbeitgeberwechsel schreibt hier automatisch fort.

icon_interviews

contact_id oder company_id, user_id, held_on, notes – wann wurde mit wem gesprochen.

icon_history

Jede Tätigkeit: entity_type (contact, company, tender, project, lead, user, system), entity_id, entity_label, action, text, changes (JSON mit Vorher/Nachher je Feld), user_id, created_at.

Geschrieben wird ausschließlich über logHistory() in server/src/history.js – nicht von den Routen selbst. Dadurch gilt an einer Stelle, was überall gelten soll: in jeder Historie steht, welcher Benutzer was geändert hat.

icon_mentions

Index der @-Erwähnungen: source_type/source_id → target_type/target_id, eindeutig je Paar. Ohne diesen Index müsste eine Karte jeden Freitext der Datenbank durchsuchen, um zu zeigen, wo sie genannt wurde.

icon_connections

Wer kennt wen: contact_a, contact_b, kind, strength. Das Paar wird aufsteigend gespeichert, ein Unique-Key verhindert, dass dieselbe Verbindung zweimal entsteht.

icon_tenders, icon_tender_contacts

Feld Bedeutung
title, source, link Ausschreibung und Herkunft
company_id Kunde
status Aktiv, Inaktiv, Ausgelaufen, Archiviert
stage Phase: Ausschreibung → Kontakte sichten → Interview → Kunden Interview → Vertragsgestaltung → Abschluss → Geschlossen
online erscheint auf der ICON Website
notes der veröffentlichte Ausschreibungstext, als HTML
details im gesamten Bestand leer, ungenutzt
deadline Frist

Die wichtigste Zeile dieses Kapitels: der Text der Website steht in notes, nicht in details. Wer den Editor auf details legt, zeigt ein leeres Feld und schreibt am veröffentlichten Text vorbei.

icon_tender_contacts verbindet Ausschreibung und Kontakt (role = Rolle im Projekt).

icon_projects

title, company_id, contact_id, started_on, ended_on, status (Aktiv / Nicht aktiv / Abgeschlossen), rate, notes (HTML). Im Menü heißt die Ansicht Laufende Projekte.

Im übernommenen Bestand steht im Kopf von notes ein Textblock aus dem alten CRM (KUNDE:, KONTAKT:, EXPERTE:, START:, ENDE:), darunter der übernommene Ausschreibungstext als HTML. src/lib/richText.ts macht daraus im Editor Absätze plus HTML – deshalb der Sonderfall „Text, dann HTML".

icon_leads, icon_lead_todos

title, company_id, contact_id, stage (acht Phasen, erste = Fuer Spaeter, in der Oberfläche Backlog), contact_state, archived, position (Reihenfolge in der Spalte), notes, remind_at, reminder_days, last_contact_at, owner_id.

icon_lead_todos: lead_id, text, done, due_on, position – die Checkliste, deren Fortschritt auf der Kachel als Prozentwert steht.

icon_cv_imports

filename, original_name, mime, size_bytes, status (eingang → screening → auswahl, oder verworfen), extracted (das JSON der KI), error, contact_id (nach der Übernahme), uploaded_by.

icon_options

Die gepflegten Auswahllisten: field (industry, contact_kind), value, position. Umbenennen zieht den Bestand mit; Entfernen geht nur, solange ein Wert nirgends benutzt wird.

icon_settings

Schlüssel/Wert, unter anderem für Migrationsmerker – damit eine einmalige Umstellung nicht bei jedem Start erneut läuft.

Migration aus Supabase

server/src/migrate.js übernimmt den Altbestand einmalig und wiederholbar (Unternehmen und Kontakte werden über ihren Namen wiedererkannt):

alt neu
icon_customers icon_companies mit is_customer = 1
icon_contacts icon_contacts, kind = 'Ansprechpartner'
icon_experts icon_contacts, kind = 'Experte'
hourlyRateOnsite, hourlyRateRemote rate_onsite, rate_remote
Status Verfügbar Interessiert
kanbanStatus: Experten sichten Phase Kontakte sichten
cd /var/www/icon_CRM/server
SUPABASE_URL=https://icondb.perinco.de SUPABASE_KEY=<anon-key> node src/migrate.js          # Probelauf
SUPABASE_URL=https://icondb.perinco.de SUPABASE_KEY=<anon-key> node src/migrate.js --write  # schreibt