feat(leads): Leads universell mit Projekten verknüpfen
Ein Projekt lebt in genau einem Heimat-Bereich und wird einmal angelegt; Leads bekommen eine optionale Verknüpfung dorthin (lead.projectId), egal in welchem Bereich das Projekt wohnt — kein redundantes Anlegen mehr. - Migration 0011: lead.project_id (FK → project, ON DELETE SET NULL). - Lead anlegen / CSV-Import / Lead-Detail: Projekt zuordnen; CSV ordnet alle importierten Leads einem Projekt zu. - Projektseite: neue "Leads"-Sektion (Vertriebs-Sicht des Projekts). - Vertrieb: "Projekte mit Vertriebsaktivität" — bereichsübergreifend alle Projekte mit verknüpften Leads (so erscheint ein Entwicklungsprojekt hier, WEIL es Leads hat). Rein interne Projekte ohne Leads tauchen nicht auf. - Lead-Liste/Detail zeigen das verknüpfte Projekt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
68480c1eb1
commit
19db774c4c
10 files changed
+3522
-3
No files matched your search
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "lead" ADD COLUMN "project_id" uuid;--> statement-breakpoint
|
||||
ALTER TABLE "lead" ADD CONSTRAINT "lead_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "idx_lead_project" ON "lead" USING btree ("project_id");
|
||||
File diff suppressed because it is too large.
Load diff
@@ -78,6 +78,13 @@
|
||||
"when": 1780672129310,
|
||||
"tag": "0010_leitfaden_phases",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1780754557002,
|
||||
"tag": "0011_lead_project_link",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -465,6 +465,10 @@ export const lead = pgTable(
|
||||
areaId: uuid('area_id')
|
||||
.notNull()
|
||||
.references(() => area.id, { onDelete: 'restrict' }),
|
||||
// Optionale Verknüpfung zum (universellen) Projekt — egal in welchem Bereich
|
||||
// dieses wohnt. So sind „HiveGrid-Leads" mit dem HiveGrid-Projekt verbunden,
|
||||
// ohne dass das Projekt im Vertrieb redundant angelegt werden muss.
|
||||
projectId: uuid('project_id').references(() => project.id, { onDelete: 'set null' }),
|
||||
// 1. Stammdaten
|
||||
firma: text('firma').notNull(),
|
||||
status: text('status', { enum: ['cold', 'warm', 'hot', 'won', 'lost'] })
|
||||
@@ -525,6 +529,7 @@ export const lead = pgTable(
|
||||
(t) => ({
|
||||
idxStatus: index('idx_lead_status').on(t.status),
|
||||
idxOwner: index('idx_lead_owner').on(t.ownerUserId),
|
||||
idxProject: index('idx_lead_project').on(t.projectId),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ const leadCoreFields = {
|
||||
status: leadStatusSchema,
|
||||
score: z.number().int().min(0).max(10),
|
||||
ownerUserId: z.string().uuid().nullable(),
|
||||
projectId: z.string().uuid().nullable(),
|
||||
branche: z.string().max(200).nullable(),
|
||||
subSegment: z.string().max(200).nullable(),
|
||||
standorte: z.string().max(500).nullable(),
|
||||
@@ -80,6 +81,7 @@ export const createLeadInput = z.object({
|
||||
status: leadStatusSchema.default('cold'),
|
||||
score: z.number().int().min(0).max(10).default(0),
|
||||
ownerUserId: z.string().uuid().optional(),
|
||||
projectId: z.string().uuid().optional(),
|
||||
branche: z.string().max(200).optional(),
|
||||
subSegment: z.string().max(200).optional(),
|
||||
website: z.string().max(500).optional(),
|
||||
@@ -87,6 +89,8 @@ export const createLeadInput = z.object({
|
||||
});
|
||||
export type CreateLeadInput = z.infer<typeof createLeadInput>;
|
||||
|
||||
export const leadsForProjectInput = z.object({ projectId: z.string().uuid() });
|
||||
|
||||
// Update: id + beliebige Teilmenge der Kernfelder (partial patch).
|
||||
export const updateLeadInput = z
|
||||
.object({ id: z.string().uuid() })
|
||||
@@ -139,5 +143,6 @@ export type PromoteToDealInput = z.infer<typeof promoteToDealInput>;
|
||||
// CSV-Import: rohe CSV-Datei wird serverseitig geparst (header-basiert).
|
||||
export const importLeadsCsvInput = z.object({
|
||||
csv: z.string().min(1).max(2_000_000),
|
||||
projectId: z.string().uuid().optional(), // alle importierten Leads diesem Projekt zuordnen
|
||||
});
|
||||
export type ImportLeadsCsvInput = z.infer<typeof importLeadsCsvInput>;
|
||||
Reference in new issue
Block a user