feat: routines module; super admin subscription badge, 7-day trial and lifetime plan

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-06 19:54:38 +03:30
co-authored by Cursor
parent 3de2d0fa67
commit b3784818fc
58 changed files with 13018 additions and 18 deletions
@@ -0,0 +1,247 @@
-- CreateEnum
CREATE TYPE "RoutineTaskStatus" AS ENUM ('todo', 'in_progress', 'completed', 'overdue', 'skipped', 'cancelled');
-- CreateEnum
CREATE TYPE "RoutineRecurrence" AS ENUM ('daily', 'weekdays', 'weekly', 'biweekly', 'monthly', 'every_n_days', 'specific_dates');
-- CreateEnum
CREATE TYPE "RoutineTaskSource" AS ENUM ('routine', 'manual', 'follow_up', 'lead', 'customer', 'deal');
-- CreateTable
CREATE TABLE "RoutineCategory" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"color" TEXT NOT NULL DEFAULT '#14b8a6',
"sortOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "RoutineCategory_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Routine" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"assigneeId" TEXT,
"teamId" TEXT,
"categoryId" TEXT,
"priority" "ActivityPriority" NOT NULL DEFAULT 'medium',
"timeOfDay" TEXT NOT NULL DEFAULT '09:00',
"durationMinutes" INTEGER,
"startDate" DATE NOT NULL,
"endDate" DATE,
"recurrence" "RoutineRecurrence" NOT NULL DEFAULT 'daily',
"weekdays" INTEGER[] DEFAULT ARRAY[]::INTEGER[],
"intervalDays" INTEGER,
"monthDay" INTEGER,
"specificDates" DATE[],
"isMandatory" BOOLEAN NOT NULL DEFAULT true,
"allowSkip" BOOLEAN NOT NULL DEFAULT false,
"skipReasonRequired" BOOLEAN NOT NULL DEFAULT true,
"reportRequired" BOOLEAN NOT NULL DEFAULT false,
"allowAfterDeadline" BOOLEAN NOT NULL DEFAULT true,
"carryOverOverdue" BOOLEAN NOT NULL DEFAULT false,
"entityLink" TEXT NOT NULL DEFAULT 'none',
"reminderMinutesBefore" INTEGER,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdById" TEXT,
"deletedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Routine_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RoutineTask" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"routineId" TEXT,
"assigneeId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"categoryId" TEXT,
"priority" "ActivityPriority" NOT NULL DEFAULT 'medium',
"status" "RoutineTaskStatus" NOT NULL DEFAULT 'todo',
"scheduledFor" DATE NOT NULL,
"dueAt" TIMESTAMP(3),
"startedAt" TIMESTAMP(3),
"completedAt" TIMESTAMP(3),
"resultNote" TEXT,
"skipReason" TEXT,
"source" "RoutineTaskSource" NOT NULL DEFAULT 'manual',
"isMandatory" BOOLEAN NOT NULL DEFAULT false,
"leadId" TEXT,
"personId" TEXT,
"companyId" TEXT,
"dealId" TEXT,
"invoiceId" TEXT,
"activityId" TEXT,
"reminderAt" TIMESTAMP(3),
"reminderSentAt" TIMESTAMP(3),
"overdueNotifiedAt" TIMESTAMP(3),
"createdById" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "RoutineTask_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RoutineTaskEvent" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"taskId" TEXT NOT NULL,
"actorId" TEXT,
"type" TEXT NOT NULL,
"fromStatus" "RoutineTaskStatus",
"toStatus" "RoutineTaskStatus",
"note" TEXT,
"meta" JSONB NOT NULL DEFAULT '{}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "RoutineTaskEvent_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "RoutineUserSettings" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"remindBeforeMinutes" INTEGER DEFAULT 30,
"endOfDaySummary" BOOLEAN NOT NULL DEFAULT true,
"overdueAlert" BOOLEAN NOT NULL DEFAULT true,
"summaryHour" INTEGER NOT NULL DEFAULT 16,
"lastSummaryOn" DATE,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "RoutineUserSettings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "RoutineCategory_organizationId_sortOrder_idx" ON "RoutineCategory"("organizationId", "sortOrder");
-- CreateIndex
CREATE UNIQUE INDEX "RoutineCategory_organizationId_name_key" ON "RoutineCategory"("organizationId", "name");
-- CreateIndex
CREATE INDEX "Routine_organizationId_isActive_deletedAt_idx" ON "Routine"("organizationId", "isActive", "deletedAt");
-- CreateIndex
CREATE INDEX "Routine_assigneeId_idx" ON "Routine"("assigneeId");
-- CreateIndex
CREATE INDEX "Routine_teamId_idx" ON "Routine"("teamId");
-- CreateIndex
CREATE INDEX "RoutineTask_organizationId_assigneeId_scheduledFor_idx" ON "RoutineTask"("organizationId", "assigneeId", "scheduledFor");
-- CreateIndex
CREATE INDEX "RoutineTask_organizationId_status_dueAt_idx" ON "RoutineTask"("organizationId", "status", "dueAt");
-- CreateIndex
CREATE INDEX "RoutineTask_reminderAt_reminderSentAt_idx" ON "RoutineTask"("reminderAt", "reminderSentAt");
-- CreateIndex
CREATE INDEX "RoutineTask_leadId_idx" ON "RoutineTask"("leadId");
-- CreateIndex
CREATE INDEX "RoutineTask_personId_idx" ON "RoutineTask"("personId");
-- CreateIndex
CREATE INDEX "RoutineTask_companyId_idx" ON "RoutineTask"("companyId");
-- CreateIndex
CREATE INDEX "RoutineTask_dealId_idx" ON "RoutineTask"("dealId");
-- CreateIndex
CREATE INDEX "RoutineTask_activityId_idx" ON "RoutineTask"("activityId");
-- CreateIndex
CREATE UNIQUE INDEX "RoutineTask_routineId_assigneeId_scheduledFor_key" ON "RoutineTask"("routineId", "assigneeId", "scheduledFor");
-- CreateIndex
CREATE INDEX "RoutineTaskEvent_taskId_createdAt_idx" ON "RoutineTaskEvent"("taskId", "createdAt");
-- CreateIndex
CREATE INDEX "RoutineTaskEvent_organizationId_createdAt_idx" ON "RoutineTaskEvent"("organizationId", "createdAt");
-- CreateIndex
CREATE UNIQUE INDEX "RoutineUserSettings_organizationId_userId_key" ON "RoutineUserSettings"("organizationId", "userId");
-- AddForeignKey
ALTER TABLE "RoutineCategory" ADD CONSTRAINT "RoutineCategory_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Routine" ADD CONSTRAINT "Routine_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Routine" ADD CONSTRAINT "Routine_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Routine" ADD CONSTRAINT "Routine_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Routine" ADD CONSTRAINT "Routine_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "RoutineCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Routine" ADD CONSTRAINT "Routine_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_routineId_fkey" FOREIGN KEY ("routineId") REFERENCES "Routine"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "RoutineCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_leadId_fkey" FOREIGN KEY ("leadId") REFERENCES "Lead"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_dealId_fkey" FOREIGN KEY ("dealId") REFERENCES "Deal"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTask" ADD CONSTRAINT "RoutineTask_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTaskEvent" ADD CONSTRAINT "RoutineTaskEvent_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTaskEvent" ADD CONSTRAINT "RoutineTaskEvent_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "RoutineTask"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineTaskEvent" ADD CONSTRAINT "RoutineTaskEvent_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineUserSettings" ADD CONSTRAINT "RoutineUserSettings_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "RoutineUserSettings" ADD CONSTRAINT "RoutineUserSettings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Enable the new routines module for organizations with an explicit module list
UPDATE "Organization"
SET "enabledModules" = array_append("enabledModules", 'routines')
WHERE cardinality("enabledModules") > 0 AND NOT ('routines' = ANY("enabledModules"));
+207 -1
View File
@@ -218,6 +218,11 @@ model Organization {
conversationMessageReactions ConversationMessageReaction[]
trackingLinks TrackingLink[]
trackingEvents TrackingEvent[]
routineCategories RoutineCategory[]
routines Routine[]
routineTasks RoutineTask[]
routineTaskEvents RoutineTaskEvent[]
routineUserSettings RoutineUserSettings[]
}
model User {
@@ -271,6 +276,12 @@ model User {
reservationAssignments ReservationAppointment[] @relation("ReservationAssignee")
assignedLeads Lead[] @relation("LeadAssignee")
createdLeads Lead[] @relation("LeadCreator")
assignedRoutines Routine[] @relation("RoutineAssignee")
createdRoutines Routine[] @relation("RoutineCreator")
routineTasks RoutineTask[] @relation("RoutineTaskAssignee")
createdRoutineTasks RoutineTask[] @relation("RoutineTaskCreator")
routineTaskEvents RoutineTaskEvent[]
routineSettings RoutineUserSettings[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -338,6 +349,7 @@ model Team {
name String
description String @default("")
members TeamMember[]
routines Routine[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -379,7 +391,7 @@ model BillingAccount {
id String @id @default(cuid())
ownerUserId String @unique
owner User @relation(fields: [ownerUserId], references: [id], onDelete: Cascade)
/// trial | monthly | quarterly | semiannual | yearly | custom
/// trial | monthly | quarterly | semiannual | yearly | lifetime | custom
planCode String @default("trial")
/// active | expired | suspended
status String @default("active")
@@ -435,6 +447,7 @@ model Company {
owner User? @relation("CompanyOwner", fields: [ownerId], references: [id], onDelete: SetNull)
tickets Ticket[]
projectCards ProjectCard[]
routineTasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -503,6 +516,7 @@ model Person {
cheques Cheque[]
trackingLinks TrackingLink[]
trackingEvents TrackingEvent[]
routineTasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -601,6 +615,7 @@ model Deal {
invoices Invoice[]
tickets Ticket[]
projectCards ProjectCard[]
routineTasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -646,6 +661,7 @@ model Activity {
createdById String?
createdBy User? @relation("ActivityCreator", fields: [createdById], references: [id], onDelete: SetNull)
notes Note[]
routineTasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -1023,6 +1039,7 @@ model Invoice {
commissionFixedAmount Decimal? @db.Decimal(18, 2)
commissionEntry CommissionEntry?
cheques Cheque[]
routineTasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -2176,6 +2193,7 @@ model Lead {
events LeadEvent[]
trackingLinks TrackingLink[]
trackingEvents TrackingEvent[]
routineTasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -2361,3 +2379,191 @@ model ConversationMessageReaction {
@@unique([organizationId, externalRoomId, messageId, userId, emoji])
@@index([organizationId, externalRoomId])
}
enum RoutineTaskStatus {
todo
in_progress
completed
overdue
skipped
cancelled
}
enum RoutineRecurrence {
daily
weekdays
weekly
biweekly
monthly
every_n_days
specific_dates
}
enum RoutineTaskSource {
routine
manual
follow_up
lead
customer
deal
}
/// دسته‌بندی کارهای روتین (فروش، پیگیری، لید، …)
model RoutineCategory {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
color String @default("#14b8a6")
sortOrder Int @default(0)
routines Routine[]
tasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, name])
@@index([organizationId, sortOrder])
}
/// الگوی کار تکرارشونده؛ هر روز Task مستقل (RoutineTask) تولید می‌کند
model Routine {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
title String
description String?
assigneeId String?
assignee User? @relation("RoutineAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
/// اگر ست باشد برای هر عضو تیم یک Task جدا ساخته می‌شود
teamId String?
team Team? @relation(fields: [teamId], references: [id], onDelete: SetNull)
categoryId String?
category RoutineCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
priority ActivityPriority @default(medium)
/// HH:mm به وقت تهران
timeOfDay String @default("09:00")
durationMinutes Int?
startDate DateTime @db.Date
endDate DateTime? @db.Date
recurrence RoutineRecurrence @default(daily)
/// ۰ = شنبه … ۶ = جمعه
weekdays Int[] @default([])
intervalDays Int?
monthDay Int?
specificDates DateTime[] @db.Date
isMandatory Boolean @default(true)
allowSkip Boolean @default(false)
skipReasonRequired Boolean @default(true)
reportRequired Boolean @default(false)
allowAfterDeadline Boolean @default(true)
carryOverOverdue Boolean @default(false)
/// none | optional | lead | customer
entityLink String @default("none")
reminderMinutesBefore Int?
isActive Boolean @default(true)
createdById String?
createdBy User? @relation("RoutineCreator", fields: [createdById], references: [id], onDelete: SetNull)
deletedAt DateTime?
tasks RoutineTask[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([organizationId, isActive, deletedAt])
@@index([assigneeId])
@@index([teamId])
}
/// نمونه روزانه یک کار (از روتین، دستی، پیگیری، لید یا مشتری)
model RoutineTask {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
routineId String?
routine Routine? @relation(fields: [routineId], references: [id], onDelete: SetNull)
assigneeId String
assignee User @relation("RoutineTaskAssignee", fields: [assigneeId], references: [id], onDelete: Cascade)
title String
description String?
categoryId String?
category RoutineCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
priority ActivityPriority @default(medium)
status RoutineTaskStatus @default(todo)
/// روز برنامه به وقت تهران
scheduledFor DateTime @db.Date
dueAt DateTime?
startedAt DateTime?
completedAt DateTime?
resultNote String?
skipReason String?
source RoutineTaskSource @default(manual)
isMandatory Boolean @default(false)
leadId String?
lead Lead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
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)
invoiceId String?
invoice Invoice? @relation(fields: [invoiceId], references: [id], onDelete: SetNull)
activityId String?
activity Activity? @relation(fields: [activityId], references: [id], onDelete: SetNull)
reminderAt DateTime?
reminderSentAt DateTime?
overdueNotifiedAt DateTime?
createdById String?
createdBy User? @relation("RoutineTaskCreator", fields: [createdById], references: [id], onDelete: SetNull)
events RoutineTaskEvent[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([routineId, assigneeId, scheduledFor])
@@index([organizationId, assigneeId, scheduledFor])
@@index([organizationId, status, dueAt])
@@index([reminderAt, reminderSentAt])
@@index([leadId])
@@index([personId])
@@index([companyId])
@@index([dealId])
@@index([activityId])
}
/// تاریخچه تغییرات هر Task
model RoutineTaskEvent {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
taskId String
task RoutineTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
actorId String?
actor User? @relation(fields: [actorId], references: [id], onDelete: SetNull)
/// created | status_changed | rescheduled | note | reassigned | updated
type String
fromStatus RoutineTaskStatus?
toStatus RoutineTaskStatus?
note String?
meta Json @default("{}")
createdAt DateTime @default(now())
@@index([taskId, createdAt])
@@index([organizationId, createdAt])
}
/// تنظیمات اعلان کارهای روتین هر کاربر
model RoutineUserSettings {
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)
remindBeforeMinutes Int? @default(30)
endOfDaySummary Boolean @default(true)
overdueAlert Boolean @default(true)
summaryHour Int @default(16)
lastSummaryOn DateTime? @db.Date
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([organizationId, userId])
}