Files
nademitex_server/runtime/schema.prisma
T

891 lines
25 KiB
Plaintext
Raw Normal View History

2026-09-10 17:24:13 +03:30
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum SettlementType {
cash
check
2026-10-01 21:02:58 +03:30
cash_check
2026-09-10 17:24:13 +03:30
}
enum CheckRegistrationMode {
full
brief
}
enum OrderStatus {
cart
awaiting_settlement
cash_in_progress
check_in_progress
submitted
awaiting_review
pricing
needs_correction
awaiting_settlement_fix
cash_invoice_issued
awaiting_cash_payment
cash_payment_submitted
awaiting_cash_confirmation
cash_confirmed
awaiting_check_review
check_needs_fix
check_rejected
checks_approved
final_approved
preparing
ready_to_ship
shipped
delivered
cancelled
rejected
expired
}
enum CheckStatus {
pending_review
approved
rejected
}
enum CashPaymentStatus {
pending_review
approved
rejected
expired
}
enum ShipmentStatus {
preparing
ready
shipped
delivered
}
enum CustomerType {
wholesale
retail
}
enum AppUserRole {
customer
agent
}
enum InteractionType {
in_person
call
message
note
}
2026-10-10 10:35:56 +03:30
enum SalesPipelineStage {
negotiation
awaiting_catalog
awaiting_price
ready_to_order
}
enum OpportunityOutcome {
open
won
lost
}
enum LostReason {
price
product
supply_time
customer_decision
other
}
enum TimelineEventSource {
manual
system
}
enum TimelineEventKind {
meeting
call
note
reminder
next_action
order_created
price_announced
price_confirmed
catalog_requested
}
enum AssignmentRequestStatus {
pending
approved
rejected
}
2026-09-10 17:24:13 +03:30
enum ProductSeason {
summer
winter
all_season
}
enum ProductUsageCategory {
trousers
suit
shirt
other
}
enum AdminRole {
admin
sales
finance
warehouse
}
enum NotificationTone {
info
warning
success
danger
}
/// گردش کالیته فیزیکی (§۷ سند)
enum CatalogRequestStatus {
new
approved
ready_to_ship
delivered
returned
cancelled
}
enum CatalogItemHolderType {
warehouse
customer
agent
}
model Company {
id String @id @default(cuid())
name String
slug String @unique
description String?
logoUrl String?
isActive Boolean @default(true)
displayOrder Int @default(0)
/// خالی یعنی از تنظیمات سیستم ارث می‌برد
maxMeteragePerInvoice Int?
cashInvoiceValidityHours Int @default(8)
cashInvoiceValidUntilHour Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bankAccounts BankAccount[]
products Product[]
orders Order[]
customers Customer[]
}
model BankAccount {
id String @id @default(cuid())
companyId String
bankName String
accountHolder String
accountNumber String
cardNumber String
iban String
notes String?
isActive Boolean @default(true)
displayOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
cashReceipts CashPaymentReceipt[]
@@index([companyId])
}
model Product {
2026-09-14 16:32:03 +03:30
id String @id @default(cuid())
2026-09-10 17:24:13 +03:30
companyId String
name String
code String
color String
description String?
2026-10-01 21:02:58 +03:30
/// مشخصات آزاد محصول (نمایش در specs-extra اپ)
specs String? @db.Text
2026-09-14 16:32:03 +03:30
minMeterage Float @default(30)
orderMultiplier Float @default(10)
/// قیمت هر متر (تومان) برای نمایش کاتالوگ / پیش‌فاکتور
2026-09-10 17:24:13 +03:30
basePrice Float?
stock Float?
2026-09-14 16:32:03 +03:30
isActive Boolean @default(true)
2026-09-10 17:24:13 +03:30
/// فیلدهای نمایش کاتالوگ موکاپ (اپ کاربر)
2026-09-14 16:32:03 +03:30
usageCategory String @default("other")
season String @default("all_season")
2026-09-10 17:24:13 +03:30
widthCm Float?
composition String?
weightGrams Float?
weaveType String?
colorsJson Json?
tagsJson Json?
imageUrlsJson Json?
subtitle String?
2026-09-14 16:32:03 +03:30
rollMeters Float @default(50)
isNew Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
2026-09-10 17:24:13 +03:30
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
cartItems CartItem[]
orderItems OrderItem[]
favorites ProductFavorite[]
catalogRequestItems CatalogRequestItem[]
productSuggestions ProductSuggestion[]
@@unique([companyId, code])
@@index([companyId])
@@index([usageCategory])
@@index([season])
}
model Customer {
id String @id @default(cuid())
firstName String
lastName String
phone String @unique
nationalId String?
customerType CustomerType @default(wholesale)
/// نقش در اپ کاربر: مشتری عادی یا واسطه
appRole AppUserRole @default(customer)
agentCode String?
businessName String?
levelLabel String?
nextLevelLabel String?
pointsToNext Int?
levelProgress Int? @default(0)
assignedAgentId String?
avatarLetter String?
avatarColor String?
avatarUrl String?
2026-09-19 17:49:02 +03:30
/// آدرس واسطه (در پروفایل اپ کاربر)
address String?
2026-09-10 17:24:13 +03:30
companyId String?
levelDefId String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
2026-10-10 10:35:56 +03:30
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
levelDef CustomerLevelDef? @relation(fields: [levelDefId], references: [id], onDelete: SetNull)
assignedAgent Customer? @relation("AgentCustomers", fields: [assignedAgentId], references: [id], onDelete: SetNull)
agentCustomers Customer[] @relation("AgentCustomers")
carts Cart[]
orders Order[]
notifications CustomerNotification[]
meetings MeetingInteraction[] @relation("MeetingCustomer")
meetingsAsAgent MeetingInteraction[] @relation("MeetingAgent")
reminders Reminder[] @relation("ReminderOwner")
remindersAbout Reminder[] @relation("ReminderCustomer")
favorites ProductFavorite[]
catalogRequests CatalogRequest[]
productSuggestions ProductSuggestion[]
addresses CustomerAddress[]
salesOpportunities SalesOpportunity[]
timelineEvents CustomerTimelineEvent[]
crmNextActions CrmNextAction[]
assignmentRequests CustomerAssignmentRequest[] @relation("AssignmentCustomer")
assignmentFrom CustomerAssignmentRequest[] @relation("AssignmentFromAgent")
assignmentTo CustomerAssignmentRequest[] @relation("AssignmentToAgent")
assignmentRequested CustomerAssignmentRequest[] @relation("AssignmentRequestedBy")
opportunitiesOwned SalesOpportunity[] @relation("OpportunityAgent")
2026-09-10 17:24:13 +03:30
@@index([companyId])
@@index([appRole])
@@index([assignedAgentId])
@@index([agentCode])
@@index([levelDefId])
}
model CustomerAddress {
id String @id @default(cuid())
customerId String
label String?
text String
isDefault Boolean @default(false)
createdAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
@@index([customerId])
}
2026-09-10 17:24:13 +03:30
model AdminUser {
id String @id @default(cuid())
fullName String
phone String @unique
email String @unique
passwordHash String
role AdminRole @default(admin)
isActive Boolean @default(true)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model OtpChallenge {
id String @id @default(cuid())
phone String
code String
firstName String?
lastName String?
isRegister Boolean @default(false)
assignedAgentId String?
attempts Int @default(0)
expiresAt DateTime
createdAt DateTime @default(now())
2026-09-10 17:24:13 +03:30
@@index([phone])
}
model Cart {
id String @id @default(cuid())
userId String
companyId String?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user Customer @relation(fields: [userId], references: [id], onDelete: Cascade)
items CartItem[]
@@index([userId])
}
model CartItem {
id String @id @default(cuid())
cartId String
productId String
catalogCode String
productName String
productCode String
color String
meterage Float
unitPrice Float?
notes String?
companyId String
companyName String
cart Cart @relation(fields: [cartId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Restrict)
@@unique([cartId, productId])
@@index([cartId])
}
model Order {
id String @id @default(cuid())
orderNumber String @unique
userId String
companyId String
companyName String
// روش تسویه بعد از قیمت‌گذاری ادمین توسط مشتری انتخاب می‌شود
settlementType SettlementType?
checkMode CheckRegistrationMode?
status OrderStatus @default(awaiting_review)
totalMeterage Float @default(0)
totalAmount Float?
shippingCost Float?
totalDiscount Float?
finalAmount Float?
notes String?
checkRassDays Int?
checkRassDate DateTime?
cashInvoiceExpiresAt DateTime?
/// زمان ارسال یادآوری پایان مهلت پرداخت تا دوباره ارسال نشود
cashReminderSentAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
2026-10-10 10:35:56 +03:30
user Customer @relation(fields: [userId], references: [id], onDelete: Restrict)
company Company @relation(fields: [companyId], references: [id], onDelete: Restrict)
items OrderItem[]
checks Check[]
cashReceipt CashPaymentReceipt?
shipment Shipment?
statusHistory OrderStatusHistory[]
notifications CustomerNotification[]
wonOpportunities SalesOpportunity[]
2026-09-10 17:24:13 +03:30
@@index([userId])
@@index([companyId])
@@index([status])
@@index([createdAt])
@@index([status, cashInvoiceExpiresAt])
}
model OrderItem {
id String @id @default(cuid())
orderId String
productId String?
catalogCode String
productName String
productCode String?
color String
requestedMeterage Float
confirmedMeterage Float?
unitPrice Float?
unitDiscount Float? @default(0)
lineTotal Float?
notes String?
companyId String
companyName String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
@@index([orderId])
@@index([companyId])
}
model OrderStatusHistory {
id String @id @default(cuid())
orderId String
fromStatus OrderStatus?
toStatus OrderStatus
changedBy String
note String?
reason String?
changedAt DateTime @default(now())
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
@@index([orderId])
}
model Check {
id String @id @default(cuid())
orderId String
row Int
amount Float
dueDate DateTime
sayadId String?
checkNumber String?
ownerNationalId String?
ownerFirstName String?
ownerLastName String?
bankName String?
branchName String?
frontImage String?
backImage String?
notes String?
mode CheckRegistrationMode @default(full)
status CheckStatus @default(pending_review)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
@@index([orderId])
@@index([dueDate])
}
model CashPaymentReceipt {
id String @id @default(cuid())
orderId String @unique
amount Float
paidAmount Float?
transferDate DateTime?
transferTime String?
trackingNumber String?
destinationAccountId String?
receiptImage String?
notes String?
expiresAt DateTime?
status CashPaymentStatus @default(pending_review)
submittedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
destinationAccount BankAccount? @relation(fields: [destinationAccountId], references: [id], onDelete: SetNull)
}
model Shipment {
id String @id @default(cuid())
orderId String @unique
carrier String
trackingNumber String?
meterage Float
status ShipmentStatus @default(preparing)
shippedAt DateTime?
deliveredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
}
model SystemSettings {
2026-10-01 21:02:58 +03:30
id String @id @default("default")
maxMeteragePerInvoice Int @default(5000)
cashInvoiceValidityHours Int @default(8)
defaultSettlementCheck Boolean @default(true)
defaultSettlementCash Boolean @default(true)
defaultSettlementCashCheck Boolean @default(false)
2026-09-10 17:24:13 +03:30
/// حداقل سفارش: تعداد طاقه از هر محصول (§۱۹)
2026-10-01 21:02:58 +03:30
minOrderRollsPerProduct Int @default(1)
2026-09-10 17:24:13 +03:30
/// نمایش قیمت عمومی در کاتالوگ (پیش‌فرض خاموش)
2026-10-01 21:02:58 +03:30
showCatalogPublicPrice Boolean @default(false)
2026-09-10 17:24:13 +03:30
/// اجازه قیمت‌گذاری توسط واسطه (§۱۹)
2026-10-01 21:02:58 +03:30
allowAgentPricing Boolean @default(false)
2026-09-10 17:24:13 +03:30
/// کف قیمت واحد برای قیمت‌گذاری واسطه (ریال) — اختیاری
2026-10-01 21:02:58 +03:30
agentMinUnitPrice Int?
smsOrderRegistered Boolean @default(true)
smsPricingReady Boolean @default(true)
smsPaymentReminder Boolean @default(true)
smsCheckReview Boolean @default(true)
smsShippingUpdate Boolean @default(true)
supportPhone String?
supportEmail String?
supportWhatsapp String?
supportTelegram String?
supportInstagram String?
2026-09-10 17:24:13 +03:30
/// تصاویر دسته‌های کاربرد/فصل اپ کاربر: { trousers: dataUrl, summer: dataUrl, ... }
2026-10-01 21:02:58 +03:30
categoryImagesJson Json?
2026-09-14 16:32:03 +03:30
/// تعاریف دسته‌ها (کاربرد/فصل). اگر خالی باشد از مقادیر پیش‌فرض کد استفاده می‌شود.
2026-10-01 21:02:58 +03:30
categoryDefsJson Json?
updatedAt DateTime @updatedAt
2026-09-10 17:24:13 +03:30
}
/// بنرهای قابل مدیریت بخش‌های اپ کاربر
model AppBanner {
id String @id @default(cuid())
slot String
title String
subtitle String?
ctaLabel String?
ctaLink String?
2026-10-01 21:02:58 +03:30
/// لینک کلیک روی تصویر بنر (مستقل از لینک دکمه)
bannerLink String?
2026-09-10 17:24:13 +03:30
imageUrl String?
displayOrder Int @default(0)
isActive Boolean @default(true)
2026-09-14 16:32:03 +03:30
showTitle Boolean @default(true)
showSubtitle Boolean @default(true)
showCta Boolean @default(true)
2026-09-10 17:24:13 +03:30
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slot, isActive, displayOrder])
}
model AdminNotification {
id String @id @default(cuid())
title String
body String
tone NotificationTone? @default(info)
link String?
isRead Boolean @default(false)
sentAt DateTime @default(now())
}
model CustomerNotification {
id String @id @default(cuid())
customerId String
orderId String?
event String
title String
body String
tone NotificationTone? @default(info)
link String?
isRead Boolean @default(false)
smsSent Boolean @default(false)
sentAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
order Order? @relation(fields: [orderId], references: [id], onDelete: SetNull)
@@index([customerId, isRead])
@@index([sentAt])
}
model InternalMessage {
id String @id @default(cuid())
senderName String
subject String
preview String
link String?
2026-09-10 17:24:13 +03:30
isRead Boolean @default(false)
sentAt DateTime @default(now())
}
model MeetingInteraction {
id String @id @default(cuid())
customerId String
agentId String
type InteractionType @default(in_person)
title String
description String?
subject String?
content String?
date String
time String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
customer Customer @relation("MeetingCustomer", fields: [customerId], references: [id], onDelete: Cascade)
agent Customer @relation("MeetingAgent", fields: [agentId], references: [id], onDelete: Cascade)
@@index([customerId])
@@index([agentId])
@@index([date])
}
model Reminder {
id String @id @default(cuid())
ownerId String
customerId String
title String
description String?
note String?
date String
time String
interactionType InteractionType @default(note)
completed Boolean @default(false)
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
owner Customer @relation("ReminderOwner", fields: [ownerId], references: [id], onDelete: Cascade)
customer Customer @relation("ReminderCustomer", fields: [customerId], references: [id], onDelete: Cascade)
@@index([ownerId, completed])
@@index([customerId])
@@index([date])
}
2026-10-10 10:35:56 +03:30
model SalesOpportunity {
id String @id @default(cuid())
customerId String
stage SalesPipelineStage @default(negotiation)
outcome OpportunityOutcome @default(open)
lostReason LostReason?
lostNote String?
lostAt DateTime?
wonOrderId String?
wonAt DateTime?
assignedAgentId String?
ownerAdminId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
assignedAgent Customer? @relation("OpportunityAgent", fields: [assignedAgentId], references: [id], onDelete: SetNull)
wonOrder Order? @relation(fields: [wonOrderId], references: [id], onDelete: SetNull)
nextActions CrmNextAction[]
@@index([customerId])
@@index([stage, outcome])
@@index([assignedAgentId])
}
model CustomerTimelineEvent {
id String @id @default(cuid())
customerId String
kind TimelineEventKind
source TimelineEventSource @default(manual)
title String
body String?
occurredAt DateTime @default(now())
actorAdminId String?
actorCustomerId String?
relatedOrderId String?
relatedMeetingId String?
relatedActionId String?
createdAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
@@index([customerId, occurredAt])
}
model CrmNextAction {
id String @id @default(cuid())
customerId String
opportunityId String?
title String
description String?
dueAt DateTime
completed Boolean @default(false)
completedAt DateTime?
assigneeAdminId String?
assigneeAgentId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
opportunity SalesOpportunity? @relation(fields: [opportunityId], references: [id], onDelete: SetNull)
@@index([customerId, completed])
@@index([dueAt])
}
model CustomerAssignmentRequest {
id String @id @default(cuid())
customerId String
fromAgentId String?
toAgentId String
requestedByAgentId String
status AssignmentRequestStatus @default(pending)
reviewNote String?
reviewedByAdminId String?
reviewedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
customer Customer @relation("AssignmentCustomer", fields: [customerId], references: [id], onDelete: Cascade)
fromAgent Customer? @relation("AssignmentFromAgent", fields: [fromAgentId], references: [id], onDelete: SetNull)
toAgent Customer @relation("AssignmentToAgent", fields: [toAgentId], references: [id], onDelete: Cascade)
requestedByAgent Customer @relation("AssignmentRequestedBy", fields: [requestedByAgentId], references: [id], onDelete: Cascade)
@@index([status])
@@index([customerId])
}
2026-09-10 17:24:13 +03:30
model ProductFavorite {
id String @id @default(cuid())
customerId String
productId String
createdAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
@@unique([customerId, productId])
@@index([customerId])
}
model CustomerLevelDef {
id String @id @default(cuid())
code String @unique
label String
sortOrder Int @default(0)
criteriaJson Json?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
benefits LevelBenefit[]
customers Customer[]
}
model LevelBenefit {
id String @id @default(cuid())
levelId String
title String
description String?
level CustomerLevelDef @relation(fields: [levelId], references: [id], onDelete: Cascade)
@@index([levelId])
}
model CatalogRequest {
id String @id @default(cuid())
customerId String
status CatalogRequestStatus @default(new)
note String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
items CatalogRequestItem[]
@@index([customerId])
@@index([status])
}
model CatalogRequestItem {
id String @id @default(cuid())
requestId String
productId String
quantity Int @default(1)
holderType CatalogItemHolderType @default(warehouse)
holderCustomerId String?
request CatalogRequest @relation(fields: [requestId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Restrict)
movements CatalogItemMovement[]
@@index([requestId])
@@index([productId])
}
model CatalogItemMovement {
id String @id @default(cuid())
itemId String
fromHolder String
toHolder String
note String?
movedAt DateTime @default(now())
item CatalogRequestItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
@@index([itemId])
}
model ProductSuggestion {
id String @id @default(cuid())
customerId String
productId String
note String?
createdByAdminId String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
@@index([customerId, isActive])
@@index([productId])
}
/// یک ردیف برای تعرفه، موجودی و اطلاعات واریز پیامک
model SmsWallet {
id String @id @default("default")
balanceToman Int @default(0)
pricePerPart Int @default(250)
blocked Boolean @default(false)
cardNumber String @default("6219861941033817")
sheba String @default("IR805606118280056043604401")
accountName String @default("حسین منتظر")
yellowThreshold Int @default(10000)
redThreshold Int @default(1000)
updatedAt DateTime @updatedAt
}
model SmsUsage {
id String @id @default(cuid())
parts Int
costToman Int
createdAt DateTime @default(now())
@@index([createdAt])
}
model SmsTopUp {
id String @id @default(cuid())
amountToman Int
note String?
createdAt DateTime @default(now())
}