generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Organization { id String @id @default(uuid()) name String wabaId String? metaBusinessId String? systemUserToken String? systemUserTokenIssuedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt slug String? @unique // For subdomains like polytech.xamle.studio brandingData Json? customPrompt String? personalityConfig Json? flowConfig Json? knowledgeBaseUrl String? mode OrganizationMode @default(EDTECH) useCase OrganizationUseCase @default(EDUCATION) isCrmActive Boolean @default(false) isEdTechActive Boolean @default(false) webhookSecret String? webhookUrl String? openAiApiKey String? googleAiApiKey String? subscriptionStatus String? @default("ACTIVE") subscriptionPlan SubscriptionPlan @default(STARTER) stripeCustomerId String? @unique stripeSubscriptionId String? @unique aiCreditsLimit Int @default(500) aiCreditsUsed Int @default(0) whatsappMessagesSent Int @default(0) billingPeriodStart DateTime @default(now()) walletBalance Int @default(0) isHardStopped Boolean @default(false) businessProfiles BusinessProfile[] usageEvents UsageEvent[] walletTransactions WalletTransaction[] enrollments Enrollment[] kbEntries KnowledgeBaseEntry[] messages Message[] payments Payment[] responses Response[] teamMembers TeamMember[] tracks Track[] trackDays TrackDay[] users User[] userBadges UserBadge[] progress UserProgress[] phoneNumbers WhatsAppPhoneNumber[] analytics AnalyticsLog[] campaigns CampaignHistory[] contacts Contact[] broadcastLists BroadcastList[] } model Contact { id String @id @default(uuid()) phoneNumber String name String? attributes Json? // For dynamic Excel columns tags String[] @default([]) organizationId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id]) campaigns CampaignHistory[] broadcastLists BroadcastList[] @relation("ContactBroadcastLists") messages Message[] language Language @default(FR) @@unique([phoneNumber, organizationId]) @@index([organizationId]) } model BroadcastList { id String @id @default(uuid()) name String organizationId String organization Organization @relation(fields: [organizationId], references: [id]) contacts Contact[] @relation("ContactBroadcastLists") createdAt DateTime @default(now()) @@index([organizationId]) } // ... existing models ... model CampaignHistory { id String @id @default(uuid()) organizationId String contactId String whatsappMessageId String? @unique // ID returned by Meta content String status CampaignStatus @default(SENT) error String? sentAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id]) contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade) analytics AnalyticsLog[] @@index([organizationId]) @@index([contactId]) @@index([whatsappMessageId]) } model AnalyticsLog { id String @id @default(uuid()) organizationId String campaignHistoryId String eventType AnalyticsEventType metadata Json? occurredAt DateTime @default(now()) organization Organization @relation(fields: [organizationId], references: [id]) campaign CampaignHistory @relation(fields: [campaignHistoryId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([campaignHistoryId]) @@index([eventType]) } // ... existing enums ... model KnowledgeBaseEntry { id String @id @default(uuid()) organizationId String content String embedding Unsupported("vector")? metadata Json? createdAt DateTime @default(now()) organization Organization @relation(fields: [organizationId], references: [id]) @@index([organizationId]) } model WhatsAppPhoneNumber { id String @id displayPhone String organizationId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id]) } model User { id String @id @default(uuid()) phone String? // Made optional for Email-based admins name String? role Role @default(STUDENT) language Language @default(FR) city String? activity String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt email String? passwordHash String? currentStreak Int @default(0) longestStreak Int @default(0) lastActivityAt DateTime? deletedAt DateTime? organizationId String businessProfile BusinessProfile? enrollments Enrollment[] messages Message[] payments Payment[] responses Response[] organization Organization @relation(fields: [organizationId], references: [id]) progress UserProgress[] pushSubscriptions PushSubscription[] @@unique([phone, organizationId]) @@unique([email, organizationId]) @@index([organizationId]) @@index([phone]) @@index([email]) } model PushSubscription { id String @id @default(uuid()) userId String organizationId String endpoint String @unique p256dh String auth String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([userId]) } model BusinessProfile { id String @id @default(uuid()) userId String @unique activityLabel String? activityPhrase String? activityType String? locationCity String? mainCustomer String? mainProblem String? offerSimple String? promise String? marketData Json? competitorList Json? financialProjections Json? fundingAsk String? lastUpdatedFromDay Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organizationId String organization Organization @relation(fields: [organizationId], references: [id]) user User @relation(fields: [userId], references: [id]) teamMembersList TeamMember[] @@index([organizationId]) } model TeamMember { id String @id @default(uuid()) businessProfileId String name String? role String? bio String? photoUrl String? organizationId String businessProfile BusinessProfile @relation(fields: [businessProfileId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id]) @@index([organizationId]) } model Track { id String @id @default(uuid()) title String description String? duration Int language Language @default(FR) isPremium Boolean @default(false) priceAmount Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organizationId String enrollments Enrollment[] payments Payment[] organization Organization @relation(fields: [organizationId], references: [id]) days TrackDay[] progress UserProgress[] @@index([organizationId]) } model TrackDay { id String @id @default(uuid()) trackId String dayNumber Int title String? audioUrl String? imageUrl String? videoUrl String? videoCaption String? lessonText String? exerciseType ExerciseType @default(TEXT) exercisePrompt String? validationKeyword String? buttonsJson Json? exerciseCriteria Json? badges Json? unlockCondition String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organizationId String organization Organization @relation(fields: [organizationId], references: [id]) track Track @relation(fields: [trackId], references: [id]) @@index([organizationId]) @@index([trackId, organizationId]) } model UserProgress { id String @id @default(uuid()) userId String trackId String score Int @default(0) lastInteraction DateTime @default(now()) exerciseStatus ExerciseStatus @default(PENDING) badges Json? @map("badges") behavioralScoring Json? confidenceScore Float? adminTranscription String? overrideAudioUrl String? reviewedBy String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt iterationCount Int @default(0) aiSource String? organizationId String userBadges UserBadge[] organization Organization @relation(fields: [organizationId], references: [id]) track Track @relation(fields: [trackId], references: [id]) user User @relation(fields: [userId], references: [id]) @@unique([userId, trackId]) @@index([organizationId]) @@index([organizationId, exerciseStatus]) } model Enrollment { id String @id @default(uuid()) userId String trackId String status EnrollmentStatus @default(ACTIVE) currentDay Int @default(1) startedAt DateTime @default(now()) completedAt DateTime? lastActivityAt DateTime @default(now()) deletedAt DateTime? organizationId String organization Organization @relation(fields: [organizationId], references: [id]) track Track @relation(fields: [trackId], references: [id]) user User @relation(fields: [userId], references: [id]) responses Response[] @@unique([userId, trackId]) @@index([organizationId]) @@index([userId]) } model Response { id String @id @default(uuid()) enrollmentId String userId String dayNumber Int content String? mediaUrl String? createdAt DateTime @default(now()) aiSource String? organizationId String enrollment Enrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id]) user User @relation(fields: [userId], references: [id]) @@index([organizationId]) @@index([userId]) @@index([enrollmentId]) } model Message { id String @id @default(uuid()) userId String? contactId String? direction Direction channel String @default("WHATSAPP") content String? mediaUrl String? mediaType MediaType? mediaId String? payload Json? createdAt DateTime @default(now()) status MessageStatus @default(SENT) organizationId String organization Organization @relation(fields: [organizationId], references: [id]) user User? @relation(fields: [userId], references: [id]) contact Contact? @relation(fields: [contactId], references: [id]) @@index([userId, createdAt]) @@index([contactId, createdAt]) @@index([organizationId, createdAt]) @@index([organizationId]) } model Payment { id String @id @default(uuid()) userId String trackId String amount Int currency String @default("XOF") status PaymentStatus @default(PENDING) paymentSessionId String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organizationId String organization Organization @relation(fields: [organizationId], references: [id]) track Track @relation(fields: [trackId], references: [id]) user User @relation(fields: [userId], references: [id]) @@index([organizationId]) } model TrainingData { id String @id @default(uuid()) audioUrl String transcription String manualCorrection String? rawWER Float? normalizedWER Float? status TrainingStatus @default(PENDING) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model NormalizationRule { id String @id @default(uuid()) original String @unique replacement String language String @default("WOLOF") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([original]) } model UserBadge { id String @id @default(uuid()) userProgressId String name String earnedAt DateTime @default(now()) organizationId String organization Organization @relation(fields: [organizationId], references: [id]) userProgress UserProgress @relation(fields: [userProgressId], references: [id], onDelete: Cascade) @@index([organizationId]) } enum Role { STUDENT ADMIN ORG_MEMBER ORG_ADMIN SUPER_ADMIN } enum OrganizationMode { EDTECH WEBHOOK AI_AGENT CRM_MARKETING PEDAGOGY CUSTOMER_SERVICE } enum OrganizationUseCase { EDUCATION CRM_WHATSAPP } enum EnrollmentStatus { ACTIVE COMPLETED DROPPED } enum Language { FR EN ES PT WOLOF } enum ContentType { TEXT AUDIO IMAGE VIDEO } enum MessageStatus { SENT DELIVERED READ RECEIVED } enum Direction { INBOUND OUTBOUND } enum PaymentStatus { PENDING COMPLETED FAILED REFUNDED } enum ResponseType { TEXT AUDIO VIDEO } enum MediaType { AUDIO IMAGE VIDEO DOCUMENT } enum ExerciseType { TEXT AUDIO BUTTON } enum ExerciseStatus { PENDING PENDING_REMEDIATION PENDING_REVIEW COMPLETED PENDING_DEEPDIVE } enum TrainingStatus { PENDING REVIEWED IGNORED } enum SubscriptionPlan { STARTER GROWTH SCALE ENTERPRISE } enum UsageEventType { AI_TEXT AI_AUDIO AI_IMAGE WHATSAPP_SENT } enum UsageProvider { GEMINI OPENAI META } enum UsageFeature { LESSON FEEDBACK DEEPDIVE TRANSCRIPTION IMAGE_ANALYSIS SPEECH CAMPAIGN KB_QUERY ONBOARDING OTHER } model UsageEvent { id String @id @default(uuid()) organizationId String userId String? type UsageEventType provider UsageProvider feature UsageFeature tokensIn Int @default(0) tokensOut Int @default(0) durationMs Int? costUsd Float @default(0) createdAt DateTime @default(now()) organization Organization @relation(fields: [organizationId], references: [id]) @@index([organizationId, createdAt]) @@index([organizationId, type]) } model WalletTransaction { id String @id @default(uuid()) organizationId String amount Int balanceAfter Int type WalletTransactionType description String? actorId String? byok Boolean @default(false) metadata Json? createdAt DateTime @default(now()) organization Organization @relation(fields: [organizationId], references: [id]) @@index([organizationId, createdAt]) @@index([type]) @@index([organizationId, type]) } enum WalletTransactionType { TOP_UP_MANUAL TOP_UP_PAYMENT DEBIT_AI DEBIT_WHATSAPP DEBIT_BROADCAST ADJUSTMENT } model AuditLog { id String @id @default(uuid()) action String actorId String? resourceId String? details Json? createdAt DateTime @default(now()) @@index([action]) @@index([actorId]) @@index([resourceId]) @@index([createdAt]) } enum CampaignStatus { SENT DELIVERED READ FAILED } enum AnalyticsEventType { CLICK READ RESPONSE }