1204 lines
41 KiB
Plaintext
1204 lines
41 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 {
|
|
deal_won
|
|
ticket_resolved
|
|
}
|
|
|
|
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"])
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
users User[]
|
|
companies Company[]
|
|
people Person[]
|
|
pipelines Pipeline[]
|
|
deals Deal[]
|
|
activities Activity[]
|
|
notes Note[]
|
|
customFields CustomFieldDefinition[]
|
|
products Product[]
|
|
notifications Notification[]
|
|
noteAttachments NoteAttachment[]
|
|
leadForms LeadForm[]
|
|
quotes Quote[]
|
|
invoices Invoice[]
|
|
salesGoals SalesGoal[]
|
|
exchangeRates ExchangeRate[]
|
|
tickets Ticket[]
|
|
faqArticles FaqArticle[]
|
|
portalOtps PortalOtp[]
|
|
projectBoards ProjectBoard[]
|
|
projectCards ProjectCard[]
|
|
loyaltyTiers LoyaltyTier[]
|
|
loyaltySettings LoyaltySettings?
|
|
loyaltyLedgers LoyaltyLedger[]
|
|
loyaltyRewards LoyaltyReward[]
|
|
loyaltyRedemptions LoyaltyRedemption[]
|
|
automationRules AutomationRule[]
|
|
memoryItems MemoryItem[]
|
|
noteAiSuggestions NoteAiSuggestion[]
|
|
accountingConnections AccountingConnection[]
|
|
accountingSyncJobs AccountingSyncJob[]
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String
|
|
/// موبایل نرمالشده (مثلاً 9121234567) — برای ورود با موبایل
|
|
mobile String?
|
|
passwordHash String
|
|
fullName String
|
|
role UserRole @default(sales)
|
|
emailVerifiedAt DateTime?
|
|
mobileVerifiedAt DateTime?
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
ownedDeals Deal[] @relation("DealOwner")
|
|
ownedQuotes Quote[] @relation("QuoteOwner")
|
|
ownedInvoices Invoice[] @relation("InvoiceOwner")
|
|
ownedPeople Person[] @relation("PersonOwner")
|
|
ownedCompanies Company[] @relation("CompanyOwner")
|
|
ownedProjectCards ProjectCard[] @relation("ProjectCardOwner")
|
|
activities Activity[] @relation("ActivityAssignee")
|
|
createdActivities Activity[] @relation("ActivityCreator")
|
|
notes Note[] @relation("NoteAuthor")
|
|
noteAttachments NoteAttachment[] @relation("NoteAttachmentUploader")
|
|
notifications Notification[]
|
|
salesGoals SalesGoal[] @relation("SalesGoalUser")
|
|
assignedTickets Ticket[] @relation("TicketAssignee")
|
|
reportedTickets Ticket[] @relation("TicketReporter")
|
|
ticketMessages TicketMessage[] @relation("TicketMessageAuthor")
|
|
confirmedMemories MemoryItem[] @relation("MemoryConfirmedBy")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, email])
|
|
@@unique([mobile])
|
|
@@index([organizationId])
|
|
@@index([email])
|
|
}
|
|
|
|
model Company {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
name String
|
|
nationalId String?
|
|
phone String?
|
|
email String?
|
|
website String?
|
|
address String?
|
|
notes String?
|
|
customFields Json @default("{}")
|
|
people Person[]
|
|
deals Deal[]
|
|
activities Activity[]
|
|
smartNotes Note[]
|
|
quotes Quote[]
|
|
invoices Invoice[]
|
|
ownerId String?
|
|
owner User? @relation("CompanyOwner", fields: [ownerId], references: [id], onDelete: SetNull)
|
|
tickets Ticket[]
|
|
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?
|
|
notes String?
|
|
mood PersonMood @default(newcomer)
|
|
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[]
|
|
ownerId String?
|
|
owner User? @relation("PersonOwner", fields: [ownerId], references: [id], onDelete: SetNull)
|
|
tickets Ticket[]
|
|
projectCards ProjectCard[]
|
|
memoryItems MemoryItem[]
|
|
accountingLinks PersonAccountingLink[]
|
|
accountingDocuments AccountingDocumentLog[]
|
|
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)
|
|
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?
|
|
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)
|
|
attachments NoteAttachment[]
|
|
aiSuggestion NoteAiSuggestion?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([organizationId, pinned, updatedAt])
|
|
@@index([organizationId, noteType, updatedAt])
|
|
@@index([organizationId, reminderAt])
|
|
@@index([personId])
|
|
@@index([companyId])
|
|
@@index([dealId])
|
|
@@index([activityId])
|
|
@@index([authorId])
|
|
}
|
|
|
|
model NoteAttachment {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
noteId String
|
|
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
|
fileName String
|
|
mimeType String
|
|
sizeBytes Int
|
|
storageKey String
|
|
uploadedById String?
|
|
uploadedBy User? @relation("NoteAttachmentUploader", fields: [uploadedById], references: [id], onDelete: SetNull)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([noteId, createdAt])
|
|
@@index([organizationId])
|
|
@@index([uploadedById])
|
|
}
|
|
|
|
model 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[]
|
|
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)
|
|
createPerson Boolean @default(true)
|
|
createDeal Boolean @default(true)
|
|
pipelineId String?
|
|
source String?
|
|
thankYouMessage String?
|
|
redirectUrl String?
|
|
assigneeIds String[] @default([])
|
|
roundRobinIndex Int @default(0)
|
|
submitLabel String @default("ارسال درخواست")
|
|
submitColor String @default("#14b8a6")
|
|
coverStorageKey String?
|
|
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?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([formId, createdAt])
|
|
}
|
|
|
|
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[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([organizationId, number])
|
|
@@index([organizationId, status, updatedAt])
|
|
@@index([personId])
|
|
@@index([companyId])
|
|
@@index([dealId])
|
|
@@index([quoteId])
|
|
@@index([ownerId])
|
|
}
|
|
|
|
model InvoiceLineItem {
|
|
id String @id @default(cuid())
|
|
invoiceId String
|
|
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
|
productId String?
|
|
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
|
|
name String
|
|
quantity Decimal @default(1) @db.Decimal(18, 2)
|
|
unitPrice Decimal @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
position Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([invoiceId, position])
|
|
@@index([productId])
|
|
}
|
|
|
|
model Payment {
|
|
id String @id @default(cuid())
|
|
invoiceId String
|
|
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
|
amount Decimal @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
method PaymentMethod @default(transfer)
|
|
paidAt DateTime @default(now())
|
|
notes String?
|
|
installmentId String? @unique
|
|
installment InvoiceInstallment? @relation(fields: [installmentId], references: [id], onDelete: SetNull)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([invoiceId, paidAt])
|
|
}
|
|
|
|
model InvoiceInstallment {
|
|
id String @id @default(cuid())
|
|
invoiceId String
|
|
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
|
position Int @default(0)
|
|
dueDate DateTime
|
|
amount Decimal @db.Decimal(18, 2)
|
|
currency String @default("IRR")
|
|
status InstallmentStatus @default(pending)
|
|
paidAt DateTime?
|
|
payment Payment?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([invoiceId, position])
|
|
@@index([invoiceId, dueDate])
|
|
@@index([status, dueDate])
|
|
}
|
|
|
|
model SalesGoal {
|
|
id String @id @default(cuid())
|
|
organizationId String
|
|
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
/// null = هدف سازمانی / تیمی
|
|
userId String?
|
|
user User? @relation("SalesGoalUser", fields: [userId], references: [id], onDelete: Cascade)
|
|
title String
|
|
/// 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?
|
|
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)
|
|
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])
|
|
}
|
|
|
|
/// چالش 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[]
|
|
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])
|
|
}
|
|
|
|
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])
|
|
}
|