feat: add DISC profiles and a sales copilot as an org module
Help reps capture a customer's likely behavior style and get negotiation guidance, with the feature gated from super admin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Person" ADD COLUMN "discLabel" TEXT,
|
||||
ADD COLUMN "discReliable" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "discAssessedAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Person_organizationId_discLabel_idx" ON "Person"("organizationId", "discLabel");
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DiscAssessment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"personId" TEXT NOT NULL,
|
||||
"assessorId" TEXT NOT NULL,
|
||||
"answers" JSONB NOT NULL,
|
||||
"scoreD" INTEGER NOT NULL,
|
||||
"scoreI" INTEGER NOT NULL,
|
||||
"scoreS" INTEGER NOT NULL,
|
||||
"scoreC" INTEGER NOT NULL,
|
||||
"percentD" INTEGER NOT NULL,
|
||||
"percentI" INTEGER NOT NULL,
|
||||
"percentS" INTEGER NOT NULL,
|
||||
"percentC" INTEGER NOT NULL,
|
||||
"validCount" INTEGER NOT NULL,
|
||||
"primaryTypes" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"secondaryTypes" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"label" TEXT NOT NULL,
|
||||
"secondaryLabel" TEXT,
|
||||
"reliable" BOOLEAN NOT NULL,
|
||||
"confidence" TEXT NOT NULL,
|
||||
"note" TEXT,
|
||||
"status" TEXT NOT NULL DEFAULT 'confirmed',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DiscAssessment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SalesCopilotSession" (
|
||||
"id" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
"personId" TEXT,
|
||||
"leadId" TEXT,
|
||||
"dealId" TEXT,
|
||||
"userId" TEXT NOT NULL,
|
||||
"topic" TEXT NOT NULL,
|
||||
"goal" TEXT NOT NULL,
|
||||
"customerStatement" TEXT NOT NULL,
|
||||
"behaviorNotes" TEXT,
|
||||
"analysis" JSONB NOT NULL,
|
||||
"confirmedResponse" TEXT,
|
||||
"nextAction" TEXT,
|
||||
"followUpAt" TIMESTAMP(3),
|
||||
"callOutcome" TEXT,
|
||||
"agentNote" TEXT,
|
||||
"status" TEXT NOT NULL,
|
||||
"messageDraft" TEXT,
|
||||
"activityId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SalesCopilotSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "DiscAssessment_organizationId_personId_createdAt_idx" ON "DiscAssessment"("organizationId", "personId", "createdAt");
|
||||
CREATE INDEX "DiscAssessment_personId_idx" ON "DiscAssessment"("personId");
|
||||
CREATE INDEX "SalesCopilotSession_organizationId_personId_createdAt_idx" ON "SalesCopilotSession"("organizationId", "personId", "createdAt");
|
||||
CREATE INDEX "SalesCopilotSession_organizationId_leadId_idx" ON "SalesCopilotSession"("organizationId", "leadId");
|
||||
CREATE INDEX "SalesCopilotSession_dealId_idx" ON "SalesCopilotSession"("dealId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "DiscAssessment" ADD CONSTRAINT "DiscAssessment_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DiscAssessment" ADD CONSTRAINT "DiscAssessment_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DiscAssessment" ADD CONSTRAINT "DiscAssessment_assessorId_fkey" FOREIGN KEY ("assessorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "SalesCopilotSession" ADD CONSTRAINT "SalesCopilotSession_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "SalesCopilotSession" ADD CONSTRAINT "SalesCopilotSession_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "SalesCopilotSession" ADD CONSTRAINT "SalesCopilotSession_leadId_fkey" FOREIGN KEY ("leadId") REFERENCES "Lead"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SalesCopilotSession" ADD CONSTRAINT "SalesCopilotSession_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SalesCopilotSession" ADD CONSTRAINT "SalesCopilotSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- ماژول برای محیطهایی که از قبل ماژول دارند روشن میشود و در سوپرادمین قابل خاموش کردن است
|
||||
UPDATE "Organization"
|
||||
SET "enabledModules" = array_append("enabledModules", 'behavior')
|
||||
WHERE cardinality("enabledModules") > 0
|
||||
AND NOT ('behavior' = ANY("enabledModules"));
|
||||
|
||||
-- مجوز رفتار برای نقشهایی که فروش دارند؛ پشتیبانی بدون این کلید میماند
|
||||
UPDATE "OrgRole"
|
||||
SET "permissions" = array_append("permissions", 'behavior')
|
||||
WHERE NOT ('behavior' = ANY("permissions"))
|
||||
AND (
|
||||
"systemKey" IN ('owner', 'admin', 'sales')
|
||||
OR 'sales' = ANY("permissions")
|
||||
);
|
||||
@@ -161,6 +161,8 @@ model Organization {
|
||||
teams Team[]
|
||||
companies Company[]
|
||||
people Person[]
|
||||
discAssessments DiscAssessment[]
|
||||
salesCopilotSessions SalesCopilotSession[]
|
||||
pipelines Pipeline[]
|
||||
deals Deal[]
|
||||
activities Activity[]
|
||||
@@ -311,6 +313,8 @@ model User {
|
||||
routineTaskEvents RoutineTaskEvent[]
|
||||
routineSettings RoutineUserSettings[]
|
||||
deletionArchives DeletionArchive[] @relation("DeletionActor")
|
||||
discAssessments DiscAssessment[] @relation("DiscAssessor")
|
||||
salesCopilotSessions SalesCopilotSession[] @relation("CopilotAuthor")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -554,6 +558,11 @@ model Person {
|
||||
routineTasks RoutineTask[]
|
||||
outboundSmsLogs OutboundSmsLog[]
|
||||
financialDocuments FinancialDocument[]
|
||||
discLabel String?
|
||||
discReliable Boolean @default(false)
|
||||
discAssessedAt DateTime?
|
||||
discAssessments DiscAssessment[]
|
||||
salesCopilotSessions SalesCopilotSession[]
|
||||
financialReceivables FinancialReceivable[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -569,6 +578,7 @@ model Person {
|
||||
@@index([companyId])
|
||||
@@index([ownerId])
|
||||
@@index([loyaltyTierId])
|
||||
@@index([organizationId, discLabel])
|
||||
}
|
||||
|
||||
model Pipeline {
|
||||
@@ -640,6 +650,7 @@ model Deal {
|
||||
expectedCloseAt DateTime?
|
||||
personId String?
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
||||
salesCopilotSessions SalesCopilotSession[]
|
||||
companyId String?
|
||||
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
||||
ownerId String?
|
||||
@@ -2766,6 +2777,7 @@ model Lead {
|
||||
convertedAt DateTime?
|
||||
archivedAt DateTime?
|
||||
events LeadEvent[]
|
||||
salesCopilotSessions SalesCopilotSession[]
|
||||
trackingLinks TrackingLink[]
|
||||
trackingEvents TrackingEvent[]
|
||||
routineTasks RoutineTask[]
|
||||
@@ -3337,3 +3349,71 @@ model GamificationBonus {
|
||||
|
||||
@@index([organizationId, isActive])
|
||||
}
|
||||
|
||||
/// ارزیابی رفتاری DISC؛ هر بار یک رکورد تازه است و تاریخچه پاک نمیشود.
|
||||
model DiscAssessment {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
personId String
|
||||
person Person @relation(fields: [personId], references: [id], onDelete: Cascade)
|
||||
assessorId String
|
||||
assessor User @relation("DiscAssessor", fields: [assessorId], references: [id], onDelete: Restrict)
|
||||
answers Json
|
||||
scoreD Int
|
||||
scoreI Int
|
||||
scoreS Int
|
||||
scoreC Int
|
||||
percentD Int
|
||||
percentI Int
|
||||
percentS Int
|
||||
percentC Int
|
||||
validCount Int
|
||||
primaryTypes String[] @default([])
|
||||
secondaryTypes String[] @default([])
|
||||
label String
|
||||
secondaryLabel String?
|
||||
reliable Boolean
|
||||
confidence String
|
||||
note String?
|
||||
status String @default("confirmed")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([organizationId, personId, createdAt])
|
||||
@@index([personId])
|
||||
}
|
||||
|
||||
/// نتیجهٔ تأییدشدهٔ دستیار فروش. پیشنهاد خام بدون تأیید کارشناس اینجا ذخیره نمیشود.
|
||||
model SalesCopilotSession {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
personId String?
|
||||
person Person? @relation(fields: [personId], references: [id], onDelete: Cascade)
|
||||
leadId String?
|
||||
lead Lead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
||||
dealId String?
|
||||
deal Deal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
|
||||
userId String
|
||||
user User @relation("CopilotAuthor", fields: [userId], references: [id], onDelete: Restrict)
|
||||
topic String
|
||||
goal String
|
||||
customerStatement String
|
||||
behaviorNotes String?
|
||||
analysis Json
|
||||
confirmedResponse String?
|
||||
nextAction String?
|
||||
followUpAt DateTime?
|
||||
callOutcome String?
|
||||
agentNote String?
|
||||
status String
|
||||
messageDraft String?
|
||||
activityId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([organizationId, personId, createdAt])
|
||||
@@index([organizationId, leadId])
|
||||
@@index([dealId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user