Initial Mento CRM: portal OTP, reports, SMS, and deploy for crm.montzar.ir.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 21:06:30 +03:30
co-authored by Cursor
commit 29a040c21d
195 changed files with 39430 additions and 0 deletions
@@ -0,0 +1,39 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('owner', 'admin', 'sales', 'support');
-- CreateTable
CREATE TABLE "Organization" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"role" "UserRole" NOT NULL DEFAULT 'sales',
"organizationId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Organization_slug_key" ON "Organization"("slug");
-- CreateIndex
CREATE INDEX "User_organizationId_idx" ON "User"("organizationId");
-- CreateIndex
CREATE UNIQUE INDEX "User_organizationId_email_key" ON "User"("organizationId", "email");
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,70 @@
-- CreateTable
CREATE TABLE "Company" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"nationalId" TEXT,
"phone" TEXT,
"email" TEXT,
"website" TEXT,
"address" TEXT,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Company_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Person" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"companyId" TEXT,
"firstName" TEXT NOT NULL,
"lastName" TEXT NOT NULL,
"email" TEXT,
"phone" TEXT,
"mobile" TEXT,
"position" TEXT,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Person_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Company_organizationId_idx" ON "Company"("organizationId");
-- CreateIndex
CREATE INDEX "Company_organizationId_name_idx" ON "Company"("organizationId", "name");
-- CreateIndex
CREATE INDEX "Company_organizationId_email_idx" ON "Company"("organizationId", "email");
-- CreateIndex
CREATE INDEX "Company_organizationId_phone_idx" ON "Company"("organizationId", "phone");
-- CreateIndex
CREATE INDEX "Person_organizationId_idx" ON "Person"("organizationId");
-- CreateIndex
CREATE INDEX "Person_organizationId_email_idx" ON "Person"("organizationId", "email");
-- CreateIndex
CREATE INDEX "Person_organizationId_mobile_idx" ON "Person"("organizationId", "mobile");
-- CreateIndex
CREATE INDEX "Person_organizationId_lastName_firstName_idx" ON "Person"("organizationId", "lastName", "firstName");
-- CreateIndex
CREATE INDEX "Person_companyId_idx" ON "Person"("companyId");
-- AddForeignKey
ALTER TABLE "Company" ADD CONSTRAINT "Company_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Person" ADD CONSTRAINT "Person_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Person" ADD CONSTRAINT "Person_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,103 @@
-- CreateEnum
CREATE TYPE "DealStatus" AS ENUM ('open', 'won', 'lost');
-- CreateTable
CREATE TABLE "Pipeline" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isDefault" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Pipeline_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PipelineStage" (
"id" TEXT NOT NULL,
"pipelineId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"position" INTEGER NOT NULL,
"isWon" BOOLEAN NOT NULL DEFAULT false,
"isLost" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "PipelineStage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Deal" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"pipelineId" TEXT NOT NULL,
"stageId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"amount" DECIMAL(18,2),
"currency" TEXT NOT NULL DEFAULT 'IRR',
"status" "DealStatus" NOT NULL DEFAULT 'open',
"source" TEXT,
"lostReason" TEXT,
"notes" TEXT,
"expectedCloseAt" TIMESTAMP(3),
"personId" TEXT,
"companyId" TEXT,
"ownerId" TEXT,
"position" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Deal_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Pipeline_organizationId_idx" ON "Pipeline"("organizationId");
-- CreateIndex
CREATE INDEX "Pipeline_organizationId_isDefault_idx" ON "Pipeline"("organizationId", "isDefault");
-- CreateIndex
CREATE INDEX "PipelineStage_pipelineId_position_idx" ON "PipelineStage"("pipelineId", "position");
-- CreateIndex
CREATE INDEX "Deal_organizationId_idx" ON "Deal"("organizationId");
-- CreateIndex
CREATE INDEX "Deal_pipelineId_stageId_idx" ON "Deal"("pipelineId", "stageId");
-- CreateIndex
CREATE INDEX "Deal_stageId_position_idx" ON "Deal"("stageId", "position");
-- CreateIndex
CREATE INDEX "Deal_personId_idx" ON "Deal"("personId");
-- CreateIndex
CREATE INDEX "Deal_companyId_idx" ON "Deal"("companyId");
-- CreateIndex
CREATE INDEX "Deal_ownerId_idx" ON "Deal"("ownerId");
-- AddForeignKey
ALTER TABLE "Pipeline" ADD CONSTRAINT "Pipeline_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "PipelineStage" ADD CONSTRAINT "PipelineStage_pipelineId_fkey" FOREIGN KEY ("pipelineId") REFERENCES "Pipeline"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_pipelineId_fkey" FOREIGN KEY ("pipelineId") REFERENCES "Pipeline"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_stageId_fkey" FOREIGN KEY ("stageId") REFERENCES "PipelineStage"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Deal" ADD CONSTRAINT "Deal_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,55 @@
-- CreateEnum
CREATE TYPE "ActivityType" AS ENUM ('call', 'meeting', 'task', 'note');
-- CreateEnum
CREATE TYPE "ActivityStatus" AS ENUM ('open', 'done', 'canceled');
-- CreateTable
CREATE TABLE "Activity" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"type" "ActivityType" NOT NULL DEFAULT 'task',
"status" "ActivityStatus" NOT NULL DEFAULT 'open',
"title" TEXT NOT NULL,
"description" TEXT,
"dueAt" TIMESTAMP(3),
"completedAt" TIMESTAMP(3),
"personId" TEXT,
"companyId" TEXT,
"dealId" TEXT,
"assigneeId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Activity_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Activity_organizationId_status_dueAt_idx" ON "Activity"("organizationId", "status", "dueAt");
-- CreateIndex
CREATE INDEX "Activity_organizationId_assigneeId_idx" ON "Activity"("organizationId", "assigneeId");
-- CreateIndex
CREATE INDEX "Activity_personId_idx" ON "Activity"("personId");
-- CreateIndex
CREATE INDEX "Activity_companyId_idx" ON "Activity"("companyId");
-- CreateIndex
CREATE INDEX "Activity_dealId_idx" ON "Activity"("dealId");
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Activity" ADD CONSTRAINT "Activity_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,52 @@
-- CreateTable
CREATE TABLE "Note" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"title" TEXT,
"body" TEXT NOT NULL,
"pinned" BOOLEAN NOT NULL DEFAULT false,
"color" TEXT DEFAULT 'default',
"tags" TEXT[] DEFAULT ARRAY[]::TEXT[],
"reminderAt" TIMESTAMP(3),
"personId" TEXT,
"companyId" TEXT,
"dealId" TEXT,
"authorId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Note_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Note_organizationId_pinned_updatedAt_idx" ON "Note"("organizationId", "pinned", "updatedAt");
-- CreateIndex
CREATE INDEX "Note_organizationId_reminderAt_idx" ON "Note"("organizationId", "reminderAt");
-- CreateIndex
CREATE INDEX "Note_personId_idx" ON "Note"("personId");
-- CreateIndex
CREATE INDEX "Note_companyId_idx" ON "Note"("companyId");
-- CreateIndex
CREATE INDEX "Note_dealId_idx" ON "Note"("dealId");
-- CreateIndex
CREATE INDEX "Note_authorId_idx" ON "Note"("authorId");
-- AddForeignKey
ALTER TABLE "Note" ADD CONSTRAINT "Note_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Note" ADD CONSTRAINT "Note_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Note" ADD CONSTRAINT "Note_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Note" ADD CONSTRAINT "Note_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Note" ADD CONSTRAINT "Note_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,41 @@
-- CreateEnum
CREATE TYPE "CustomFieldEntity" AS ENUM ('person', 'company', 'deal');
-- CreateEnum
CREATE TYPE "CustomFieldType" AS ENUM ('text', 'number', 'date', 'boolean', 'select');
-- AlterTable
ALTER TABLE "Company" ADD COLUMN "customFields" JSONB NOT NULL DEFAULT '{}';
-- AlterTable
ALTER TABLE "Deal" ADD COLUMN "customFields" JSONB NOT NULL DEFAULT '{}';
-- AlterTable
ALTER TABLE "Person" ADD COLUMN "customFields" JSONB NOT NULL DEFAULT '{}';
-- CreateTable
CREATE TABLE "CustomFieldDefinition" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"entityType" "CustomFieldEntity" NOT NULL,
"key" TEXT NOT NULL,
"label" TEXT NOT NULL,
"fieldType" "CustomFieldType" NOT NULL DEFAULT 'text',
"options" TEXT[] DEFAULT ARRAY[]::TEXT[],
"required" BOOLEAN NOT NULL DEFAULT false,
"position" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "CustomFieldDefinition_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "CustomFieldDefinition_organizationId_entityType_position_idx" ON "CustomFieldDefinition"("organizationId", "entityType", "position");
-- CreateIndex
CREATE UNIQUE INDEX "CustomFieldDefinition_organizationId_entityType_key_key" ON "CustomFieldDefinition"("organizationId", "entityType", "key");
-- AddForeignKey
ALTER TABLE "CustomFieldDefinition" ADD CONSTRAINT "CustomFieldDefinition_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "Deal" ADD COLUMN "isRotten" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "isStale" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "tags" TEXT[] DEFAULT ARRAY[]::TEXT[];
@@ -0,0 +1,52 @@
-- CreateTable
CREATE TABLE "Product" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"sku" TEXT,
"description" TEXT,
"unitPrice" DECIMAL(18,2),
"currency" TEXT NOT NULL DEFAULT 'IRR',
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DealLineItem" (
"id" TEXT NOT NULL,
"dealId" TEXT NOT NULL,
"productId" TEXT,
"name" TEXT NOT NULL,
"quantity" DECIMAL(18,2) NOT NULL DEFAULT 1,
"unitPrice" DECIMAL(18,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IRR',
"position" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DealLineItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Product_organizationId_name_idx" ON "Product"("organizationId", "name");
-- CreateIndex
CREATE INDEX "Product_organizationId_isActive_idx" ON "Product"("organizationId", "isActive");
-- CreateIndex
CREATE INDEX "DealLineItem_dealId_position_idx" ON "DealLineItem"("dealId", "position");
-- CreateIndex
CREATE INDEX "DealLineItem_productId_idx" ON "DealLineItem"("productId");
-- AddForeignKey
ALTER TABLE "Product" ADD CONSTRAINT "Product_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DealLineItem" ADD CONSTRAINT "DealLineItem_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DealLineItem" ADD CONSTRAINT "DealLineItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,31 @@
-- CreateTable
CREATE TABLE "Notification" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"title" TEXT NOT NULL,
"body" TEXT,
"link" TEXT,
"entityType" TEXT,
"entityId" TEXT,
"readAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Notification_userId_readAt_createdAt_idx" ON "Notification"("userId", "readAt", "createdAt");
-- CreateIndex
CREATE INDEX "Notification_organizationId_createdAt_idx" ON "Notification"("organizationId", "createdAt");
-- CreateIndex
CREATE INDEX "Notification_userId_type_entityId_idx" ON "Notification"("userId", "type", "entityId");
-- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Note" ADD COLUMN "noteType" TEXT NOT NULL DEFAULT 'general';
-- CreateIndex
CREATE INDEX "Note_organizationId_noteType_updatedAt_idx" ON "Note"("organizationId", "noteType", "updatedAt");
@@ -0,0 +1,32 @@
-- CreateTable
CREATE TABLE "NoteAttachment" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"noteId" TEXT NOT NULL,
"fileName" TEXT NOT NULL,
"mimeType" TEXT NOT NULL,
"sizeBytes" INTEGER NOT NULL,
"storageKey" TEXT NOT NULL,
"uploadedById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "NoteAttachment_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "NoteAttachment_noteId_createdAt_idx" ON "NoteAttachment"("noteId", "createdAt");
-- CreateIndex
CREATE INDEX "NoteAttachment_organizationId_idx" ON "NoteAttachment"("organizationId");
-- CreateIndex
CREATE INDEX "NoteAttachment_uploadedById_idx" ON "NoteAttachment"("uploadedById");
-- AddForeignKey
ALTER TABLE "NoteAttachment" ADD CONSTRAINT "NoteAttachment_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "NoteAttachment" ADD CONSTRAINT "NoteAttachment_noteId_fkey" FOREIGN KEY ("noteId") REFERENCES "Note"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "NoteAttachment" ADD CONSTRAINT "NoteAttachment_uploadedById_fkey" FOREIGN KEY ("uploadedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,52 @@
-- CreateTable
CREATE TABLE "LeadForm" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"publicToken" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createPerson" BOOLEAN NOT NULL DEFAULT true,
"createDeal" BOOLEAN NOT NULL DEFAULT true,
"pipelineId" TEXT,
"source" TEXT,
"thankYouMessage" TEXT,
"redirectUrl" TEXT,
"assigneeIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"roundRobinIndex" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "LeadForm_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LeadFormField" (
"id" TEXT NOT NULL,
"formId" TEXT NOT NULL,
"key" TEXT NOT NULL,
"label" TEXT NOT NULL,
"fieldType" TEXT NOT NULL DEFAULT 'text',
"required" BOOLEAN NOT NULL DEFAULT false,
"mapTo" TEXT NOT NULL,
"position" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "LeadFormField_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "LeadForm_publicToken_key" ON "LeadForm"("publicToken");
-- CreateIndex
CREATE INDEX "LeadForm_organizationId_updatedAt_idx" ON "LeadForm"("organizationId", "updatedAt");
-- CreateIndex
CREATE INDEX "LeadFormField_formId_position_idx" ON "LeadFormField"("formId", "position");
-- CreateIndex
CREATE UNIQUE INDEX "LeadFormField_formId_key_key" ON "LeadFormField"("formId", "key");
-- AddForeignKey
ALTER TABLE "LeadForm" ADD CONSTRAINT "LeadForm_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LeadFormField" ADD CONSTRAINT "LeadFormField_formId_fkey" FOREIGN KEY ("formId") REFERENCES "LeadForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,83 @@
-- CreateEnum
CREATE TYPE "QuoteStatus" AS ENUM ('draft', 'sent', 'accepted', 'rejected', 'expired');
-- CreateTable
CREATE TABLE "Quote" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"number" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" "QuoteStatus" NOT NULL DEFAULT 'draft',
"currency" TEXT NOT NULL DEFAULT 'IRR',
"notes" TEXT,
"validUntil" TIMESTAMP(3),
"personId" TEXT,
"companyId" TEXT,
"dealId" TEXT,
"ownerId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Quote_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "QuoteLineItem" (
"id" TEXT NOT NULL,
"quoteId" TEXT NOT NULL,
"productId" TEXT,
"name" TEXT NOT NULL,
"quantity" DECIMAL(18,2) NOT NULL DEFAULT 1,
"unitPrice" DECIMAL(18,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IRR',
"position" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "QuoteLineItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Quote_organizationId_status_updatedAt_idx" ON "Quote"("organizationId", "status", "updatedAt");
-- CreateIndex
CREATE INDEX "Quote_personId_idx" ON "Quote"("personId");
-- CreateIndex
CREATE INDEX "Quote_companyId_idx" ON "Quote"("companyId");
-- CreateIndex
CREATE INDEX "Quote_dealId_idx" ON "Quote"("dealId");
-- CreateIndex
CREATE INDEX "Quote_ownerId_idx" ON "Quote"("ownerId");
-- CreateIndex
CREATE UNIQUE INDEX "Quote_organizationId_number_key" ON "Quote"("organizationId", "number");
-- CreateIndex
CREATE INDEX "QuoteLineItem_quoteId_position_idx" ON "QuoteLineItem"("quoteId", "position");
-- CreateIndex
CREATE INDEX "QuoteLineItem_productId_idx" ON "QuoteLineItem"("productId");
-- AddForeignKey
ALTER TABLE "Quote" ADD CONSTRAINT "Quote_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Quote" ADD CONSTRAINT "Quote_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Quote" ADD CONSTRAINT "Quote_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Quote" ADD CONSTRAINT "Quote_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Quote" ADD CONSTRAINT "Quote_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "QuoteLineItem" ADD CONSTRAINT "QuoteLineItem_quoteId_fkey" FOREIGN KEY ("quoteId") REFERENCES "Quote"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "QuoteLineItem" ADD CONSTRAINT "QuoteLineItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,153 @@
-- CreateEnum
CREATE TYPE "InvoiceStatus" AS ENUM ('draft', 'issued', 'partially_paid', 'paid', 'canceled');
-- CreateEnum
CREATE TYPE "PaymentMethod" AS ENUM ('cash', 'transfer', 'card', 'check', 'other');
-- CreateEnum
CREATE TYPE "InstallmentStatus" AS ENUM ('pending', 'paid', 'canceled');
-- CreateTable
CREATE TABLE "Invoice" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"number" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" "InvoiceStatus" NOT NULL DEFAULT 'draft',
"currency" TEXT NOT NULL DEFAULT 'IRR',
"notes" TEXT,
"issuedAt" TIMESTAMP(3),
"dueDate" TIMESTAMP(3),
"personId" TEXT,
"companyId" TEXT,
"dealId" TEXT,
"quoteId" TEXT,
"ownerId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Invoice_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InvoiceLineItem" (
"id" TEXT NOT NULL,
"invoiceId" TEXT NOT NULL,
"productId" TEXT,
"name" TEXT NOT NULL,
"quantity" DECIMAL(18,2) NOT NULL DEFAULT 1,
"unitPrice" DECIMAL(18,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IRR',
"position" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "InvoiceLineItem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Payment" (
"id" TEXT NOT NULL,
"invoiceId" TEXT NOT NULL,
"amount" DECIMAL(18,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IRR',
"method" "PaymentMethod" NOT NULL DEFAULT 'transfer',
"paidAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"notes" TEXT,
"installmentId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "InvoiceInstallment" (
"id" TEXT NOT NULL,
"invoiceId" TEXT NOT NULL,
"position" INTEGER NOT NULL DEFAULT 0,
"dueDate" TIMESTAMP(3) NOT NULL,
"amount" DECIMAL(18,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IRR',
"status" "InstallmentStatus" NOT NULL DEFAULT 'pending',
"paidAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "InvoiceInstallment_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Invoice_organizationId_status_updatedAt_idx" ON "Invoice"("organizationId", "status", "updatedAt");
-- CreateIndex
CREATE INDEX "Invoice_personId_idx" ON "Invoice"("personId");
-- CreateIndex
CREATE INDEX "Invoice_companyId_idx" ON "Invoice"("companyId");
-- CreateIndex
CREATE INDEX "Invoice_dealId_idx" ON "Invoice"("dealId");
-- CreateIndex
CREATE INDEX "Invoice_quoteId_idx" ON "Invoice"("quoteId");
-- CreateIndex
CREATE INDEX "Invoice_ownerId_idx" ON "Invoice"("ownerId");
-- CreateIndex
CREATE UNIQUE INDEX "Invoice_organizationId_number_key" ON "Invoice"("organizationId", "number");
-- CreateIndex
CREATE INDEX "InvoiceLineItem_invoiceId_position_idx" ON "InvoiceLineItem"("invoiceId", "position");
-- CreateIndex
CREATE INDEX "InvoiceLineItem_productId_idx" ON "InvoiceLineItem"("productId");
-- CreateIndex
CREATE UNIQUE INDEX "Payment_installmentId_key" ON "Payment"("installmentId");
-- CreateIndex
CREATE INDEX "Payment_invoiceId_paidAt_idx" ON "Payment"("invoiceId", "paidAt");
-- CreateIndex
CREATE INDEX "InvoiceInstallment_invoiceId_position_idx" ON "InvoiceInstallment"("invoiceId", "position");
-- CreateIndex
CREATE INDEX "InvoiceInstallment_invoiceId_dueDate_idx" ON "InvoiceInstallment"("invoiceId", "dueDate");
-- CreateIndex
CREATE INDEX "InvoiceInstallment_status_dueDate_idx" ON "InvoiceInstallment"("status", "dueDate");
-- AddForeignKey
ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_quoteId_fkey" FOREIGN KEY ("quoteId") REFERENCES "Quote"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invoice" ADD CONSTRAINT "Invoice_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InvoiceLineItem" ADD CONSTRAINT "InvoiceLineItem_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InvoiceLineItem" ADD CONSTRAINT "InvoiceLineItem_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_installmentId_fkey" FOREIGN KEY ("installmentId") REFERENCES "InvoiceInstallment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "InvoiceInstallment" ADD CONSTRAINT "InvoiceInstallment_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,17 @@
-- AlterTable
ALTER TABLE "Company" ADD COLUMN "ownerId" TEXT;
-- AlterTable
ALTER TABLE "Person" ADD COLUMN "ownerId" TEXT;
-- CreateIndex
CREATE INDEX "Company_ownerId_idx" ON "Company"("ownerId");
-- CreateIndex
CREATE INDEX "Person_ownerId_idx" ON "Person"("ownerId");
-- AddForeignKey
ALTER TABLE "Company" ADD CONSTRAINT "Company_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Person" ADD CONSTRAINT "Person_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,28 @@
-- CreateTable
CREATE TABLE "SalesGoal" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"userId" TEXT,
"title" TEXT NOT NULL,
"periodStart" TIMESTAMP(3) NOT NULL,
"periodEnd" TIMESTAMP(3) NOT NULL,
"targetAmount" DECIMAL(18,2) NOT NULL,
"currency" TEXT NOT NULL DEFAULT 'IRR',
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SalesGoal_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "SalesGoal_organizationId_periodStart_periodEnd_idx" ON "SalesGoal"("organizationId", "periodStart", "periodEnd");
-- CreateIndex
CREATE INDEX "SalesGoal_userId_idx" ON "SalesGoal"("userId");
-- AddForeignKey
ALTER TABLE "SalesGoal" ADD CONSTRAINT "SalesGoal_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "SalesGoal" ADD CONSTRAINT "SalesGoal_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,24 @@
-- AlterTable
ALTER TABLE "Organization" ADD COLUMN "baseCurrency" TEXT NOT NULL DEFAULT 'IRR',
ADD COLUMN "enabledCurrencies" TEXT[] DEFAULT ARRAY['IRR', 'USD', 'EUR']::TEXT[];
-- CreateTable
CREATE TABLE "ExchangeRate" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"currency" TEXT NOT NULL,
"rateToBase" DECIMAL(24,6) NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ExchangeRate_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "ExchangeRate_organizationId_idx" ON "ExchangeRate"("organizationId");
-- CreateIndex
CREATE UNIQUE INDEX "ExchangeRate_organizationId_currency_key" ON "ExchangeRate"("organizationId", "currency");
-- AddForeignKey
ALTER TABLE "ExchangeRate" ADD CONSTRAINT "ExchangeRate_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,89 @@
-- CreateEnum
CREATE TYPE "TicketStatus" AS ENUM ('open', 'in_progress', 'waiting', 'resolved', 'closed');
-- CreateEnum
CREATE TYPE "TicketPriority" AS ENUM ('low', 'normal', 'high', 'urgent');
-- CreateTable
CREATE TABLE "Ticket" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"number" TEXT NOT NULL,
"subject" TEXT NOT NULL,
"description" TEXT,
"status" "TicketStatus" NOT NULL DEFAULT 'open',
"priority" "TicketPriority" NOT NULL DEFAULT 'normal',
"personId" TEXT,
"companyId" TEXT,
"dealId" TEXT,
"assigneeId" TEXT,
"reporterId" TEXT,
"resolvedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TicketMessage" (
"id" TEXT NOT NULL,
"ticketId" TEXT NOT NULL,
"authorId" TEXT,
"body" TEXT NOT NULL,
"isInternal" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "TicketMessage_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "Ticket_organizationId_status_updatedAt_idx" ON "Ticket"("organizationId", "status", "updatedAt");
-- CreateIndex
CREATE INDEX "Ticket_assigneeId_idx" ON "Ticket"("assigneeId");
-- CreateIndex
CREATE INDEX "Ticket_reporterId_idx" ON "Ticket"("reporterId");
-- CreateIndex
CREATE INDEX "Ticket_personId_idx" ON "Ticket"("personId");
-- CreateIndex
CREATE INDEX "Ticket_companyId_idx" ON "Ticket"("companyId");
-- CreateIndex
CREATE INDEX "Ticket_dealId_idx" ON "Ticket"("dealId");
-- CreateIndex
CREATE UNIQUE INDEX "Ticket_organizationId_number_key" ON "Ticket"("organizationId", "number");
-- CreateIndex
CREATE INDEX "TicketMessage_ticketId_createdAt_idx" ON "TicketMessage"("ticketId", "createdAt");
-- CreateIndex
CREATE INDEX "TicketMessage_authorId_idx" ON "TicketMessage"("authorId");
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_reporterId_fkey" FOREIGN KEY ("reporterId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TicketMessage" ADD CONSTRAINT "TicketMessage_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TicketMessage" ADD CONSTRAINT "TicketMessage_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,30 @@
-- AlterTable
ALTER TABLE "Ticket" ADD COLUMN "csatAt" TIMESTAMP(3),
ADD COLUMN "csatComment" TEXT,
ADD COLUMN "csatScore" INTEGER,
ADD COLUMN "firstRespondedAt" TIMESTAMP(3),
ADD COLUMN "slaDueAt" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "FaqArticle" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"question" TEXT NOT NULL,
"answer" TEXT NOT NULL,
"category" TEXT,
"isPublished" BOOLEAN NOT NULL DEFAULT true,
"position" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FaqArticle_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "FaqArticle_organizationId_isPublished_position_idx" ON "FaqArticle"("organizationId", "isPublished", "position");
-- CreateIndex
CREATE INDEX "Ticket_organizationId_slaDueAt_idx" ON "Ticket"("organizationId", "slaDueAt");
-- AddForeignKey
ALTER TABLE "FaqArticle" ADD CONSTRAINT "FaqArticle_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,21 @@
-- CreateTable
CREATE TABLE "PortalOtp" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"mobile" TEXT NOT NULL,
"code" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"consumedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PortalOtp_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "PortalOtp_organizationId_mobile_createdAt_idx" ON "PortalOtp"("organizationId", "mobile", "createdAt");
-- CreateIndex
CREATE INDEX "PortalOtp_expiresAt_idx" ON "PortalOtp"("expiresAt");
-- AddForeignKey
ALTER TABLE "PortalOtp" ADD CONSTRAINT "PortalOtp_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
+738
View File
@@ -0,0 +1,738 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
owner
admin
sales
support
}
enum DealStatus {
open
won
lost
}
enum ActivityType {
call
meeting
task
note
}
enum ActivityStatus {
open
done
canceled
}
enum CustomFieldEntity {
person
company
deal
}
enum CustomFieldType {
text
number
date
boolean
select
}
enum QuoteStatus {
draft
sent
accepted
rejected
expired
}
enum InvoiceStatus {
draft
issued
partially_paid
paid
canceled
}
enum PaymentMethod {
cash
transfer
card
check
other
}
enum InstallmentStatus {
pending
paid
canceled
}
enum TicketStatus {
open
in_progress
waiting
resolved
closed
}
enum TicketPriority {
low
normal
high
urgent
}
model Organization {
id String @id @default(cuid())
name String
slug String @unique
baseCurrency String @default("IRR")
enabledCurrencies String[] @default(["IRR", "USD", "EUR"])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
companies Company[]
people Person[]
pipelines Pipeline[]
deals Deal[]
activities Activity[]
notes Note[]
customFields CustomFieldDefinition[]
products Product[]
notifications Notification[]
noteAttachments NoteAttachment[]
leadForms LeadForm[]
quotes Quote[]
invoices Invoice[]
salesGoals SalesGoal[]
exchangeRates ExchangeRate[]
tickets Ticket[]
faqArticles FaqArticle[]
portalOtps PortalOtp[]
}
model User {
id String @id @default(cuid())
email String
passwordHash String
fullName String
role UserRole @default(sales)
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
ownedDeals Deal[] @relation("DealOwner")
ownedQuotes Quote[] @relation("QuoteOwner")
ownedInvoices Invoice[] @relation("InvoiceOwner")
ownedPeople Person[] @relation("PersonOwner")
ownedCompanies Company[] @relation("CompanyOwner")
activities Activity[] @relation("ActivityAssignee")
notes Note[] @relation("NoteAuthor")
noteAttachments NoteAttachment[] @relation("NoteAttachmentUploader")
notifications Notification[]
salesGoals SalesGoal[] @relation("SalesGoalUser")
assignedTickets Ticket[] @relation("TicketAssignee")
reportedTickets Ticket[] @relation("TicketReporter")
ticketMessages TicketMessage[] @relation("TicketMessageAuthor")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, email])
@@index([organizationId])
}
model Company {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
nationalId String?
phone String?
email String?
website String?
address String?
notes String?
customFields Json @default("{}")
people Person[]
deals Deal[]
activities Activity[]
smartNotes Note[]
quotes Quote[]
invoices Invoice[]
ownerId String?
owner User? @relation("CompanyOwner", fields: [ownerId], references: [id], onDelete: SetNull)
tickets Ticket[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId])
@@index([organizationId, name])
@@index([organizationId, email])
@@index([organizationId, phone])
@@index([ownerId])
}
model Person {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
firstName String
lastName String
email String?
phone String?
mobile String?
position String?
notes String?
customFields Json @default("{}")
deals Deal[]
activities Activity[]
smartNotes Note[]
quotes Quote[]
invoices Invoice[]
ownerId String?
owner User? @relation("PersonOwner", fields: [ownerId], references: [id], onDelete: SetNull)
tickets Ticket[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId])
@@index([organizationId, email])
@@index([organizationId, mobile])
@@index([organizationId, lastName, firstName])
@@index([companyId])
@@index([ownerId])
}
model Pipeline {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
isDefault Boolean @default(false)
stages PipelineStage[]
deals Deal[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId])
@@index([organizationId, isDefault])
}
model PipelineStage {
id String @id @default(cuid())
pipelineId String
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
name String
position Int
isWon Boolean @default(false)
isLost Boolean @default(false)
deals Deal[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([pipelineId, position])
}
model Deal {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
pipelineId String
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
stageId String
stage PipelineStage @relation(fields: [stageId], references: [id], onDelete: Restrict)
title String
amount Decimal? @db.Decimal(18, 2)
currency String @default("IRR")
status DealStatus @default(open)
source String?
lostReason String?
notes String?
tags String[] @default([])
isStale Boolean @default(false)
isRotten Boolean @default(false)
customFields Json @default("{}")
expectedCloseAt DateTime?
personId String?
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
ownerId String?
owner User? @relation("DealOwner", fields: [ownerId], references: [id], onDelete: SetNull)
position Int @default(0)
activities Activity[]
smartNotes Note[]
lineItems DealLineItem[]
quotes Quote[]
invoices Invoice[]
tickets Ticket[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId])
@@index([pipelineId, stageId])
@@index([stageId, position])
@@index([personId])
@@index([companyId])
@@index([ownerId])
}
model Activity {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
type ActivityType @default(task)
status ActivityStatus @default(open)
title String
description String?
dueAt DateTime?
completedAt DateTime?
personId String?
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
dealId String?
deal Deal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
assigneeId String?
assignee User? @relation("ActivityAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, status, dueAt])
@@index([organizationId, assigneeId])
@@index([personId])
@@index([companyId])
@@index([dealId])
}
model Note {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
title String?
body String
noteType String @default("general")
pinned Boolean @default(false)
color String? @default("default")
tags String[] @default([])
reminderAt DateTime?
personId String?
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
dealId String?
deal Deal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
authorId String?
author User? @relation("NoteAuthor", fields: [authorId], references: [id], onDelete: SetNull)
attachments NoteAttachment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, pinned, updatedAt])
@@index([organizationId, noteType, updatedAt])
@@index([organizationId, reminderAt])
@@index([personId])
@@index([companyId])
@@index([dealId])
@@index([authorId])
}
model NoteAttachment {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
noteId String
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
fileName String
mimeType String
sizeBytes Int
storageKey String
uploadedById String?
uploadedBy User? @relation("NoteAttachmentUploader", fields: [uploadedById], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@index([noteId, createdAt])
@@index([organizationId])
@@index([uploadedById])
}
model CustomFieldDefinition {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
entityType CustomFieldEntity
key String
label String
fieldType CustomFieldType @default(text)
options String[] @default([])
required Boolean @default(false)
position Int @default(0)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, entityType, key])
@@index([organizationId, entityType, position])
}
model Product {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
sku String?
description String?
unitPrice Decimal? @db.Decimal(18, 2)
currency String @default("IRR")
isActive Boolean @default(true)
lineItems DealLineItem[]
quoteLineItems QuoteLineItem[]
invoiceLineItems InvoiceLineItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, name])
@@index([organizationId, isActive])
}
model DealLineItem {
id String @id @default(cuid())
dealId String
deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade)
productId String?
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
name String
quantity Decimal @default(1) @db.Decimal(18, 2)
unitPrice Decimal @db.Decimal(18, 2)
currency String @default("IRR")
position Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([dealId, position])
@@index([productId])
}
model Notification {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type String
title String
body String?
link String?
entityType String?
entityId String?
readAt DateTime?
createdAt DateTime @default(now())
@@index([userId, readAt, createdAt])
@@index([organizationId, createdAt])
@@index([userId, type, entityId])
}
model LeadForm {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
publicToken String @unique @default(cuid())
isActive Boolean @default(true)
createPerson Boolean @default(true)
createDeal Boolean @default(true)
pipelineId String?
source String?
thankYouMessage String?
redirectUrl String?
assigneeIds String[] @default([])
roundRobinIndex Int @default(0)
fields LeadFormField[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, updatedAt])
}
model LeadFormField {
id String @id @default(cuid())
formId String
form LeadForm @relation(fields: [formId], references: [id], onDelete: Cascade)
key String
label String
fieldType String @default("text")
required Boolean @default(false)
mapTo String
position Int @default(0)
@@unique([formId, key])
@@index([formId, position])
}
model Quote {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
number String
title String
status QuoteStatus @default(draft)
currency String @default("IRR")
notes String?
validUntil DateTime?
personId String?
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
dealId String?
deal Deal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
ownerId String?
owner User? @relation("QuoteOwner", fields: [ownerId], references: [id], onDelete: SetNull)
lineItems QuoteLineItem[]
invoices Invoice[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, number])
@@index([organizationId, status, updatedAt])
@@index([personId])
@@index([companyId])
@@index([dealId])
@@index([ownerId])
}
model QuoteLineItem {
id String @id @default(cuid())
quoteId String
quote Quote @relation(fields: [quoteId], references: [id], onDelete: Cascade)
productId String?
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
name String
quantity Decimal @default(1) @db.Decimal(18, 2)
unitPrice Decimal @db.Decimal(18, 2)
currency String @default("IRR")
position Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([quoteId, position])
@@index([productId])
}
model Invoice {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
number String
title String
status InvoiceStatus @default(draft)
currency String @default("IRR")
notes String?
issuedAt DateTime?
dueDate DateTime?
personId String?
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
dealId String?
deal Deal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
quoteId String?
quote Quote? @relation(fields: [quoteId], references: [id], onDelete: SetNull)
ownerId String?
owner User? @relation("InvoiceOwner", fields: [ownerId], references: [id], onDelete: SetNull)
lineItems InvoiceLineItem[]
payments Payment[]
installments InvoiceInstallment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, number])
@@index([organizationId, status, updatedAt])
@@index([personId])
@@index([companyId])
@@index([dealId])
@@index([quoteId])
@@index([ownerId])
}
model InvoiceLineItem {
id String @id @default(cuid())
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
productId String?
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
name String
quantity Decimal @default(1) @db.Decimal(18, 2)
unitPrice Decimal @db.Decimal(18, 2)
currency String @default("IRR")
position Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([invoiceId, position])
@@index([productId])
}
model Payment {
id String @id @default(cuid())
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
amount Decimal @db.Decimal(18, 2)
currency String @default("IRR")
method PaymentMethod @default(transfer)
paidAt DateTime @default(now())
notes String?
installmentId String? @unique
installment InvoiceInstallment? @relation(fields: [installmentId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([invoiceId, paidAt])
}
model InvoiceInstallment {
id String @id @default(cuid())
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
position Int @default(0)
dueDate DateTime
amount Decimal @db.Decimal(18, 2)
currency String @default("IRR")
status InstallmentStatus @default(pending)
paidAt DateTime?
payment Payment?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([invoiceId, position])
@@index([invoiceId, dueDate])
@@index([status, dueDate])
}
model SalesGoal {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
/// null = هدف سازمانی / تیمی
userId String?
user User? @relation("SalesGoalUser", fields: [userId], references: [id], onDelete: Cascade)
title String
periodStart DateTime
periodEnd DateTime
targetAmount Decimal @db.Decimal(18, 2)
currency String @default("IRR")
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, periodStart, periodEnd])
@@index([userId])
}
model ExchangeRate {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
/// ارز مبدأ؛ ۱ واحد این ارز = rateToBase واحد ارز پایه
currency String
rateToBase Decimal @db.Decimal(24, 6)
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
@@unique([organizationId, currency])
@@index([organizationId])
}
model Ticket {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
number String
subject String
description String?
status TicketStatus @default(open)
priority TicketPriority @default(normal)
personId String?
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
companyId String?
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
dealId String?
deal Deal? @relation(fields: [dealId], references: [id], onDelete: SetNull)
assigneeId String?
assignee User? @relation("TicketAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
reporterId String?
reporter User? @relation("TicketReporter", fields: [reporterId], references: [id], onDelete: SetNull)
resolvedAt DateTime?
/// مهلت پاسخ اول بر اساس اولویت
slaDueAt DateTime?
firstRespondedAt DateTime?
csatScore Int?
csatComment String?
csatAt DateTime?
messages TicketMessage[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, number])
@@index([organizationId, status, updatedAt])
@@index([organizationId, slaDueAt])
@@index([assigneeId])
@@index([reporterId])
@@index([personId])
@@index([companyId])
@@index([dealId])
}
model TicketMessage {
id String @id @default(cuid())
ticketId String
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
authorId String?
author User? @relation("TicketMessageAuthor", fields: [authorId], references: [id], onDelete: SetNull)
body String
isInternal Boolean @default(false)
createdAt DateTime @default(now())
@@index([ticketId, createdAt])
@@index([authorId])
}
model FaqArticle {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
question String
answer String
category String?
isPublished Boolean @default(true)
position Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, isPublished, position])
}
/// کد یک‌بارمصرف ورود پرتال مشتری (موبایل)
model PortalOtp {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
mobile String
code String
expiresAt DateTime
consumedAt DateTime?
createdAt DateTime @default(now())
@@index([organizationId, mobile, createdAt])
@@index([expiresAt])
}
File diff suppressed because it is too large Load Diff