2283 lines
80 KiB
Plaintext
2283 lines
80 KiB
Plaintext
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 ActivityPriority {
|
|
low
|
|
medium
|
|
high
|
|
very_low
|
|
very_high
|
|
}
|
|
|
|
enum PersonMood {
|
|
newcomer
|
|
happy
|
|
unhappy
|
|
special
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
enum AutomationTrigger {
|
|
person_created
|
|
lead_submitted
|
|
deal_created
|
|
deal_stage_changed
|
|
deal_won
|
|
deal_lost
|
|
ticket_created
|
|
ticket_resolved
|
|
invoice_issued
|
|
invoice_paid
|
|
activity_done
|
|
sms_link_clicked
|
|
}
|
|
|
|
enum AutomationAction {
|
|
create_activity
|
|
create_notification
|
|
add_loyalty_points
|
|
}
|
|
|
|
enum NoteAiSuggestionStatus {
|
|
pending
|
|
applied
|
|
dismissed
|
|
failed
|
|
}
|
|
|
|
enum MemorySourceType {
|
|
note
|
|
manual
|
|
}
|
|
|
|
model Organization {
|
|
id String @id @default(cuid())
|
|
name String
|
|
slug String @unique
|
|
/// زمینه فعالیت سازمان — فهرست همتراز صنایع دیدار
|
|
businessField String?
|
|
baseCurrency String @default("IRR")
|
|
enabledCurrencies String[] @default(["IRR", "USD", "EUR"])
|
|
/// ماژولهای فعال منو برای این محیط
|
|
enabledModules String[] @default([])
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
users User[]
|
|
memberships Membership[]
|
|
orgRoles OrgRole[]
|
|
teams Team[]
|
|
companies Company[]
|
|
people Person[]
|
|
pipelines Pipeline[]
|
|
deals Deal[]
|
|
activities Activity[]
|
|
notes Note[]
|
|
noteRevisions NoteRevision[]
|
|
customFields CustomFieldDefinition[]
|
|
products Product[]
|
|
notifications Notification[]
|
|
noteAttachments NoteAttachment[]
|
|
leadForms LeadForm[]
|
|
quotes Quote[]
|
|
invoices Invoice[]
|
|
salesGoals SalesGoal[]
|
|
exchangeRates ExchangeRate[]
|
|
tickets Ticket[]
|
|
faqArticles FaqArticle[]
|
|
portalOtps PortalOtp[]
|
|
projectBoards ProjectBoard[]
|
|
projectCards ProjectCard[]
|
|
loyaltyTiers LoyaltyTier[]
|
|
loyaltySettings LoyaltySettings?
|
|
loyaltyLedgers LoyaltyLedger[]
|
|
loyaltyRewards LoyaltyReward[]
|
|
loyaltyRedemptions LoyaltyRedemption[]
|
|
automationRules AutomationRule[]
|
|
messageAutomationRules MessageAutomationRule[]
|
|
messageAutomationJobs MessageAutomationJob[]
|
|
memoryItems MemoryItem[]
|
|
noteAiSuggestions NoteAiSuggestion[]
|
|
accountingConnections AccountingConnection[]
|
|
accountingSyncJobs AccountingSyncJob[]
|
|
warehouses Warehouse[]
|
|
stockMovements StockMovement[]
|
|
partyOpenings PartyOpeningBalance[]
|
|
commissionEntries CommissionEntry[]
|
|
cheques Cheque[]
|
|
auditLogs AuditLog[]
|
|
savedViews SavedView[]
|
|
apiKeys OrganizationApiKey[]
|
|
webhookEndpoints WebhookEndpoint[]
|
|
smsCampaigns SmsCampaign[]
|
|
wooCommerceConnections WooCommerceConnection[]
|
|
voipConnections VoipConnection[]
|
|
reservationServices ReservationService[]
|
|
reservationAppointments ReservationAppointment[]
|
|
leads Lead[]
|
|
leadStages LeadStage[]
|
|
leadEvents LeadEvent[]
|
|
leadImportJobs LeadImportJob[]
|
|
conversationSettings ConversationSettings?
|
|
conversationRooms ConversationRoom[]
|
|
conversationHiddenMessages ConversationHiddenMessage[]
|
|
conversationPinnedMessages ConversationPinnedMessage[]
|
|
conversationRoomUserPins ConversationRoomUserPin[]
|
|
conversationRoomMembers ConversationRoomMember[]
|
|
conversationRoomUserMutes ConversationRoomUserMute[]
|
|
conversationMessageReactions ConversationMessageReaction[]
|
|
trackingLinks TrackingLink[]
|
|
trackingEvents TrackingEvent[]
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String
|
|
/// موبایل نرمالشده (مثلاً 9121234567) — برای ورود با موبایل
|
|
mobile String?
|
|
passwordHash String
|
|
fullName String
|
|
role UserRole @default(sales)
|
|
/// مسیر فایل آواتار در فضای آپلود
|
|
avatarStorageKey String?
|
|
isActive Boolean @default(true)
|
|
lastLoginAt DateTime?
|
|
orgRoleId String?
|
|
orgRole OrgRole? @relation(fields: [orgRoleId], references: [id], onDelete: SetNull)
|
|
emailVerifiedAt DateTime?
|
|
mobileVerifiedAt DateTime?
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
ownedDeals Deal[] @relation("DealOwner")
|
|
assignedDeals Deal[] @relation("DealAssignees")
|
|
ownedQuotes Quote[] @relation("QuoteOwner")
|
|
ownedInvoices Invoice[] @relation("InvoiceOwner")
|
|
ownedPeople Person[] @relation("PersonOwner")
|
|
ownedCompanies Company[] @relation("CompanyOwner")
|
|
ownedProjectCards ProjectCard[] @relation("ProjectCardOwner")
|
|
activities Activity[] @relation("ActivityAssignee")
|
|
createdActivities Activity[] @relation("ActivityCreator")
|
|
notes Note[] @relation("NoteAuthor")
|
|
noteRevisions NoteRevision[] @relation("NoteRevisionEditor")
|
|
noteAttachments NoteAttachment[] @relation("NoteAttachmentUploader")
|
|
notifications Notification[]
|
|
salesGoals SalesGoal[] @relation("SalesGoalUser")
|
|
commissionPercent Decimal @default(0) @db.Decimal(8, 2)
|
|
commissionEntries CommissionEntry[]
|
|
receivedCheques Cheque[] @relation("ChequeOwner")
|
|
assignedTickets Ticket[] @relation("TicketAssignee")
|
|
reportedTickets Ticket[] @relation("TicketReporter")
|
|
ticketMessages TicketMessage[] @relation("TicketMessageAuthor")
|
|
confirmedMemories MemoryItem[] @relation("MemoryConfirmedBy")
|
|
memberships Membership[]
|
|
billingAccount BillingAccount?
|
|
auditLogs AuditLog[] @relation("AuditLogActor")
|
|
savedViews SavedView[] @relation("SavedViewOwner")
|
|
assignedProjectCards ProjectCard[] @relation("ProjectCardAssignees")
|
|
teamMemberships TeamMember[]
|
|
reservationAssignments ReservationAppointment[] @relation("ReservationAssignee")
|
|
assignedLeads Lead[] @relation("LeadAssignee")
|
|
createdLeads Lead[] @relation("LeadCreator")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, email])
|
|
@@unique([mobile])
|
|
@@index([organizationId])
|
|
@@index([email])
|
|
@@index([orgRoleId])
|
|
}
|
|
|
|
model OrgRole {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
description String @default("")
|
|
color String @default("teal")
|
|
permissions String[] @default([])
|
|
/// owner | admin | sales | support — نقشهای سیستمی قابل حذف نیستند
|
|
systemKey String?
|
|
users User[]
|
|
memberships Membership[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, name])
|
|
@@index([organizationId])
|
|
}
|
|
|
|
model Membership {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
role UserRole @default(sales)
|
|
orgRoleId String?
|
|
orgRole OrgRole? @relation(fields: [orgRoleId], references: [id], onDelete: SetNull)
|
|
isActive Boolean @default(true)
|
|
commissionPercent Decimal @default(0) @db.Decimal(8, 2)
|
|
/// own | team | org — سطح دید دادهها برای نقشهای غیرمالک/مدیر
|
|
dataVisibility String @default("own")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, organizationId])
|
|
@@index([organizationId, role])
|
|
@@index([userId])
|
|
@@index([orgRoleId])
|
|
}
|
|
|
|
/// گروه کاری داخل سازمان برای دیدپذیری مشترک
|
|
model Team {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
description String @default("")
|
|
members TeamMember[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, name])
|
|
@@index([organizationId])
|
|
}
|
|
|
|
model TeamMember {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
/// member | manager
|
|
role String @default("member")
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([teamId, userId])
|
|
@@index([userId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
/// حساب سوپرادمین پلتفرم — جدا از User سازمانی
|
|
model PlatformAdmin {
|
|
id String @id @default(cuid())
|
|
/// ورود اصلی با موبایل نرمالشده (مثلاً 0912…)
|
|
mobile String? @unique
|
|
email String? @unique
|
|
passwordHash String
|
|
fullName String
|
|
isActive Boolean @default(true)
|
|
lastLoginAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
/// اشتراک SaaS روی مالک حساب (نه روی هر عضویت)
|
|
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
|
|
planCode String @default("trial")
|
|
/// active | expired | suspended
|
|
status String @default("active")
|
|
startsAt DateTime @default(now())
|
|
expiresAt DateTime
|
|
maxOrganizations Int @default(1)
|
|
/// رزرو برای درگاه/لندینگ بعدی
|
|
externalPaymentRef String?
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([status])
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
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?
|
|
tags String[] @default([])
|
|
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[]
|
|
projectCards ProjectCard[]
|
|
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?
|
|
/// آدرس وبسایت
|
|
website String?
|
|
/// پیج اینستاگرام
|
|
instagram String?
|
|
/// نام معرف
|
|
referrer String?
|
|
/// لیست آدرسها (JSON array of strings)
|
|
addresses Json @default("[]")
|
|
notes String?
|
|
mood PersonMood @default(newcomer)
|
|
tags String[] @default([])
|
|
customFields Json @default("{}")
|
|
loyaltyPoints Int @default(0)
|
|
loyaltyTierId String?
|
|
loyaltyTier LoyaltyTier? @relation(fields: [loyaltyTierId], references: [id], onDelete: SetNull)
|
|
loyaltyLedgers LoyaltyLedger[]
|
|
loyaltyRedemptions LoyaltyRedemption[]
|
|
deals Deal[]
|
|
activities Activity[]
|
|
smartNotes Note[]
|
|
quotes Quote[]
|
|
invoices Invoice[]
|
|
reservationAppointments ReservationAppointment[]
|
|
ownerId String?
|
|
owner User? @relation("PersonOwner", fields: [ownerId], references: [id], onDelete: SetNull)
|
|
tickets Ticket[]
|
|
projectCards ProjectCard[]
|
|
memoryItems MemoryItem[]
|
|
accountingLinks PersonAccountingLink[]
|
|
accountingDocuments AccountingDocumentLog[]
|
|
openingBalances PartyOpeningBalance[]
|
|
cheques Cheque[]
|
|
trackingLinks TrackingLink[]
|
|
trackingEvents TrackingEvent[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId])
|
|
@@index([organizationId, email])
|
|
@@index([organizationId, mobile])
|
|
@@index([organizationId, lastName, firstName])
|
|
@@index([organizationId, loyaltyPoints])
|
|
@@index([companyId])
|
|
@@index([ownerId])
|
|
@@index([loyaltyTierId])
|
|
}
|
|
|
|
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)
|
|
assignees User[] @relation("DealAssignees")
|
|
position Int @default(0)
|
|
activities Activity[]
|
|
smartNotes Note[]
|
|
lineItems DealLineItem[]
|
|
quotes Quote[]
|
|
invoices Invoice[]
|
|
tickets Ticket[]
|
|
projectCards ProjectCard[]
|
|
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)
|
|
priority ActivityPriority @default(medium)
|
|
title String
|
|
description String?
|
|
dueAt DateTime?
|
|
durationMinutes Int?
|
|
reminderAt DateTime?
|
|
/// اعلان داخل پنل هنگام رسیدن یادآور
|
|
reminderNotifyApp Boolean @default(true)
|
|
/// پیامک به موبایل مسئول هنگام رسیدن یادآور
|
|
reminderNotifySms Boolean @default(false)
|
|
completedAt DateTime?
|
|
tags String[] @default([])
|
|
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)
|
|
createdById String?
|
|
createdBy User? @relation("ActivityCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
|
notes Note[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, status, dueAt])
|
|
@@index([organizationId, assigneeId])
|
|
@@index([organizationId, createdById])
|
|
@@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?
|
|
/// [{ id: string, text: string, done: boolean }]
|
|
checklist Json?
|
|
locationText String?
|
|
analyzeEnabled Boolean @default(true)
|
|
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)
|
|
activityId String?
|
|
activity Activity? @relation(fields: [activityId], references: [id], onDelete: SetNull)
|
|
authorId String?
|
|
author User? @relation("NoteAuthor", fields: [authorId], references: [id], onDelete: SetNull)
|
|
/// team = قابلمشاهده برای تیم/سازمان در محدوده پرونده | personal = فقط نویسنده
|
|
visibility String @default("team")
|
|
attachments NoteAttachment[]
|
|
aiSuggestion NoteAiSuggestion?
|
|
revisions NoteRevision[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, pinned, updatedAt])
|
|
@@index([organizationId, noteType, updatedAt])
|
|
@@index([organizationId, reminderAt])
|
|
@@index([organizationId, visibility])
|
|
@@index([personId])
|
|
@@index([companyId])
|
|
@@index([dealId])
|
|
@@index([activityId])
|
|
@@index([authorId])
|
|
}
|
|
|
|
/// اسنپشات متن قبل از هر ویرایش معنادار یادداشت
|
|
model NoteRevision {
|
|
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)
|
|
editorId String?
|
|
editor User? @relation("NoteRevisionEditor", fields: [editorId], references: [id], onDelete: SetNull)
|
|
title String?
|
|
body String
|
|
noteType String
|
|
visibility String @default("team")
|
|
tags String[] @default([])
|
|
checklist Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([noteId, createdAt])
|
|
@@index([organizationId])
|
|
@@index([editorId])
|
|
}
|
|
|
|
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 NoteAiSuggestion {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
noteId String @unique
|
|
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
|
status NoteAiSuggestionStatus @default(pending)
|
|
payload Json?
|
|
errorMessage String?
|
|
analyzedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, status])
|
|
}
|
|
|
|
model MemoryItem {
|
|
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)
|
|
content String
|
|
sourceType MemorySourceType @default(manual)
|
|
sourceId String?
|
|
confirmedById String?
|
|
confirmedBy User? @relation("MemoryConfirmedBy", fields: [confirmedById], references: [id], onDelete: SetNull)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, personId, updatedAt])
|
|
@@index([personId])
|
|
@@index([confirmedById])
|
|
}
|
|
|
|
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)
|
|
/// product | service
|
|
kind String @default("product")
|
|
name String
|
|
sku String?
|
|
description String?
|
|
category String?
|
|
unit String? @default("عدد")
|
|
stockQty Int?
|
|
brand String?
|
|
model String?
|
|
country String?
|
|
tags String[] @default([])
|
|
unitPrice Decimal? @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
isActive Boolean @default(true)
|
|
lineItems DealLineItem[]
|
|
quoteLineItems QuoteLineItem[]
|
|
invoiceLineItems InvoiceLineItem[]
|
|
stockMovements StockMovement[]
|
|
accountingLinks ProductAccountingLink[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, name])
|
|
@@index([organizationId, isActive])
|
|
@@index([organizationId, category])
|
|
}
|
|
|
|
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
|
|
description String?
|
|
publicToken String @unique @default(cuid())
|
|
isActive Boolean @default(true)
|
|
/// ثبت بهعنوان Lead در CRM (مسیر اصلی انتشار فرم)
|
|
createLead Boolean @default(true)
|
|
createPerson Boolean @default(false)
|
|
createDeal Boolean @default(false)
|
|
pipelineId String?
|
|
/// مرحله اولیه لید (اختیاری؛ وگرنه اولین مرحله)
|
|
leadStageId String?
|
|
/// skip | update | activity
|
|
duplicateMode String @default("update")
|
|
source String?
|
|
thankYouMessage String?
|
|
redirectUrl String?
|
|
assigneeIds String[] @default([])
|
|
roundRobinIndex Int @default(0)
|
|
submitLabel String @default("ارسال درخواست")
|
|
submitColor String @default("#14b8a6")
|
|
coverStorageKey String?
|
|
viewCount Int @default(0)
|
|
startCount Int @default(0)
|
|
submitCount Int @default(0)
|
|
leadCreateCount Int @default(0)
|
|
duplicateCount Int @default(0)
|
|
fields LeadFormField[]
|
|
submissions LeadFormSubmission[]
|
|
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)
|
|
placeholder String?
|
|
options Json?
|
|
|
|
@@unique([formId, key])
|
|
@@index([formId, position])
|
|
}
|
|
|
|
model LeadFormSubmission {
|
|
id String @id @default(cuid())
|
|
formId String
|
|
form LeadForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
|
values Json
|
|
personId String?
|
|
leadId String?
|
|
/// created | updated | duplicate_skipped | error
|
|
status String @default("created")
|
|
utm Json?
|
|
meta Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([formId, createdAt])
|
|
@@index([leadId])
|
|
}
|
|
|
|
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?
|
|
discountAmount Decimal @default(0) @db.Decimal(18, 2)
|
|
taxPercent Decimal @default(0) @db.Decimal(8, 2)
|
|
issuedAt DateTime?
|
|
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)
|
|
kind String @default("invoice") // invoice | credit
|
|
currency String @default("IRR")
|
|
notes String?
|
|
discountAmount Decimal @default(0) @db.Decimal(18, 2)
|
|
taxPercent Decimal @default(0) @db.Decimal(8, 2)
|
|
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[]
|
|
stockMovements StockMovement[]
|
|
commissionPercent Decimal? @db.Decimal(8, 2)
|
|
commissionFixedAmount Decimal? @db.Decimal(18, 2)
|
|
commissionEntry CommissionEntry?
|
|
cheques Cheque[]
|
|
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)
|
|
cheque Cheque?
|
|
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
|
|
/// sales|customer|revenue|deal|activity|quality
|
|
type String @default("sales")
|
|
periodStart DateTime
|
|
periodEnd DateTime
|
|
targetAmount Decimal @db.Decimal(18, 2)
|
|
forecastAmount Decimal? @db.Decimal(18, 2)
|
|
/// 0-100
|
|
probability Decimal? @db.Decimal(5, 2)
|
|
currency String @default("IRR")
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, periodStart, periodEnd])
|
|
@@index([userId])
|
|
@@index([organizationId, type])
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
/// برد کانبان برای کارهای داخلی / پسازفروش (جدا از کاریز فروش)
|
|
model ProjectBoard {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
isDefault Boolean @default(false)
|
|
stages ProjectBoardStage[]
|
|
cards ProjectCard[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId])
|
|
@@index([organizationId, isDefault])
|
|
}
|
|
|
|
model ProjectBoardStage {
|
|
id String @id @default(cuid())
|
|
boardId String
|
|
board ProjectBoard @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
name String
|
|
position Int
|
|
cards ProjectCard[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([boardId, position])
|
|
}
|
|
|
|
model ProjectCard {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
boardId String
|
|
board ProjectBoard @relation(fields: [boardId], references: [id], onDelete: Cascade)
|
|
stageId String
|
|
stage ProjectBoardStage @relation(fields: [stageId], references: [id], onDelete: Restrict)
|
|
title String
|
|
notes String?
|
|
tags String[] @default([])
|
|
position Int @default(0)
|
|
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("ProjectCardOwner", fields: [ownerId], references: [id], onDelete: SetNull)
|
|
assignees User[] @relation("ProjectCardAssignees")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId])
|
|
@@index([boardId, stageId])
|
|
@@index([stageId, position])
|
|
@@index([personId])
|
|
@@index([companyId])
|
|
@@index([dealId])
|
|
@@index([ownerId])
|
|
}
|
|
|
|
model LoyaltyTier {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
minPoints Int @default(0)
|
|
position Int @default(0)
|
|
color String?
|
|
people Person[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, position])
|
|
@@index([organizationId, minPoints])
|
|
}
|
|
|
|
model LoyaltySettings {
|
|
id String @id @default(cuid())
|
|
organizationId String @unique
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
enabled Boolean @default(true)
|
|
welcomePoints Int @default(0)
|
|
notifyOnAdjust Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model LoyaltyLedger {
|
|
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)
|
|
delta Int
|
|
reason String?
|
|
source String @default("manual")
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([organizationId, createdAt])
|
|
@@index([personId, createdAt])
|
|
}
|
|
|
|
model LoyaltyReward {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
description String?
|
|
pointsCost Int
|
|
isActive Boolean @default(true)
|
|
position Int @default(0)
|
|
color String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
redemptions LoyaltyRedemption[]
|
|
|
|
@@index([organizationId, position])
|
|
}
|
|
|
|
model LoyaltyRedemption {
|
|
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)
|
|
rewardId String
|
|
reward LoyaltyReward @relation(fields: [rewardId], references: [id], onDelete: Restrict)
|
|
pointsSpent Int
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([organizationId, createdAt])
|
|
@@index([personId])
|
|
@@index([rewardId])
|
|
}
|
|
|
|
model AutomationRule {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
isActive Boolean @default(true)
|
|
trigger AutomationTrigger
|
|
action AutomationAction
|
|
/// عنوان فعالیت/اعلان، assigneeId، notifyUserId، points، activityType
|
|
config Json @default("{}")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, isActive, trigger])
|
|
}
|
|
|
|
enum MessageAutomationTrigger {
|
|
person_created
|
|
lead_submitted
|
|
deal_created
|
|
deal_stage_changed
|
|
deal_won
|
|
deal_lost
|
|
ticket_created
|
|
ticket_resolved
|
|
invoice_issued
|
|
invoice_paid
|
|
activity_created
|
|
activity_done
|
|
sms_link_clicked
|
|
}
|
|
|
|
enum MessageAutomationDelayUnit {
|
|
minutes
|
|
hours
|
|
days
|
|
weeks
|
|
months
|
|
}
|
|
|
|
enum MessageAutomationChannel {
|
|
app
|
|
sms
|
|
both
|
|
}
|
|
|
|
enum MessageAutomationTarget {
|
|
staff
|
|
person
|
|
both
|
|
}
|
|
|
|
enum MessageAutomationJobStatus {
|
|
pending
|
|
sent
|
|
cancelled
|
|
failed
|
|
}
|
|
|
|
model MessageAutomationRule {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
isActive Boolean @default(true)
|
|
trigger MessageAutomationTrigger
|
|
startDelayValue Int @default(0)
|
|
startDelayUnit MessageAutomationDelayUnit @default(minutes)
|
|
repeatCount Int @default(1)
|
|
intervalValue Int @default(1)
|
|
intervalUnit MessageAutomationDelayUnit @default(days)
|
|
notifyTarget MessageAutomationTarget @default(person)
|
|
channel MessageAutomationChannel @default(sms)
|
|
title String
|
|
description String?
|
|
body String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
jobs MessageAutomationJob[]
|
|
|
|
@@index([organizationId, isActive, trigger])
|
|
}
|
|
|
|
model MessageAutomationJob {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
ruleId String
|
|
rule MessageAutomationRule @relation(fields: [ruleId], references: [id], onDelete: Cascade)
|
|
trigger MessageAutomationTrigger
|
|
entityType String
|
|
entityId String
|
|
entityTitle String
|
|
personId String?
|
|
ownerId String?
|
|
assigneeId String?
|
|
attemptIndex Int
|
|
sendAt DateTime
|
|
status MessageAutomationJobStatus @default(pending)
|
|
createdAt DateTime @default(now())
|
|
sentAt DateTime?
|
|
|
|
@@unique([ruleId, entityId, attemptIndex])
|
|
@@index([status, sendAt])
|
|
@@index([organizationId, entityType, entityId, status])
|
|
}
|
|
|
|
/// چالش OTP برای ثبتنام / ورود پنل
|
|
model AuthChallenge {
|
|
id String @id @default(cuid())
|
|
/// register | login
|
|
kind String
|
|
/// برای login: email | mobile
|
|
channel String?
|
|
email String?
|
|
mobile String?
|
|
passwordHash String?
|
|
/// دادهٔ موقت ثبتنام (سازمان، نام، زمینه، کد معرف، …)
|
|
payload Json @default("{}")
|
|
emailCode String?
|
|
mobileCode String?
|
|
emailVerified Boolean @default(false)
|
|
mobileVerified Boolean @default(false)
|
|
expiresAt DateTime
|
|
consumedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([kind, createdAt])
|
|
@@index([expiresAt])
|
|
@@index([email])
|
|
@@index([mobile])
|
|
}
|
|
|
|
/// اتصال به نرمافزار حسابداری خارجی (تهحساب) — چند اتصال/DB بهازای هر سازمان
|
|
model AccountingConnection {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
/// فعلاً فقط tahesab
|
|
provider String @default("tahesab")
|
|
label String
|
|
baseUrl String
|
|
dbName String
|
|
/// AES-256-GCM ciphertext (base64)
|
|
tokenCiphertext String
|
|
tokenIv String
|
|
tokenTag String
|
|
/// چهار کاراکتر آخر توکن برای نمایش ماسکشده
|
|
tokenLast4 String @default("")
|
|
enabled Boolean @default(true)
|
|
isDefault Boolean @default(false)
|
|
/// اگر true باشد verify SSL نادیده گرفته میشود (فقط برای نصبهای داخلی با گواهی خودامضا)
|
|
allowInsecureTls Boolean @default(false)
|
|
lastHealthAt DateTime?
|
|
lastHealthOk Boolean?
|
|
lastError String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
personLinks PersonAccountingLink[]
|
|
productLinks ProductAccountingLink[]
|
|
documentLogs AccountingDocumentLog[]
|
|
balanceCaches AccountingBalanceCache[]
|
|
|
|
@@unique([organizationId, baseUrl, dbName])
|
|
@@index([organizationId, enabled])
|
|
@@index([organizationId, isDefault])
|
|
}
|
|
|
|
model PersonAccountingLink {
|
|
id String @id @default(cuid())
|
|
personId String
|
|
person Person @relation(fields: [personId], references: [id], onDelete: Cascade)
|
|
connectionId String
|
|
connection AccountingConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
/// کد مشتری در تهحساب (Moshtari_Code)
|
|
moshtariCode Int
|
|
/// manual | mobile | name | created
|
|
matchMethod String
|
|
linkedAt DateTime @default(now())
|
|
lastBalanceSyncedAt DateTime?
|
|
balanceCache AccountingBalanceCache?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([personId, connectionId])
|
|
@@unique([connectionId, moshtariCode])
|
|
@@index([personId])
|
|
@@index([connectionId])
|
|
}
|
|
|
|
/// لینک محصول CRM ↔ نام کارساخته (یا معادل) در تهحساب
|
|
model ProductAccountingLink {
|
|
id String @id @default(cuid())
|
|
productId String
|
|
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
|
connectionId String
|
|
connection AccountingConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
/// نام کارساخته در تهحساب (DoListNameKarsakhte.Name)
|
|
externalName String
|
|
ayar Int?
|
|
vaznSabet Float?
|
|
/// import | manual
|
|
matchMethod String @default("import")
|
|
linkedAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([productId, connectionId])
|
|
@@unique([connectionId, externalName])
|
|
@@index([productId])
|
|
@@index([connectionId])
|
|
}
|
|
|
|
model AccountingDocumentLog {
|
|
id String @id @default(cuid())
|
|
connectionId String
|
|
connection AccountingConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
personId String?
|
|
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
|
/// مثلاً DoNewSanadBuySaleGOLD
|
|
docType String
|
|
/// کد یکتای ≥۲۰ کاراکتر — idempotency
|
|
factorCode String
|
|
requestPayload Json
|
|
responsePayload Json?
|
|
/// pending | success | failed
|
|
status String @default("pending")
|
|
tahesabOk String?
|
|
tahesabFactorId Int?
|
|
errorMessage String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([connectionId, factorCode])
|
|
@@index([connectionId, status])
|
|
@@index([personId])
|
|
@@index([docType])
|
|
}
|
|
|
|
model AccountingBalanceCache {
|
|
id String @id @default(cuid())
|
|
linkId String @unique
|
|
link PersonAccountingLink @relation(fields: [linkId], references: [id], onDelete: Cascade)
|
|
connectionId String
|
|
connection AccountingConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
goldBalance Float?
|
|
rialBalance Float?
|
|
currencyBalance Float?
|
|
/// مانده همارز طلا (MandeyeKolBeTALA) در صورت وجود
|
|
totalGoldEquiv Float?
|
|
/// جزئیات گروهبندیشده برای UI
|
|
mandehDetail Json @default("[]")
|
|
rawResponse Json?
|
|
fetchedAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([connectionId])
|
|
}
|
|
|
|
model AccountingSyncJob {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
connectionId String?
|
|
/// search | import | match
|
|
kind String
|
|
status String @default("pending")
|
|
request Json @default("{}")
|
|
result Json?
|
|
errorMessage String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, createdAt])
|
|
}
|
|
|
|
/// انبار داخلی — وقتی تهحساب وصل نیست
|
|
model Warehouse {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
isDefault Boolean @default(true)
|
|
movements StockMovement[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, isDefault])
|
|
}
|
|
|
|
/// گردش کالا؛ مقدار مثبت ورود، منفی خروج
|
|
model StockMovement {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
warehouseId String
|
|
warehouse Warehouse @relation(fields: [warehouseId], references: [id], onDelete: Cascade)
|
|
productId String
|
|
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
|
/// opening | purchase | sale | credit | adjustment
|
|
type String
|
|
quantity Decimal @db.Decimal(18, 2)
|
|
note String?
|
|
invoiceId String?
|
|
invoice Invoice? @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
|
occurredAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([organizationId, productId, occurredAt])
|
|
@@index([invoiceId])
|
|
@@index([warehouseId])
|
|
}
|
|
|
|
/// پورسانت کارشناس بعد از صدور فاکتور
|
|
model CommissionEntry {
|
|
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)
|
|
invoiceId String @unique
|
|
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
|
percent Decimal @db.Decimal(8, 2)
|
|
amount Decimal @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
/// posted | void
|
|
status String @default("posted")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, userId, createdAt])
|
|
@@index([status])
|
|
}
|
|
|
|
/// چک دریافتی؛ مستقل یا وصل به پرداخت فاکتور
|
|
model Cheque {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
paymentId String? @unique
|
|
payment Payment? @relation(fields: [paymentId], references: [id], onDelete: Cascade)
|
|
invoiceId String?
|
|
invoice Invoice? @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
|
personId String?
|
|
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
|
ownerId String?
|
|
owner User? @relation("ChequeOwner", fields: [ownerId], references: [id], onDelete: SetNull)
|
|
amount Decimal @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
number String
|
|
bank String
|
|
dueDate DateTime
|
|
/// pending | cleared | bounced
|
|
status String @default("pending")
|
|
/// in = دریافتی، out = پرداختی
|
|
direction String @default("in")
|
|
reminderNotifiedAt DateTime?
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, status, dueDate])
|
|
@@index([organizationId, direction, dueDate])
|
|
@@index([invoiceId])
|
|
@@index([personId])
|
|
@@index([ownerId])
|
|
}
|
|
|
|
/// مانده اول دوره طرفحساب (مثبت = بدهکار ما)
|
|
model PartyOpeningBalance {
|
|
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)
|
|
amount Decimal @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
note String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, personId])
|
|
@@index([personId])
|
|
}
|
|
|
|
/// لاگ فعالیت کاربران روی منابع سازمان
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
actorUserId String?
|
|
actor User? @relation("AuditLogActor", fields: [actorUserId], references: [id], onDelete: SetNull)
|
|
action String
|
|
method String
|
|
path String
|
|
entityType String?
|
|
entityId String?
|
|
summary String
|
|
meta Json?
|
|
ip String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([organizationId, createdAt])
|
|
@@index([organizationId, actorUserId, createdAt])
|
|
@@index([organizationId, entityType, createdAt])
|
|
}
|
|
|
|
/// فیلترهای ذخیرهشده برای لیستها و کاریزها
|
|
model SavedView {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
ownerUserId String?
|
|
owner User? @relation("SavedViewOwner", fields: [ownerUserId], references: [id], onDelete: SetNull)
|
|
name String
|
|
/// deals | people | companies | activities | project_cards | reports
|
|
scope String
|
|
filters Json @default("{}")
|
|
isShared Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, scope])
|
|
@@index([organizationId, ownerUserId])
|
|
}
|
|
|
|
/// کلید API عمومی برای یکپارچهسازی خارجی
|
|
model OrganizationApiKey {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
/// پیشوند قابلنمایش (مثلاً mk_ab12)
|
|
prefix String
|
|
/// SHA-256 هش کلید کامل
|
|
keyHash String
|
|
lastUsedAt DateTime?
|
|
revokedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([keyHash])
|
|
@@index([organizationId, revokedAt])
|
|
@@index([prefix])
|
|
}
|
|
|
|
/// وبهوک خروجی سازمان
|
|
model WebhookEndpoint {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
url String
|
|
secret String
|
|
events String[] @default([])
|
|
isActive Boolean @default(true)
|
|
deliveries WebhookDelivery[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, isActive])
|
|
}
|
|
|
|
model WebhookDelivery {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
endpointId String
|
|
endpoint WebhookEndpoint @relation(fields: [endpointId], references: [id], onDelete: Cascade)
|
|
event String
|
|
payload Json
|
|
status String @default("pending")
|
|
statusCode Int?
|
|
attempts Int @default(0)
|
|
lastError String?
|
|
nextAttemptAt DateTime @default(now())
|
|
deliveredAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, createdAt])
|
|
@@index([status, nextAttemptAt])
|
|
@@index([endpointId, createdAt])
|
|
}
|
|
|
|
/// کمپین پیامک گروهی
|
|
model SmsCampaign {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
body String
|
|
/// draft | queued | sending | done | cancelled
|
|
status String @default("draft")
|
|
scheduledAt DateTime?
|
|
totalCount Int @default(0)
|
|
sentCount Int @default(0)
|
|
failedCount Int @default(0)
|
|
skippedCount Int @default(0)
|
|
createdById String?
|
|
recipients SmsCampaignRecipient[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, status, scheduledAt])
|
|
}
|
|
|
|
model SmsCampaignRecipient {
|
|
id String @id @default(cuid())
|
|
campaignId String
|
|
campaign SmsCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
|
|
personId String?
|
|
/// برای پشتیبانی آینده از Lead (فعلاً کمپین روی Person است)
|
|
leadId String?
|
|
mobile String?
|
|
/// pending | sent | failed | skipped
|
|
status String @default("pending")
|
|
error String?
|
|
sentAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
trackingLinks TrackingLink[]
|
|
|
|
@@index([campaignId, status])
|
|
@@index([personId])
|
|
@@index([leadId])
|
|
}
|
|
|
|
/// لینک ردیابی یکتا — کانالخنثی (SMS / Email / …)
|
|
model TrackingLink {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
/// توکن امن و غیرقابلحدس (بدون PII)
|
|
token String @unique
|
|
originalUrl String
|
|
/// sms | email | whatsapp | notification | other
|
|
channel String @default("sms")
|
|
/// sms_campaign | …
|
|
sourceType String
|
|
sourceId String
|
|
/// sms_campaign_recipient | …
|
|
recipientType String?
|
|
recipientId String?
|
|
smsRecipientId String?
|
|
smsRecipient SmsCampaignRecipient? @relation(fields: [smsRecipientId], references: [id], onDelete: SetNull)
|
|
personId String?
|
|
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
|
leadId String?
|
|
lead Lead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
|
clickCount Int @default(0)
|
|
firstClickedAt DateTime?
|
|
lastClickedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
events TrackingEvent[]
|
|
|
|
@@index([organizationId, sourceType, sourceId])
|
|
@@index([organizationId, personId])
|
|
@@index([organizationId, leadId])
|
|
@@index([smsRecipientId])
|
|
@@index([token])
|
|
}
|
|
|
|
/// رویداد رفتاری عمومی — آمادهٔ Automation
|
|
model TrackingEvent {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
trackingLinkId String?
|
|
trackingLink TrackingLink? @relation(fields: [trackingLinkId], references: [id], onDelete: SetNull)
|
|
/// مثلاً sms_link_clicked | sms_delivered | sms_failed | …
|
|
eventType String
|
|
channel String @default("sms")
|
|
sourceType String?
|
|
sourceId String?
|
|
recipientType String?
|
|
recipientId String?
|
|
personId String?
|
|
person Person? @relation(fields: [personId], references: [id], onDelete: SetNull)
|
|
leadId String?
|
|
lead Lead? @relation(fields: [leadId], references: [id], onDelete: SetNull)
|
|
occurredAt DateTime @default(now())
|
|
/// ua / referrer / ipHash (اختیاری، بدون PII خام)
|
|
metadata Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([organizationId, eventType, occurredAt])
|
|
@@index([organizationId, sourceType, sourceId, eventType])
|
|
@@index([organizationId, personId, eventType])
|
|
@@index([organizationId, leadId, eventType])
|
|
@@index([trackingLinkId, occurredAt])
|
|
}
|
|
|
|
/// اتصال فروشگاه ووکامرس
|
|
model WooCommerceConnection {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
label String
|
|
storeUrl String
|
|
consumerKeyCiphertext String
|
|
consumerKeyIv String
|
|
consumerKeyTag String
|
|
consumerKeyLast4 String @default("")
|
|
consumerSecretCiphertext String
|
|
consumerSecretIv String
|
|
consumerSecretTag String
|
|
webhookSecretCiphertext String?
|
|
webhookSecretIv String?
|
|
webhookSecretTag String?
|
|
enabled Boolean @default(true)
|
|
lastSyncAt DateTime?
|
|
lastHealthAt DateTime?
|
|
lastHealthOk Boolean?
|
|
lastError String?
|
|
orders WooCommerceOrderLink[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, enabled])
|
|
}
|
|
|
|
model WooCommerceOrderLink {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
connectionId String
|
|
connection WooCommerceConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
wooOrderId String
|
|
wooOrderNumber String?
|
|
invoiceId String
|
|
status String @default("")
|
|
total Decimal @default(0) @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
rawPayload Json?
|
|
syncedAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([connectionId, wooOrderId])
|
|
@@index([organizationId, syncedAt])
|
|
@@index([invoiceId])
|
|
}
|
|
|
|
/// اتصال تلفن سازمانی / سانترال (وبهوک عمومی)
|
|
model VoipConnection {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
label String
|
|
enabled Boolean @default(true)
|
|
webhookSecretCiphertext String?
|
|
webhookSecretIv String?
|
|
webhookSecretTag String?
|
|
/// قالب URL شروع تماس؛ جای {number} شماره مینشیند
|
|
clickToCallUrl String?
|
|
lastEventAt DateTime?
|
|
lastError String?
|
|
calls VoipCallLog[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, enabled])
|
|
}
|
|
|
|
model VoipCallLog {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
connectionId String
|
|
connection VoipConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
|
|
externalCallId String
|
|
/// inbound | outbound
|
|
direction String @default("inbound")
|
|
/// ringing | answered | missed | completed | failed
|
|
status String @default("completed")
|
|
fromNumber String?
|
|
toNumber String?
|
|
personId String?
|
|
activityId String?
|
|
durationSec Int?
|
|
startedAt DateTime @default(now())
|
|
endedAt DateTime?
|
|
rawPayload Json?
|
|
/// لینک فایل ضبط از سانترال
|
|
recordingUrl String?
|
|
/// متن خام (از وبهوک یا STT)
|
|
transcript String?
|
|
/// خلاصه فارسی تولیدشده
|
|
summary String?
|
|
/// none | pending | processing | done | failed | skipped
|
|
summaryStatus String @default("none")
|
|
summaryNoteId String?
|
|
summaryError String?
|
|
summaryAttempts Int @default(0)
|
|
summaryNextAttemptAt DateTime?
|
|
syncedAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([connectionId, externalCallId])
|
|
@@index([organizationId, syncedAt])
|
|
@@index([personId])
|
|
@@index([activityId])
|
|
@@index([summaryStatus, summaryNextAttemptAt])
|
|
}
|
|
|
|
/// خدمت قابل رزرو (مثلاً مشاوره ۳۰ دقیقهای)
|
|
model ReservationService {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
description String @default("")
|
|
durationMinutes Int @default(30)
|
|
/// روزهای کار ۰=یکشنبه … ۶=شنبه
|
|
workDays Int[] @default([0, 1, 2, 3, 4, 5, 6])
|
|
dayStartHour Int @default(9)
|
|
dayEndHour Int @default(17)
|
|
isActive Boolean @default(true)
|
|
appointments ReservationAppointment[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, isActive])
|
|
}
|
|
|
|
model ReservationAppointment {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
serviceId String
|
|
service ReservationService @relation(fields: [serviceId], references: [id], onDelete: Restrict)
|
|
personId String
|
|
person Person @relation(fields: [personId], references: [id], onDelete: Cascade)
|
|
assigneeId String?
|
|
assignee User? @relation("ReservationAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
|
startsAt DateTime
|
|
endsAt DateTime
|
|
/// pending | confirmed | canceled | done
|
|
status String @default("pending")
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, startsAt])
|
|
@@index([organizationId, status])
|
|
@@index([serviceId, startsAt])
|
|
@@index([personId])
|
|
@@index([assigneeId, startsAt])
|
|
}
|
|
|
|
/// مرحله/وضعیت لید — هم لیست وضعیت و هم ستون Kanban
|
|
model LeadStage {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
position Int @default(0)
|
|
color String @default("teal")
|
|
/// نمایش در تخته Kanban
|
|
showOnBoard Boolean @default(true)
|
|
isConverted Boolean @default(false)
|
|
isLost Boolean @default(false)
|
|
isUnqualified Boolean @default(false)
|
|
leads Lead[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, position])
|
|
@@unique([organizationId, name])
|
|
}
|
|
|
|
/// مخاطب بالقوه — جدا از Person/مشتری
|
|
model Lead {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
/// نام فروشگاه / شخص / عنوان نمایشی
|
|
title String
|
|
/// نام مالک یا مسئول
|
|
contactName String?
|
|
mobile String
|
|
phone String?
|
|
city String?
|
|
region String?
|
|
/// پیج اینستاگرام
|
|
instagram String?
|
|
/// لیست آدرسها (JSON array of strings)
|
|
addresses Json @default("[]")
|
|
/// صنف
|
|
trade String?
|
|
source String?
|
|
notes String?
|
|
/// لوگوی فروشگاه / شرکت
|
|
logoStorageKey String?
|
|
estimatedValue Decimal? @db.Decimal(18, 2)
|
|
stageId String
|
|
stage LeadStage @relation(fields: [stageId], references: [id], onDelete: Restrict)
|
|
position Int @default(0)
|
|
assigneeId String?
|
|
assignee User? @relation("LeadAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
|
|
createdById String?
|
|
createdBy User? @relation("LeadCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
|
lastContactAt DateTime?
|
|
nextFollowUpAt DateTime?
|
|
/// شخص مشتری پس از تبدیل
|
|
convertedPersonId String?
|
|
convertedAt DateTime?
|
|
archivedAt DateTime?
|
|
events LeadEvent[]
|
|
trackingLinks TrackingLink[]
|
|
trackingEvents TrackingEvent[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, mobile])
|
|
@@index([organizationId, stageId, position])
|
|
@@index([organizationId, assigneeId])
|
|
@@index([organizationId, nextFollowUpAt])
|
|
@@index([organizationId, lastContactAt])
|
|
@@index([organizationId, archivedAt])
|
|
@@index([organizationId, createdAt])
|
|
@@index([organizationId, city])
|
|
@@index([organizationId, trade])
|
|
@@index([organizationId, source])
|
|
}
|
|
|
|
model LeadEvent {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
leadId String
|
|
lead Lead @relation(fields: [leadId], references: [id], onDelete: Cascade)
|
|
/// created | assigned | stage_changed | call | note | task | follow_up | converted | archived | import
|
|
type String
|
|
title String
|
|
body String?
|
|
meta Json?
|
|
actorId String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([leadId, createdAt])
|
|
@@index([organizationId, createdAt])
|
|
}
|
|
|
|
/// ایمپورت پسزمینه فایل اکسل لید
|
|
model LeadImportJob {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
createdById String?
|
|
fileName String?
|
|
/// pending | processing | done | failed
|
|
status String @default("pending")
|
|
totalCount Int @default(0)
|
|
processedCount Int @default(0)
|
|
successCount Int @default(0)
|
|
duplicateCount Int @default(0)
|
|
errorCount Int @default(0)
|
|
/// ردیفهای خام [{...}]
|
|
rows Json @default("[]")
|
|
/// خلاصه خطاها
|
|
errorSamples Json @default("[]")
|
|
startedAt DateTime?
|
|
finishedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, status, createdAt])
|
|
@@index([status, createdAt])
|
|
}
|
|
|
|
/// تنظیمات اتصال به سرویس گفتگوی خارجی (هر محیط CRM)
|
|
model ConversationSettings {
|
|
organizationId String @id
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
chatHost String
|
|
apiKeyCipher String
|
|
apiKeyIv String
|
|
apiKeyTag String
|
|
uploadUrl String @default("")
|
|
downloadUrl String @default("")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
/// نگاشت roomهای سرویس چت (DM dedup و گروههای ساختهشده از CRM)
|
|
model ConversationRoom {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
externalRoomId String
|
|
kind String
|
|
name String?
|
|
dmUserMinId String?
|
|
dmUserMaxId String?
|
|
createdByUserId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, externalRoomId])
|
|
@@unique([organizationId, dmUserMinId, dmUserMaxId])
|
|
@@index([organizationId, kind])
|
|
}
|
|
|
|
/// پیامهای حذفشده در CRM (سرویس چت خارجی حذف پایدار ندارد)
|
|
/// scope=all → برای همه؛ scope=self → فقط برای hiddenByUserId
|
|
model ConversationHiddenMessage {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
externalRoomId String
|
|
messageId String
|
|
hiddenByUserId String
|
|
/// all = حذف برای همه | self = حذف فقط برای کاربر
|
|
scope String @default("all")
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([organizationId, externalRoomId, messageId, scope, hiddenByUserId])
|
|
@@index([organizationId, externalRoomId])
|
|
}
|
|
|
|
/// یک پیام سنجاقشده در هر اتاق (برای همه اعضا)
|
|
model ConversationPinnedMessage {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
externalRoomId String
|
|
messageId String
|
|
preview String @default("")
|
|
pinnedByUserId String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, externalRoomId])
|
|
@@index([organizationId, externalRoomId])
|
|
}
|
|
|
|
/// سنجاق گفتگو در لیست — فقط برای همان کاربر
|
|
model ConversationRoomUserPin {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
userId String
|
|
externalRoomId String
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([organizationId, userId, externalRoomId])
|
|
@@index([organizationId, userId])
|
|
}
|
|
|
|
/// اعضای گروه/کانال در CRM (نقش ادمین برای ارسال در کانال و مدیریت اعضا)
|
|
model ConversationRoomMember {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
externalRoomId String
|
|
userId String
|
|
/// admin | member
|
|
role String @default("member")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, externalRoomId, userId])
|
|
@@index([organizationId, externalRoomId])
|
|
}
|
|
|
|
/// بیصدا کردن گفتگو برای کاربر — با استثنای منشن
|
|
model ConversationRoomUserMute {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
userId String
|
|
externalRoomId String
|
|
/// اگر true باشد منشن همچنان اعلان/خواندهنشده میسازد
|
|
allowMentions Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, userId, externalRoomId])
|
|
@@index([organizationId, userId])
|
|
}
|
|
|
|
/// واکنش ایموجی روی پیام
|
|
model ConversationMessageReaction {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
externalRoomId String
|
|
messageId String
|
|
emoji String
|
|
userId String
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([organizationId, externalRoomId, messageId, userId, emoji])
|
|
@@index([organizationId, externalRoomId])
|
|
}
|