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 } 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 } 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[] catalogs Catalog[] 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 Catalog { id String @id @default(cuid()) companyId String name String code String description String? displayOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) products Product[] @@unique([companyId, code]) @@index([companyId]) } model Product { id String @id @default(cuid()) companyId String catalogId String name String code String color String description String? minMeterage Float @default(30) orderMultiplier Float @default(10) basePrice Float? stock Float? isActive Boolean @default(true) /// فیلدهای نمایش کاتالوگ موکاپ (اپ کاربر) usageCategory ProductUsageCategory @default(other) season ProductSeason @default(all_season) widthCm Float? composition String? weightGrams Float? weaveType String? colorsJson Json? tagsJson Json? imageUrlsJson Json? subtitle String? rollMeters Float @default(50) isNew Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) catalog Catalog @relation(fields: [catalogId], references: [id], onDelete: Cascade) cartItems CartItem[] orderItems OrderItem[] favorites ProductFavorite[] catalogRequestItems CatalogRequestItem[] stockNotifyRequests StockNotifyRequest[] productSuggestions ProductSuggestion[] @@unique([companyId, code]) @@index([catalogId]) @@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? companyId String? levelDefId String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt 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[] stockNotifyRequests StockNotifyRequest[] productSuggestions ProductSuggestion[] customerBrands CustomerBrand[] @@index([companyId]) @@index([appRole]) @@index([assignedAgentId]) @@index([agentCode]) @@index([levelDefId]) } 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) attempts Int @default(0) expiresAt DateTime createdAt DateTime @default(now()) @@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 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[] @@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 { id String @id @default("default") maxMeteragePerInvoice Int @default(5000) cashInvoiceValidityHours Int @default(8) defaultSettlementCheck Boolean @default(true) defaultSettlementCash Boolean @default(true) /// حداقل سفارش: تعداد طاقه از هر محصول (§۱۹) minOrderRollsPerProduct Int @default(1) /// نمایش قیمت عمومی در کاتالوگ (پیش‌فرض خاموش) showCatalogPublicPrice Boolean @default(false) /// اجازه قیمت‌گذاری توسط واسطه (§۱۹) allowAgentPricing Boolean @default(false) /// کف قیمت واحد برای قیمت‌گذاری واسطه (ریال) — اختیاری 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? /// تصاویر دسته‌های کاربرد/فصل اپ کاربر: { trousers: dataUrl, summer: dataUrl, ... } categoryImagesJson Json? updatedAt DateTime @updatedAt } /// بنرهای قابل مدیریت بخش‌های اپ کاربر model AppBanner { id String @id @default(cuid()) slot String title String subtitle String? ctaLabel String? ctaLink String? imageUrl String? displayOrder Int @default(0) isActive Boolean @default(true) 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 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]) } 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 Brand { id String @id @default(cuid()) name String slug String? @unique description String? isActive Boolean @default(true) displayOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt customers CustomerBrand[] } model CustomerBrand { customerId String brandId String createdAt DateTime @default(now()) customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade) @@id([customerId, brandId]) @@index([brandId]) } 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 StockNotifyRequest { id String @id @default(cuid()) customerId String productId String active Boolean @default(true) createdAt DateTime @default(now()) notifiedAt DateTime? customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) product Product @relation(fields: [productId], references: [id], onDelete: Cascade) @@unique([customerId, productId]) @@index([productId, active]) } 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]) }