- Suche ohne 21-Tage-Grenze (BA ohne veroeffentlichtseit, Adzuna ohne max_days_old); Einstellung Höchstalter entfällt - Dashboard: Filter Anzeigenalter (bis/älter als 21 Tage), Sortierung älteste Anzeige, Filter nur Alarm, Zeitraum heißt jetzt Gefunden - Suchagent: Schlagwörter mit Alarm; Treffer mit alarm = 1 melden sich über den AlarmWatcher (App-weit, jede Minute) mit Ton und Pop-up, Quittieren gilt für alle Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
544 lines
27 KiB
SQL
544 lines
27 KiB
SQL
-- ============================================================================
|
|
-- ICON CRM - Datenbankschema (MariaDB, Datenbank icon_CRM)
|
|
--
|
|
-- Idempotent: die Datei laeuft bei jedem Backend-Start durch. Jedes Statement
|
|
-- muss deshalb ein IF NOT EXISTS tragen oder sonst wiederholbar sein.
|
|
-- Spaltenaenderungen an bestehenden Tabellen gehoeren nicht hierher, sondern in
|
|
-- die Migrationen in db.js - CREATE TABLE IF NOT EXISTS fasst eine vorhandene
|
|
-- Tabelle nicht an.
|
|
--
|
|
-- Grundgedanke des Modells (aus der Featureliste vom 14.09.2026):
|
|
-- * "Experten" heissen jetzt Kontakte und umfassen auch die Ansprechpartner
|
|
-- der Kunden - eine Tabelle, unterschieden ueber `kind`.
|
|
-- * "Kunden" heissen jetzt Unternehmen. Ein Kunde ist ein Unternehmen mit
|
|
-- is_customer = 1; der Umschalter in der Oberflaeche filtert nur darauf.
|
|
-- * Jede Aenderung landet in icon_history. Wer wann was getan hat, steht
|
|
-- damit an genau einer Stelle - je Kontakt, je Unternehmen und global.
|
|
-- ============================================================================
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Benutzer
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_users (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
username VARCHAR(60) NOT NULL,
|
|
email VARCHAR(190) NULL,
|
|
name VARCHAR(190) NOT NULL,
|
|
role ENUM('admin','user') NOT NULL DEFAULT 'user',
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
-- Persoenliche Farbe: traegt Initialen-Avatare, Historie und Netzwerkknoten.
|
|
color VARCHAR(9) NULL,
|
|
-- Foto als Data-URL, im Browser auf 256 px vorkomprimiert.
|
|
avatar MEDIUMTEXT NULL,
|
|
theme VARCHAR(40) NULL,
|
|
theme_mode ENUM('light','dark') NULL,
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_icon_users_username (username),
|
|
UNIQUE KEY uq_icon_users_email (email)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Unternehmen (fruehere "Kunden")
|
|
--
|
|
-- `is_customer` unterscheidet unsere Kunden von allen uebrigen bekannten
|
|
-- Unternehmen; `customer_state` trennt darin aktiv von alumni. Ein Unternehmen
|
|
-- ohne Kundenhaken ist trotzdem vollwertig - dort haengen Werdegaenge, Leads
|
|
-- und die Unternehmenskarte dran.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_companies (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(190) NOT NULL,
|
|
-- Dachgesellschaft, z. B. "Finanz Informatik" ueber FI Hamburg/Frankfurt.
|
|
group_name VARCHAR(190) NULL,
|
|
industry VARCHAR(190) NULL,
|
|
employees INT UNSIGNED NULL,
|
|
website VARCHAR(300) NULL,
|
|
address TEXT NULL,
|
|
logo MEDIUMTEXT NULL,
|
|
-- 0 = nicht bewertet, 1..5 Sterne: wie interessant ist das Unternehmen.
|
|
rating TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
|
is_customer TINYINT(1) NOT NULL DEFAULT 0,
|
|
customer_state ENUM('aktiv','alumni') NOT NULL DEFAULT 'aktiv',
|
|
freelancers ENUM('unbekannt','ja','nein') NOT NULL DEFAULT 'unbekannt',
|
|
notes MEDIUMTEXT NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
owner_id INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
KEY idx_icon_companies_name (name),
|
|
KEY idx_icon_companies_group (group_name),
|
|
KEY idx_icon_companies_customer (is_customer, customer_state),
|
|
CONSTRAINT fk_icon_companies_creator FOREIGN KEY (created_by)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_companies_owner FOREIGN KEY (owner_id)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Kontakte (fruehere "Experten" plus Ansprechpartner der Unternehmen)
|
|
--
|
|
-- Der Verfuegbarkeitsstatus ist bewusst entfallen (Featureliste 14.09.2026).
|
|
-- `status` beschreibt nur noch den Stand der Zusammenarbeit.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_contacts (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
-- Akademischer Titel als Freitext ("Dr.", "Prof. Dr."). Bewusst keine
|
|
-- Auswahlliste: die Schreibweisen sind zu vielfaeltig, um sie zu pflegen.
|
|
title VARCHAR(60) NULL,
|
|
first_name VARCHAR(120) NULL,
|
|
last_name VARCHAR(120) NOT NULL,
|
|
position VARCHAR(190) NULL,
|
|
email VARCHAR(190) NULL,
|
|
phone VARCHAR(80) NULL,
|
|
linkedin VARCHAR(300) NULL,
|
|
avatar MEDIUMTEXT NULL,
|
|
-- Aktueller Arbeitgeber. Die Historie der frueheren steht in
|
|
-- icon_employments - daraus speist sich der Zweig "Verlassen".
|
|
company_id INT UNSIGNED NULL,
|
|
kind ENUM('Experte','Ansprechpartner (Kunde)','Lead') NOT NULL DEFAULT 'Experte',
|
|
status ENUM('Interessiert','Interview','Gebucht','Inaktiv') NOT NULL DEFAULT 'Interessiert',
|
|
-- Woher kommt der Kontakt (LinkedIn, Empfehlung, Portal, ...).
|
|
lead_source VARCHAR(190) NULL,
|
|
-- Featureliste: "persoenlicher Kontakt" oder "noch kein Kontakt gehabt".
|
|
contact_state ENUM('unbekannt','persoenlich','kein Kontakt') NOT NULL DEFAULT 'unbekannt',
|
|
freelancers ENUM('unbekannt','ja','nein') NOT NULL DEFAULT 'unbekannt',
|
|
bio MEDIUMTEXT NULL,
|
|
-- Kommagetrennte Schlagworte; die Oberflaeche zerlegt sie in Chips.
|
|
tags VARCHAR(1000) NULL,
|
|
rate_onsite DECIMAL(8,2) NULL,
|
|
rate_remote DECIMAL(8,2) NULL,
|
|
cv_url VARCHAR(500) NULL,
|
|
cv_file VARCHAR(255) NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
owner_id INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
KEY idx_icon_contacts_name (last_name, first_name),
|
|
KEY idx_icon_contacts_company (company_id),
|
|
KEY idx_icon_contacts_kind (kind),
|
|
KEY idx_icon_contacts_status (status),
|
|
CONSTRAINT fk_icon_contacts_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_contacts_creator FOREIGN KEY (created_by)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_contacts_owner FOREIGN KEY (owner_id)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Werdegang: welcher Kontakt war wann bei welchem Unternehmen
|
|
--
|
|
-- Traegt zwei Dinge aus der Featureliste: die automatische Liste "letzter
|
|
-- Arbeitgeber (Branche & Unternehmen)" und den Zweig "Verlassen" im Stammbaum
|
|
-- eines Unternehmens (is_current = 0).
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_employments (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
contact_id INT UNSIGNED NOT NULL,
|
|
company_id INT UNSIGNED NOT NULL,
|
|
position VARCHAR(190) NULL,
|
|
started_on DATE NULL,
|
|
ended_on DATE NULL,
|
|
is_current TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_icon_employments_contact (contact_id),
|
|
KEY idx_icon_employments_company (company_id, is_current),
|
|
CONSTRAINT fk_icon_employments_contact FOREIGN KEY (contact_id)
|
|
REFERENCES icon_contacts (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_icon_employments_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Interviews: "Hatten wir schon einmal ein Interview, und falls ja - wann,
|
|
-- mit wem?" Eigene Tabelle statt Freitext, damit danach gefiltert werden kann.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_interviews (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
contact_id INT UNSIGNED NULL,
|
|
company_id INT UNSIGNED NULL,
|
|
-- Wer von uns das Gespraech gefuehrt hat.
|
|
user_id INT UNSIGNED NULL,
|
|
held_on DATE NOT NULL,
|
|
notes MEDIUMTEXT NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_icon_interviews_contact (contact_id, held_on),
|
|
KEY idx_icon_interviews_company (company_id, held_on),
|
|
CONSTRAINT fk_icon_interviews_contact FOREIGN KEY (contact_id)
|
|
REFERENCES icon_contacts (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_icon_interviews_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_icon_interviews_user FOREIGN KEY (user_id)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Historie / Aktivitaetsprotokoll
|
|
--
|
|
-- Eine Tabelle fuer alles: Systemereignisse (angelegt, geaendert, geloescht)
|
|
-- und von Hand erfasste Notizen. `entity_type`/`entity_id` sagen, an welcher
|
|
-- Karte der Eintrag haengt; `user_id` immer, wer es war.
|
|
--
|
|
-- Absichtlich keine Fremdschluessel auf die Ziel-Tabellen: die Historie soll
|
|
-- einen geloeschten Kontakt ueberleben. Deshalb steht mit `entity_label` auch
|
|
-- der Name zum Zeitpunkt des Eintrags mit drin.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_history (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
entity_type ENUM('contact','company','tender','project','lead','user','system') NOT NULL,
|
|
entity_id INT UNSIGNED NULL,
|
|
entity_label VARCHAR(300) NULL,
|
|
-- created | updated | deleted | note | mention | interview | stage | ...
|
|
action VARCHAR(40) NOT NULL DEFAULT 'note',
|
|
-- Anzeigetext, darf Mention-Token @[Name](contact:12) enthalten.
|
|
text MEDIUMTEXT NULL,
|
|
-- Feldaenderungen als JSON: { feld: [vorher, nachher], ... }
|
|
changes MEDIUMTEXT NULL,
|
|
user_id INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_icon_history_entity (entity_type, entity_id, created_at),
|
|
KEY idx_icon_history_created (created_at),
|
|
CONSTRAINT fk_icon_history_user FOREIGN KEY (user_id)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Erwaehnungen (@-Mentions)
|
|
--
|
|
-- Der Token steht im Text; diese Tabelle ist der Index dazu. Damit beantwortet
|
|
-- eine Abfrage "wo wurde dieses Unternehmen ueberall genannt?", ohne jeden
|
|
-- Freitext der Datenbank durchsuchen zu muessen.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_mentions (
|
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
-- Wo die Erwaehnung steht.
|
|
source_type ENUM('history','contact','company','tender','project','lead') NOT NULL,
|
|
source_id BIGINT UNSIGNED NOT NULL,
|
|
-- Wer bzw. was erwaehnt wurde.
|
|
target_type ENUM('contact','company','user') NOT NULL,
|
|
target_id INT UNSIGNED NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_icon_mentions (source_type, source_id, target_type, target_id),
|
|
KEY idx_icon_mentions_target (target_type, target_id, created_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Netzwerk: wer kennt wen
|
|
--
|
|
-- Das Paar wird aufsteigend gespeichert (a < b), der Unique-Key verhindert
|
|
-- damit Duplikate in beide Richtungen.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_connections (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
contact_a INT UNSIGNED NOT NULL,
|
|
contact_b INT UNSIGNED NOT NULL,
|
|
kind VARCHAR(60) NOT NULL DEFAULT 'Kontakt',
|
|
strength TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_icon_connections (contact_a, contact_b),
|
|
CONSTRAINT fk_icon_connections_a FOREIGN KEY (contact_a)
|
|
REFERENCES icon_contacts (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_icon_connections_b FOREIGN KEY (contact_b)
|
|
REFERENCES icon_contacts (id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Ausschreibungen
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_tenders (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
title VARCHAR(300) NOT NULL,
|
|
source VARCHAR(190) NULL,
|
|
link VARCHAR(600) NULL,
|
|
company_id INT UNSIGNED NULL,
|
|
status ENUM('Aktiv','Inaktiv','Ausgelaufen','Archiviert') NOT NULL DEFAULT 'Aktiv',
|
|
stage ENUM('Ausschreibung','Kontakte sichten','Interview','Kunden Interview',
|
|
'Vertragsgestaltung','Abschluss','Geschlossen') NOT NULL DEFAULT 'Ausschreibung',
|
|
online TINYINT(1) NOT NULL DEFAULT 1,
|
|
details MEDIUMTEXT NULL,
|
|
notes MEDIUMTEXT NULL,
|
|
deadline DATE NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
KEY idx_icon_tenders_stage (stage),
|
|
KEY idx_icon_tenders_status (status),
|
|
CONSTRAINT fk_icon_tenders_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE TABLE IF NOT EXISTS icon_tender_contacts (
|
|
tender_id INT UNSIGNED NOT NULL,
|
|
contact_id INT UNSIGNED NOT NULL,
|
|
role VARCHAR(120) NULL,
|
|
PRIMARY KEY (tender_id, contact_id),
|
|
CONSTRAINT fk_icon_tc_tender FOREIGN KEY (tender_id)
|
|
REFERENCES icon_tenders (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_icon_tc_contact FOREIGN KEY (contact_id)
|
|
REFERENCES icon_contacts (id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Laufende Projekte
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_projects (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
title VARCHAR(300) NOT NULL,
|
|
company_id INT UNSIGNED NULL,
|
|
contact_id INT UNSIGNED NULL,
|
|
started_on DATE NULL,
|
|
ended_on DATE NULL,
|
|
status ENUM('Aktiv','Nicht aktiv','Abgeschlossen') NOT NULL DEFAULT 'Aktiv',
|
|
rate DECIMAL(8,2) NULL,
|
|
notes MEDIUMTEXT NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
KEY idx_icon_projects_status (status),
|
|
CONSTRAINT fk_icon_projects_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_projects_contact FOREIGN KEY (contact_id)
|
|
REFERENCES icon_contacts (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Lead-Pipeline (Kanban)
|
|
--
|
|
-- Schema steht schon; die Oberflaeche dazu kommt in der zweiten Ausbaustufe.
|
|
-- `remind_at` traegt die Erinnerungsfunktion, `reminder_days` das Intervall.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_leads (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
title VARCHAR(300) NOT NULL,
|
|
company_id INT UNSIGNED NULL,
|
|
contact_id INT UNSIGNED NULL,
|
|
stage ENUM('Fuer Spaeter','Neuer Lead','Erstkontakt','1. Gespraech',
|
|
'Followup geplant','Followup Gespraech','Closed','Absage')
|
|
NOT NULL DEFAULT 'Neuer Lead',
|
|
contact_state ENUM('unbekannt','persoenlich','kein Kontakt') NOT NULL DEFAULT 'unbekannt',
|
|
archived TINYINT(1) NOT NULL DEFAULT 0,
|
|
position INT NOT NULL DEFAULT 0,
|
|
notes MEDIUMTEXT NULL,
|
|
-- Zeitpunkt der naechsten Erinnerung; NULL = keine.
|
|
remind_at DATETIME NULL,
|
|
reminder_days INT UNSIGNED NULL,
|
|
last_contact_at DATETIME NULL,
|
|
owner_id INT UNSIGNED NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
KEY idx_icon_leads_stage (stage, archived, position),
|
|
KEY idx_icon_leads_remind (remind_at),
|
|
CONSTRAINT fk_icon_leads_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_leads_contact FOREIGN KEY (contact_id)
|
|
REFERENCES icon_contacts (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Suspects - die Stufe vor dem Lead (29.09.2026)
|
|
--
|
|
-- Personen, die interessant sein koennten, als schlichte Tabelle: Name,
|
|
-- Funktion und Unternehmen sind bewusst Freitext, nicht Verweise auf Kontakte
|
|
-- und Unternehmen - ein Suspect soll in Sekunden erfasst sein. Erst beim
|
|
-- Schritt zum Lead wird gegen die Stammdaten abgeglichen.
|
|
--
|
|
-- `remind_at` ist der "naechste Impuls": dann erinnert das CRM daran, die
|
|
-- Person erneut anzusprechen. `lead_id` zeigt nach der Uebergabe auf den Lead;
|
|
-- der Suspect selbst wandert dann ins Archiv.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_suspects (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(190) NOT NULL,
|
|
role VARCHAR(190) NULL,
|
|
company VARCHAR(190) NULL,
|
|
email VARCHAR(190) NULL,
|
|
linkedin VARCHAR(500) NULL,
|
|
website VARCHAR(500) NULL,
|
|
notes TEXT NULL,
|
|
last_contact_on DATE NULL,
|
|
remind_at DATETIME NULL,
|
|
archived TINYINT(1) NOT NULL DEFAULT 0,
|
|
lead_id INT UNSIGNED NULL,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
KEY idx_icon_suspects_archived (archived, id),
|
|
KEY idx_icon_suspects_remind (remind_at),
|
|
CONSTRAINT fk_icon_suspects_lead FOREIGN KEY (lead_id)
|
|
REFERENCES icon_leads (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_suspects_creator FOREIGN KEY (created_by)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE TABLE IF NOT EXISTS icon_lead_todos (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
lead_id INT UNSIGNED NOT NULL,
|
|
text VARCHAR(500) NOT NULL,
|
|
done TINYINT(1) NOT NULL DEFAULT 0,
|
|
due_on DATE NULL,
|
|
position INT NOT NULL DEFAULT 0,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
KEY idx_icon_lead_todos_lead (lead_id, position),
|
|
CONSTRAINT fk_icon_lead_todos_lead FOREIGN KEY (lead_id)
|
|
REFERENCES icon_leads (id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- CV-Import
|
|
--
|
|
-- Der Ablauf aus der Featureliste als drei Ordner in einer Tabelle:
|
|
-- eingang hochgeladen, noch nicht ausgelesen
|
|
-- screening von der KI ausgelesen, wartet auf Ja oder Nein
|
|
-- auswahl uebernommen - der Kontakt ist angelegt, der CV haengt dran
|
|
-- verworfen abgelehnt; die Datei bleibt, bis sie jemand loescht
|
|
--
|
|
-- `extracted` haelt die Ausgabe des Modells als JSON. Sie bleibt auch nach der
|
|
-- Uebernahme stehen: so laesst sich spaeter nachvollziehen, was die KI gelesen
|
|
-- hat und was ein Mensch daran geaendert hat.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_cv_imports (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
filename VARCHAR(255) NOT NULL,
|
|
original_name VARCHAR(255) NOT NULL,
|
|
mime VARCHAR(120) NULL,
|
|
size_bytes INT UNSIGNED NOT NULL DEFAULT 0,
|
|
status ENUM('eingang','screening','auswahl','verworfen') NOT NULL DEFAULT 'eingang',
|
|
extracted MEDIUMTEXT NULL,
|
|
-- Das im Lebenslauf gefundene Portrait, unveraendert als Data-URL. Es steht
|
|
-- hier nur zwischen Auslesen und Uebernehmen; beim Anlegen des Kontakts geht
|
|
-- es verkleinert an icon_contacts.avatar und wird hier geleert.
|
|
photo MEDIUMTEXT NULL,
|
|
error VARCHAR(500) NULL,
|
|
contact_id INT UNSIGNED NULL,
|
|
uploaded_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
processed_at TIMESTAMP NULL DEFAULT NULL,
|
|
KEY idx_icon_cv_status (status, created_at),
|
|
CONSTRAINT fk_icon_cv_contact FOREIGN KEY (contact_id)
|
|
REFERENCES icon_contacts (id) ON DELETE SET NULL,
|
|
CONSTRAINT fk_icon_cv_user FOREIGN KEY (uploaded_by)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Stammdaten: gepflegte Auswahllisten
|
|
--
|
|
-- Branche und Kontaktart waren Freitext bzw. ein festes ENUM. Beides wird in
|
|
-- der Praxis erweitert - eine neue Branche, eine neue Art von Kontakt - und
|
|
-- beides soll ohne Codeaenderung gehen. Die Werte stehen deshalb hier und
|
|
-- speisen die Auswahlfelder der Oberflaeche.
|
|
--
|
|
-- `field` ist bewusst kein ENUM: eine weitere Liste soll keine Migration
|
|
-- kosten.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_options (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
field VARCHAR(40) NOT NULL,
|
|
value VARCHAR(190) NOT NULL,
|
|
position INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_icon_options (field, value),
|
|
KEY idx_icon_options_field (field, position)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Einstellungen (Schluessel/Wert) - auch fuer einmalige Migrationsmerker
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_settings (
|
|
key_name VARCHAR(100) NOT NULL PRIMARY KEY,
|
|
value MEDIUMTEXT NULL,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Scanner: Stellenanzeigen aus Jobbörsen (DACH, jedes Alter)
|
|
--
|
|
-- Ein Suchagent ist eine Liste von Schlagwörtern plus der Börsen, in denen
|
|
-- gesucht wird, plus Ausschlussbegriffen. Jede Anzeige liegt genau einmal in
|
|
-- icon_scanner_jobs (eindeutig je Börse und deren Kennung); welcher Agent sie
|
|
-- mit welcher Güte gefunden hat, steht in icon_scanner_matches.
|
|
--
|
|
-- Bewertet wird nicht per KI, sondern per Zählung: je mehr Schlagwörter eines
|
|
-- Agenten in Titel und Text vorkommen, desto höher der Treffer.
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS icon_scanner_agents (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(190) NOT NULL,
|
|
-- JSON-Arrays: ["SAP FI", "Interim CFO"], ["ba", "stepstone"], ["Praktikum"]
|
|
keywords TEXT NOT NULL,
|
|
sources TEXT NOT NULL,
|
|
excludes TEXT NULL,
|
|
-- Schlagwoerter mit Alarm: werden wie `keywords` gesucht und gezaehlt,
|
|
-- ein Fund meldet sich zusaetzlich mit Ton und Pop-up.
|
|
alarm_keywords TEXT NULL,
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
created_by INT UNSIGNED NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_icon_scanner_agents_creator FOREIGN KEY (created_by)
|
|
REFERENCES icon_users (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE TABLE IF NOT EXISTS icon_scanner_jobs (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
source VARCHAR(30) NOT NULL,
|
|
external_id VARCHAR(190) NOT NULL,
|
|
title VARCHAR(500) NOT NULL,
|
|
company VARCHAR(300) NULL,
|
|
-- Treffer im CRM: Unternehmen mit gleichem (normalisiertem) Namen.
|
|
company_id INT UNSIGNED NULL,
|
|
location VARCHAR(300) NULL,
|
|
country CHAR(2) NULL,
|
|
employment_type VARCHAR(60) NULL,
|
|
url VARCHAR(1000) NOT NULL,
|
|
description MEDIUMTEXT NULL,
|
|
published_at DATETIME NULL,
|
|
first_seen_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_seen_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_icon_scanner_jobs (source, external_id),
|
|
KEY idx_icon_scanner_jobs_published (published_at),
|
|
KEY idx_icon_scanner_jobs_company (company_id),
|
|
CONSTRAINT fk_icon_scanner_jobs_company FOREIGN KEY (company_id)
|
|
REFERENCES icon_companies (id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
CREATE TABLE IF NOT EXISTS icon_scanner_matches (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
agent_id INT UNSIGNED NOT NULL,
|
|
job_id INT UNSIGNED NOT NULL,
|
|
score TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
|
-- JSON-Array der gefundenen Schlagwörter
|
|
hits TEXT NULL,
|
|
status ENUM('neu','gesehen','in_bearbeitung','verworfen') NOT NULL DEFAULT 'neu',
|
|
-- Alarm-Schlagwort getroffen; alarm_ack = jemand hat das Pop-up quittiert.
|
|
alarm TINYINT(1) NOT NULL DEFAULT 0,
|
|
alarm_ack TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_icon_scanner_matches (agent_id, job_id),
|
|
KEY idx_icon_scanner_matches_created (created_at),
|
|
CONSTRAINT fk_icon_scanner_matches_agent FOREIGN KEY (agent_id)
|
|
REFERENCES icon_scanner_agents (id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_icon_scanner_matches_job FOREIGN KEY (job_id)
|
|
REFERENCES icon_scanner_jobs (id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- Protokoll der Läufe: wann, wodurch ausgelöst, was je Börse herauskam.
|
|
CREATE TABLE IF NOT EXISTS icon_scanner_runs (
|
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
trigger_kind ENUM('auto','manual') NOT NULL,
|
|
started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
finished_at TIMESTAMP NULL,
|
|
jobs_found INT UNSIGNED NOT NULL DEFAULT 0,
|
|
matches_new INT UNSIGNED NOT NULL DEFAULT 0,
|
|
-- JSON: { ba: { ok: true, count: 12 }, stepstone: { ok: false, error: "..." } }
|
|
per_source TEXT NULL,
|
|
KEY idx_icon_scanner_runs_started (started_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|