feat: Interviews, Mail-Papierkorb, Kontaktart

Drei Aenderungen aus dem Alltagsgebrauch:

- Der Kalender heisst jetzt "Interviews" - dafuer wird er benutzt. Der Wert in
  ViewState wandert mit, damit Menue und Kopfzeile nicht auseinanderlaufen.
  Sichtbare Reste ("Kalenderfarbe") heissen jetzt Terminfarbe.

- Mails haben einen Papierkorb, der unter dem Menuepunkt aufklappt. Loeschen
  heisst dabei ausschliesslich: im CRM. Im Postfach crm@perinco.de bleibt die
  Mail unberuehrt - Schreibrechte darauf hat die Anwendung nicht. Damit der
  Delta-Abgleich geloeschte Mails nicht wieder einliest, bleibt nach dem Leeren
  eine leere Zeile als Merker stehen (crm_purged); runMailSync() ueberspringt
  alles, was im CRM geloescht ist. Zurueckholen und Leeren darf jeder
  Angemeldete, Leeren nach einer Warnung.

- Der Kontaktstatus (Neu .. Verloren) wird zur Kontaktart: Recruiter, Partner,
  Endkunde. Die Spalte heisst jetzt kind, weil "Status" das Feld falsch
  benannte. migrateLeadKind() stellt bestehende Datenbanken um und setzt alle
  vorhandenen Kontakte auf Recruiter - welche Art jemand ist, sagt der alte
  Status nicht (so mit dem Nutzer entschieden).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-08-18 11:12:49 +02:00
co-authored by Claude Opus 5
parent 025390e18a
commit 8741a9ae0c
39 changed files with 719 additions and 199 deletions
+28 -4
View File
@@ -40,8 +40,9 @@ CREATE TABLE IF NOT EXISTS crm_leads (
zip VARCHAR(24) NULL,
city VARCHAR(120) NULL,
country VARCHAR(120) NULL,
status ENUM('Neu','Kontaktiert','Qualifiziert','Angebot','Gewonnen','Verloren')
NOT NULL DEFAULT 'Neu',
-- Art des Kontakts. Frueher ein Vertriebsstatus (Neu .. Verloren); die
-- Umstellung bestehender Datenbanken macht migrateLeadKind() in db.js.
kind ENUM('Recruiter','Partner','Endkunde') NOT NULL DEFAULT 'Recruiter',
source VARCHAR(120) NULL,
tags LONGTEXT NULL, -- JSON-Array als Text (MariaDB-kompatibel)
notes TEXT NULL,
@@ -53,7 +54,7 @@ CREATE TABLE IF NOT EXISTS crm_leads (
PRIMARY KEY (id),
KEY idx_crm_leads_name (last_name, first_name),
KEY idx_crm_leads_company (company),
KEY idx_crm_leads_status (status),
KEY idx_crm_leads_kind (kind),
KEY idx_crm_leads_owner (owner_id),
KEY idx_crm_leads_creator (created_by),
CONSTRAINT fk_crm_leads_owner FOREIGN KEY (owner_id) REFERENCES crm_users (id) ON DELETE SET NULL,
@@ -231,15 +232,38 @@ CREATE TABLE IF NOT EXISTS crm_mail_messages (
web_link VARCHAR(1024) NULL, -- Outlook-Web-Link
is_read_in_mailbox TINYINT(1) NOT NULL DEFAULT 0,
is_deleted TINYINT(1) NOT NULL DEFAULT 0, -- im Postfach geloescht
crm_deleted_at DATETIME NULL, -- im CRM in den Papierkorb gelegt (UTC)
crm_deleted_by INT UNSIGNED NULL, -- wer; bewusst ohne Fremdschluessel, s.u.
crm_purged TINYINT(1) NOT NULL DEFAULT 0, -- Papierkorb geleert
first_seen_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_crm_mail_graph (graph_id_hash),
KEY idx_crm_mail_received (received_at),
KEY idx_crm_mail_from (from_email),
KEY idx_crm_mail_conversation (conversation_id(191))
KEY idx_crm_mail_conversation (conversation_id(191)),
KEY idx_crm_mail_crm_deleted (crm_deleted_at)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*
* Papierkorb des CRM. Zwei Stufen, weil sie zwei verschiedene Dinge bedeuten:
*
* crm_deleted_at - liegt im Papierkorb, laesst sich zurueckholen.
* crm_purged - Papierkorb geleert. Die Zeile bleibt als Merker stehen,
* ihr Inhalt wird geleert. Ohne diesen Merker wuerde der
* naechste Abgleich die Mail aus dem Postfach - dort ist sie
* ja weiterhin vorhanden - sofort wieder einlesen.
*
* crm_deleted_by traegt bewusst keinen Fremdschluessel: Spalten lassen sich per
* ADD COLUMN IF NOT EXISTS idempotent nachziehen, ein Fremdschluessel nicht.
* Verschwindet der Benutzer, bleibt hier eine Id ohne Namen stehen - der Merker
* ist die Loeschung selbst, nicht die Person.
*/
ALTER TABLE crm_mail_messages ADD COLUMN IF NOT EXISTS crm_deleted_at DATETIME NULL AFTER is_deleted;
ALTER TABLE crm_mail_messages ADD COLUMN IF NOT EXISTS crm_deleted_by INT UNSIGNED NULL AFTER crm_deleted_at;
ALTER TABLE crm_mail_messages ADD COLUMN IF NOT EXISTS crm_purged TINYINT(1) NOT NULL DEFAULT 0 AFTER crm_deleted_by;
ALTER TABLE crm_mail_messages ADD INDEX IF NOT EXISTS idx_crm_mail_crm_deleted (crm_deleted_at);
/*
* Verknuepfung einer Mail mit Kontakt und/oder Opportunity. Absichtlich eine
* eigene Tabelle statt eines Textanhangs im Notizfeld: so laesst sich eine