mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 09:26:40 +02:00
Backend (CRM-Expert Phase 1): - New enums: ContactSource, EntityStatus, CompanySize, OwnerRole, LostReason, EmailType, PhoneType - Contact: add linkedinUrl, birthday, source, department, status - Company: add vatId, taxId, tradeRegisterNumber, registerCourt, companySize, deliveryAddress, dataEnrichedAt/Source, status - Deal: add lostReason + lostReasonText (required when status=LOST) - Multi-value emails/phones tables (contact_emails, contact_phones) - Owner m:n model (contact_owners, company_owners, deal_owners) - Redis Pub/Sub CRM events (crm.contact.created, crm.deal.won, etc.) - Activity due_soon scheduler (cron every 15 min) - SQL migration with data migration for existing records Frontend integration: - types.ts: all new enums, interfaces, label maps - api.ts: owner CRUD endpoints (add/remove for contacts/companies/deals) - hooks.ts: 6 new owner mutation hooks - ContactFormModal: LinkedIn, birthday, source, department, status fields - ContactDetailPage: display new fields (LinkedIn, department, birthday, source, status badge) - CompanyDetailPage: display vatId, taxId, trade register, company size, delivery address, data enrichment info - DealFormModal: lost reason dropdown + text (shown when status=LOST) - DealDetailPage: display lost reason with label - CompaniesPage: EntityStatus-aware status dots (ACTIVE/INACTIVE/BLOCKED) - ActivityType: add FOLLOWUP to all label maps Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
270 lines
10 KiB
SQL
270 lines
10 KiB
SQL
-- ============================================================
|
|
-- Phase 1: Schema Expansion
|
|
-- Neue Enums, Felder, Multi-Value Tabellen, Owner-Tabellen
|
|
-- ============================================================
|
|
|
|
-- WICHTIG: ALTER TYPE ... ADD VALUE kann NICHT in einer Transaction laufen.
|
|
-- Diese Migration muss daher ausserhalb von BEGIN/COMMIT ausgefuehrt werden,
|
|
-- oder der ADD VALUE muss separat laufen.
|
|
|
|
-- --------------------------------------------------------
|
|
-- 1. Neue Enums erstellen
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE TYPE "app_crm"."ContactSource" AS ENUM (
|
|
'TRADE_FAIR', 'REFERRAL', 'WEBSITE', 'COLD_CALL',
|
|
'IMPORT', 'BUSINESS_CARD', 'OTHER'
|
|
);
|
|
|
|
CREATE TYPE "app_crm"."EntityStatus" AS ENUM (
|
|
'ACTIVE', 'INACTIVE', 'BLOCKED'
|
|
);
|
|
|
|
CREATE TYPE "app_crm"."CompanySize" AS ENUM (
|
|
'SIZE_1_10', 'SIZE_11_50', 'SIZE_51_200', 'SIZE_201_500', 'SIZE_500_PLUS'
|
|
);
|
|
|
|
CREATE TYPE "app_crm"."OwnerRole" AS ENUM (
|
|
'OWNER', 'MEMBER', 'WATCHER'
|
|
);
|
|
|
|
CREATE TYPE "app_crm"."LostReason" AS ENUM (
|
|
'PRICE', 'TIMING', 'COMPETITOR', 'NO_NEED', 'OTHER'
|
|
);
|
|
|
|
CREATE TYPE "app_crm"."EmailType" AS ENUM (
|
|
'WORK', 'PERSONAL', 'OTHER'
|
|
);
|
|
|
|
CREATE TYPE "app_crm"."PhoneType" AS ENUM (
|
|
'OFFICE', 'MOBILE', 'FAX'
|
|
);
|
|
|
|
-- FOLLOWUP zum ActivityType Enum hinzufuegen (NICHT transaktionsfaehig!)
|
|
ALTER TYPE "app_crm"."ActivityType" ADD VALUE IF NOT EXISTS 'FOLLOWUP';
|
|
|
|
-- --------------------------------------------------------
|
|
-- 2. Neue Spalten auf contacts
|
|
-- --------------------------------------------------------
|
|
|
|
ALTER TABLE "app_crm"."contacts"
|
|
ADD COLUMN "linkedin_url" VARCHAR(500),
|
|
ADD COLUMN "birthday" TIMESTAMP(3),
|
|
ADD COLUMN "source" "app_crm"."ContactSource",
|
|
ADD COLUMN "department" VARCHAR(200),
|
|
ADD COLUMN "status" "app_crm"."EntityStatus" NOT NULL DEFAULT 'ACTIVE';
|
|
|
|
-- --------------------------------------------------------
|
|
-- 3. Neue Spalten auf companies
|
|
-- --------------------------------------------------------
|
|
|
|
ALTER TABLE "app_crm"."companies"
|
|
ADD COLUMN "vat_id" VARCHAR(50),
|
|
ADD COLUMN "tax_id" VARCHAR(50),
|
|
ADD COLUMN "trade_register_number" VARCHAR(100),
|
|
ADD COLUMN "register_court" VARCHAR(200),
|
|
ADD COLUMN "company_size" "app_crm"."CompanySize",
|
|
ADD COLUMN "delivery_street" VARCHAR(200),
|
|
ADD COLUMN "delivery_zip" VARCHAR(20),
|
|
ADD COLUMN "delivery_city" VARCHAR(100),
|
|
ADD COLUMN "delivery_country" VARCHAR(2),
|
|
ADD COLUMN "status" "app_crm"."EntityStatus" NOT NULL DEFAULT 'ACTIVE',
|
|
ADD COLUMN "data_enriched_at" TIMESTAMP(3),
|
|
ADD COLUMN "data_enriched_source" VARCHAR(200);
|
|
|
|
-- --------------------------------------------------------
|
|
-- 4. Neue Spalten auf deals (Lost-Reason)
|
|
-- --------------------------------------------------------
|
|
|
|
ALTER TABLE "app_crm"."deals"
|
|
ADD COLUMN "lost_reason" "app_crm"."LostReason",
|
|
ADD COLUMN "lost_reason_text" TEXT;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 5. Daten-Migration: isActive → status
|
|
-- --------------------------------------------------------
|
|
|
|
UPDATE "app_crm"."contacts"
|
|
SET "status" = 'INACTIVE'
|
|
WHERE "is_active" = false;
|
|
|
|
UPDATE "app_crm"."companies"
|
|
SET "status" = 'INACTIVE'
|
|
WHERE "is_active" = false;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 6. Multi-Value: contact_emails Tabelle
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE TABLE "app_crm"."contact_emails" (
|
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
"contact_id" UUID,
|
|
"company_id" UUID,
|
|
"email" VARCHAR(255) NOT NULL,
|
|
"type" "app_crm"."EmailType" NOT NULL DEFAULT 'WORK',
|
|
"is_primary" BOOLEAN NOT NULL DEFAULT false,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT "contact_emails_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE INDEX "contact_emails_contact_id_idx" ON "app_crm"."contact_emails"("contact_id");
|
|
CREATE INDEX "contact_emails_company_id_idx" ON "app_crm"."contact_emails"("company_id");
|
|
|
|
ALTER TABLE "app_crm"."contact_emails"
|
|
ADD CONSTRAINT "contact_emails_contact_id_fkey"
|
|
FOREIGN KEY ("contact_id") REFERENCES "app_crm"."contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
ALTER TABLE "app_crm"."contact_emails"
|
|
ADD CONSTRAINT "contact_emails_company_id_fkey"
|
|
FOREIGN KEY ("company_id") REFERENCES "app_crm"."companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 7. Multi-Value: contact_phones Tabelle
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE TABLE "app_crm"."contact_phones" (
|
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
"contact_id" UUID,
|
|
"company_id" UUID,
|
|
"phone" VARCHAR(50) NOT NULL,
|
|
"type" "app_crm"."PhoneType" NOT NULL DEFAULT 'OFFICE',
|
|
"is_primary" BOOLEAN NOT NULL DEFAULT false,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT "contact_phones_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE INDEX "contact_phones_contact_id_idx" ON "app_crm"."contact_phones"("contact_id");
|
|
CREATE INDEX "contact_phones_company_id_idx" ON "app_crm"."contact_phones"("company_id");
|
|
|
|
ALTER TABLE "app_crm"."contact_phones"
|
|
ADD CONSTRAINT "contact_phones_contact_id_fkey"
|
|
FOREIGN KEY ("contact_id") REFERENCES "app_crm"."contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
ALTER TABLE "app_crm"."contact_phones"
|
|
ADD CONSTRAINT "contact_phones_company_id_fkey"
|
|
FOREIGN KEY ("company_id") REFERENCES "app_crm"."companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 8. Daten-Migration: Bestehende Emails/Phones migrieren
|
|
-- --------------------------------------------------------
|
|
|
|
-- Contact Emails
|
|
INSERT INTO "app_crm"."contact_emails" ("id", "contact_id", "email", "type", "is_primary")
|
|
SELECT gen_random_uuid(), "id", "email", 'WORK'::"app_crm"."EmailType", true
|
|
FROM "app_crm"."contacts"
|
|
WHERE "email" IS NOT NULL AND "email" != '';
|
|
|
|
-- Contact Phones (phone → OFFICE, mobile → MOBILE)
|
|
INSERT INTO "app_crm"."contact_phones" ("id", "contact_id", "phone", "type", "is_primary")
|
|
SELECT gen_random_uuid(), "id", "phone", 'OFFICE'::"app_crm"."PhoneType", true
|
|
FROM "app_crm"."contacts"
|
|
WHERE "phone" IS NOT NULL AND "phone" != '';
|
|
|
|
INSERT INTO "app_crm"."contact_phones" ("id", "contact_id", "phone", "type", "is_primary")
|
|
SELECT gen_random_uuid(), "id", "mobile", 'MOBILE'::"app_crm"."PhoneType", false
|
|
FROM "app_crm"."contacts"
|
|
WHERE "mobile" IS NOT NULL AND "mobile" != '';
|
|
|
|
-- Company Emails
|
|
INSERT INTO "app_crm"."contact_emails" ("id", "company_id", "email", "type", "is_primary")
|
|
SELECT gen_random_uuid(), "id", "email", 'WORK'::"app_crm"."EmailType", true
|
|
FROM "app_crm"."companies"
|
|
WHERE "email" IS NOT NULL AND "email" != '';
|
|
|
|
-- Company Phones
|
|
INSERT INTO "app_crm"."contact_phones" ("id", "company_id", "phone", "type", "is_primary")
|
|
SELECT gen_random_uuid(), "id", "phone", 'OFFICE'::"app_crm"."PhoneType", true
|
|
FROM "app_crm"."companies"
|
|
WHERE "phone" IS NOT NULL AND "phone" != '';
|
|
|
|
-- Hinweis: Legacy-Spalten email, phone, mobile bleiben bestehen (deprecated)
|
|
|
|
-- --------------------------------------------------------
|
|
-- 9. Owner-Tabellen: contact_owners
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE TABLE "app_crm"."contact_owners" (
|
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
"tenant_id" UUID NOT NULL,
|
|
"contact_id" UUID NOT NULL,
|
|
"user_id" UUID NOT NULL,
|
|
"role" "app_crm"."OwnerRole" NOT NULL DEFAULT 'OWNER',
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT "contact_owners_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE UNIQUE INDEX "contact_owners_contact_id_user_id_key"
|
|
ON "app_crm"."contact_owners"("contact_id", "user_id");
|
|
CREATE INDEX "contact_owners_tenant_id_idx"
|
|
ON "app_crm"."contact_owners"("tenant_id");
|
|
CREATE INDEX "contact_owners_tenant_id_contact_id_idx"
|
|
ON "app_crm"."contact_owners"("tenant_id", "contact_id");
|
|
|
|
ALTER TABLE "app_crm"."contact_owners"
|
|
ADD CONSTRAINT "contact_owners_contact_id_fkey"
|
|
FOREIGN KEY ("contact_id") REFERENCES "app_crm"."contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 10. Owner-Tabellen: company_owners
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE TABLE "app_crm"."company_owners" (
|
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
"tenant_id" UUID NOT NULL,
|
|
"company_id" UUID NOT NULL,
|
|
"user_id" UUID NOT NULL,
|
|
"role" "app_crm"."OwnerRole" NOT NULL DEFAULT 'OWNER',
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT "company_owners_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE UNIQUE INDEX "company_owners_company_id_user_id_key"
|
|
ON "app_crm"."company_owners"("company_id", "user_id");
|
|
CREATE INDEX "company_owners_tenant_id_idx"
|
|
ON "app_crm"."company_owners"("tenant_id");
|
|
CREATE INDEX "company_owners_tenant_id_company_id_idx"
|
|
ON "app_crm"."company_owners"("tenant_id", "company_id");
|
|
|
|
ALTER TABLE "app_crm"."company_owners"
|
|
ADD CONSTRAINT "company_owners_company_id_fkey"
|
|
FOREIGN KEY ("company_id") REFERENCES "app_crm"."companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- Bestehende Company.ownerId → company_owners migrieren
|
|
INSERT INTO "app_crm"."company_owners" ("id", "tenant_id", "company_id", "user_id", "role")
|
|
SELECT gen_random_uuid(), "tenant_id", "id", "owner_id", 'OWNER'::"app_crm"."OwnerRole"
|
|
FROM "app_crm"."companies"
|
|
WHERE "owner_id" IS NOT NULL;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 11. Owner-Tabellen: deal_owners
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE TABLE "app_crm"."deal_owners" (
|
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
"tenant_id" UUID NOT NULL,
|
|
"deal_id" UUID NOT NULL,
|
|
"user_id" UUID NOT NULL,
|
|
"role" "app_crm"."OwnerRole" NOT NULL DEFAULT 'OWNER',
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT "deal_owners_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE UNIQUE INDEX "deal_owners_deal_id_user_id_key"
|
|
ON "app_crm"."deal_owners"("deal_id", "user_id");
|
|
CREATE INDEX "deal_owners_tenant_id_idx"
|
|
ON "app_crm"."deal_owners"("tenant_id");
|
|
CREATE INDEX "deal_owners_tenant_id_deal_id_idx"
|
|
ON "app_crm"."deal_owners"("tenant_id", "deal_id");
|
|
|
|
ALTER TABLE "app_crm"."deal_owners"
|
|
ADD CONSTRAINT "deal_owners_deal_id_fkey"
|
|
FOREIGN KEY ("deal_id") REFERENCES "app_crm"."deals"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
|
|
-- --------------------------------------------------------
|
|
-- 12. Zusaetzliche Indexes fuer status
|
|
-- --------------------------------------------------------
|
|
|
|
CREATE INDEX "contacts_tenant_id_status_idx"
|
|
ON "app_crm"."contacts"("tenant_id", "status");
|
|
CREATE INDEX "companies_tenant_id_status_idx"
|
|
ON "app_crm"."companies"("tenant_id", "status");
|