generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) email String @unique passwordHash String fullName String? phone String? companyName String? addressLine1 String? addressLine2 String? city String? state String? postalCode String? country String? role String @default("user") emailVerified Boolean @default(false) twoFactorEnabled Boolean @default(false) twoFactorSecret String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt accounts Account[] rules Rule[] exports ExportLog[] exportDownloadTokens ExportDownloadToken[] auditLogs AuditLog[] abuseEvents AbuseEvent[] googleConnection GoogleConnection? apiKeys ApiKey[] emailVerificationToken EmailVerificationToken? passwordResetTokens PasswordResetToken[] refreshTokens RefreshToken[] sessions Session[] socialAccounts SocialAccount[] subscription Subscription? taxReturns TaxReturn[] csvImportMappings CsvImportMapping[] createdHouseholds Household[] @relation("HouseholdCreator") householdMemberships HouseholdMember[] sentHouseholdInvites HouseholdInvite[] @relation("HouseholdInviteInviter") acceptedHouseholdInvites HouseholdInvite[] @relation("HouseholdInviteAccepter") createdAccountantTasks AccountantTask[] @relation("AccountantTaskCreator") assignedAccountantTasks AccountantTask[] @relation("AccountantTaskAssignee") ownedAccounts Account[] @relation("AccountOwnerUser") createdHouseholdGoals HouseholdGoal[] @relation("HouseholdGoalCreator") personalGoals PersonalGoal[] investmentHoldings InvestmentHolding[] netWorthSnapshots NetWorthSnapshot[] recurringTransactions RecurringTransaction[] createdHouseholdBudgets HouseholdBudget[] @relation("HouseholdBudgetCreator") notificationPreferences NotificationPreference? notifications Notification[] pushSubscriptions PushSubscription[] billPayees BillPayee[] bills Bill[] billPayments BillPayment[] creditScoreEntries CreditScoreEntry[] transactionComments TransactionComment[] } model NotificationPreference { id String @id @default(uuid()) userId String @unique emailEnabled Boolean @default(true) pushEnabled Boolean @default(false) minSeverity String @default("info") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Notification { id String @id @default(uuid()) userId String type String severity String @default("info") title String body String metadata Json @default("{}") channels String[] @default([]) readAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, createdAt]) @@index([userId, readAt]) @@index([type, createdAt]) } model PushSubscription { id String @id @default(uuid()) userId String endpoint String @unique p256dh String auth String userAgent String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastUsedAt DateTime? revokedAt DateTime? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, revokedAt]) } model BillPayee { id String @id @default(uuid()) userId String name String nickname String? category String? website String? accountNumberLast4 String? metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) bills Bill[] @@index([userId, name]) } model Bill { id String @id @default(uuid()) userId String payeeId String? name String amount Decimal currency String @default("USD") dueDate DateTime status String @default("pending") recurrence String @default("none") autopay Boolean @default(false) reminderDays Int @default(3) notes String? metadata Json @default("{}") paidAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) payee BillPayee? @relation(fields: [payeeId], references: [id], onDelete: SetNull) payments BillPayment[] @@index([userId, dueDate]) @@index([userId, status]) @@index([payeeId]) } model BillPayment { id String @id @default(uuid()) userId String billId String amount Decimal paidAt DateTime @default(now()) method String @default("manual") status String @default("completed") provider String? providerPaymentId String? confirmationNumber String? notes String? metadata Json @default("{}") createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) bill Bill @relation(fields: [billId], references: [id], onDelete: Cascade) @@index([userId, paidAt]) @@index([userId, status]) @@index([provider, providerPaymentId]) @@index([billId]) } model CreditScoreEntry { id String @id @default(uuid()) userId String score Int bureau String @default("unknown") source String @default("manual") model String @default("vantage_score_3") scoreDate DateTime factors Json @default("{}") metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, scoreDate]) @@index([userId, bureau]) } model Household { id String @id @default(uuid()) name String createdByUserId String metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy User @relation("HouseholdCreator", fields: [createdByUserId], references: [id], onDelete: Cascade) members HouseholdMember[] invites HouseholdInvite[] accounts Account[] goals HouseholdGoal[] budgets HouseholdBudget[] accountantTasks AccountantTask[] @@index([createdByUserId, createdAt]) } model AccountantTask { id String @id @default(uuid()) householdId String createdByUserId String assignedToUserId String? title String description String? taskType String @default("review") status String @default("open") priority String @default("medium") dueDate DateTime? completedAt DateTime? metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) createdBy User @relation("AccountantTaskCreator", fields: [createdByUserId], references: [id], onDelete: Cascade) assignedTo User? @relation("AccountantTaskAssignee", fields: [assignedToUserId], references: [id], onDelete: SetNull) @@index([householdId, status]) @@index([assignedToUserId, status]) @@index([dueDate]) } model HouseholdBudget { id String @id @default(uuid()) householdId String createdByUserId String name String category String? period String @default("monthly") limitAmount Decimal spentAmount Decimal @default(0) isoCurrencyCode String @default("USD") startDate DateTime? endDate DateTime? status String @default("active") metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) createdBy User @relation("HouseholdBudgetCreator", fields: [createdByUserId], references: [id], onDelete: Cascade) @@index([householdId, status]) @@index([createdByUserId, createdAt]) } model PersonalGoal { id String @id @default(uuid()) userId String name String description String? targetAmount Decimal currentAmount Decimal @default(0) isoCurrencyCode String @default("USD") targetDate DateTime? priority String @default("medium") status String @default("active") automation Json @default("{}") metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, status]) @@index([targetDate]) } model InvestmentHolding { id String @id @default(uuid()) userId String accountId String? symbol String name String assetClass String @default("stock") quantity Decimal price Decimal marketValue Decimal costBasis Decimal? isoCurrencyCode String @default("USD") asOfDate DateTime source String @default("manual") metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) account Account? @relation(fields: [accountId], references: [id], onDelete: SetNull) @@index([userId, asOfDate]) @@index([userId, symbol]) } model NetWorthSnapshot { id String @id @default(uuid()) userId String snapshotDate DateTime assets Decimal liabilities Decimal netWorth Decimal isoCurrencyCode String @default("USD") source String @default("computed") breakdown Json @default("{}") createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, snapshotDate]) } model RecurringTransaction { id String @id @default(uuid()) userId String merchant String cadence String averageAmount Decimal isoCurrencyCode String @default("USD") nextExpectedDate DateTime? lastSeenDate DateTime? occurrenceCount Int @default(0) confidence Decimal @default(0) status String @default("active") source String @default("detected") metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, status]) @@index([userId, merchant]) } model HouseholdGoal { id String @id @default(uuid()) householdId String createdByUserId String name String description String? targetAmount Decimal currentAmount Decimal @default(0) isoCurrencyCode String @default("USD") targetDate DateTime? priority String @default("medium") status String @default("active") metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) createdBy User @relation("HouseholdGoalCreator", fields: [createdByUserId], references: [id], onDelete: Cascade) @@index([householdId, status]) @@index([createdByUserId, createdAt]) @@index([targetDate]) } model HouseholdMember { id String @id @default(uuid()) householdId String userId String role String @default("member") status String @default("active") joinedAt DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([householdId, userId]) @@index([userId, status]) @@index([householdId, role]) } model HouseholdInvite { id String @id @default(uuid()) householdId String invitedById String email String role String @default("member") tokenHash String @unique status String @default("pending") expiresAt DateTime acceptedAt DateTime? acceptedById String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt household Household @relation(fields: [householdId], references: [id], onDelete: Cascade) invitedBy User @relation("HouseholdInviteInviter", fields: [invitedById], references: [id], onDelete: Cascade) acceptedBy User? @relation("HouseholdInviteAccepter", fields: [acceptedById], references: [id], onDelete: SetNull) @@index([householdId, status]) @@index([email, status]) @@index([expiresAt]) } model SocialAccount { id String @id @default(uuid()) userId String provider String providerAccountId String email String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@index([userId]) } model Account { id String @id @default(uuid()) userId String householdId String? ownerUserId String? ownershipType String @default("mine") institutionName String accountType String mask String? plaidAccessToken String? plaidItemId String? plaidAccountId String? @unique tellerAccessToken String? tellerEnrollmentId String? tellerAccountId String? @unique currentBalance Decimal? availableBalance Decimal? isoCurrencyCode String? lastBalanceSync DateTime? lastTransactionSync DateTime? lastSyncAttemptAt DateTime? syncStatus String @default("idle") lastSyncError String? syncConsecutiveFailures Int @default(0) plaidWebhookCode String? plaidWebhookAt DateTime? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) household Household? @relation(fields: [householdId], references: [id], onDelete: SetNull) ownerUser User? @relation("AccountOwnerUser", fields: [ownerUserId], references: [id], onDelete: SetNull) transactionsRaw TransactionRaw[] investmentHoldings InvestmentHolding[] @@index([householdId, ownershipType]) @@index([ownerUserId]) @@index([plaidItemId]) @@index([tellerEnrollmentId]) } model PlaidWebhookEvent { id String @id @default(uuid()) itemId String? webhookType String webhookCode String payload Json status String @default("received") error String? receivedAt DateTime @default(now()) processedAt DateTime? @@index([itemId, receivedAt]) @@index([webhookType, webhookCode]) } model TransactionRaw { id String @id @default(uuid()) accountId String bankTransactionId String @unique date DateTime amount Decimal description String rawPayload Json ingestedAt DateTime @default(now()) source String account Account @relation(fields: [accountId], references: [id]) derived TransactionDerived? ruleExecutions RuleExecution[] comments TransactionComment[] } model TransactionDerived { id String @id @default(uuid()) rawTransactionId String @unique userCategory String? userNotes String? attribution String @default("mine") splitMode String @default("none") splitMinePercent Decimal? splitYoursPercent Decimal? isHidden Boolean @default(false) modifiedAt DateTime @default(now()) modifiedBy String raw TransactionRaw @relation(fields: [rawTransactionId], references: [id]) } model TransactionComment { id String @id @default(uuid()) rawTransactionId String userId String body String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt raw TransactionRaw @relation(fields: [rawTransactionId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([rawTransactionId, createdAt]) @@index([userId, createdAt]) } model CsvImportMapping { id String @id @default(uuid()) userId String headerSignature String name String? mapping Json createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastUsedAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, headerSignature]) @@index([userId, lastUsedAt]) } model Rule { id String @id @default(uuid()) userId String name String priority Int conditions Json actions Json isActive Boolean @default(true) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) executions RuleExecution[] } model RuleExecution { id String @id @default(uuid()) ruleId String transactionId String executedAt DateTime @default(now()) result Json rule Rule @relation(fields: [ruleId], references: [id]) transaction TransactionRaw @relation(fields: [transactionId], references: [id]) } model ExportLog { id String @id @default(uuid()) userId String format String @default("csv") destination String @default("download") filters Json rowCount Int fileName String? mimeType String? fileHash String? ipAddress String? userAgent String? metadata Json @default("{}") createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) @@index([userId, createdAt]) @@index([format, createdAt]) } model ExportDownloadToken { id String @id @default(uuid()) userId String tokenHash String @unique format String filters Json storageProvider String? storageKey String? fileName String? mimeType String? rowCount Int? fileHash String? expiresAt DateTime usedAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, createdAt]) @@index([expiresAt]) } model AuditLog { id String @id @default(uuid()) userId String action String metadata Json createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) } model AbuseEvent { id String @id @default(uuid()) userId String? eventType String riskPoints Int severity String ipAddress String? userAgent String? metadata Json @default("{}") createdAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId, createdAt]) @@index([eventType, createdAt]) } model GoogleConnection { id String @id @default(uuid()) userId String @unique googleEmail String refreshToken String accessToken String? spreadsheetId String? driveMirrorEnabled Boolean @default(false) driveMirrorStatus String @default("not_started") driveMirrorSpreadsheetUrl String? driveMirrorLastSyncedAt DateTime? isConnected Boolean @default(true) connectedAt DateTime @default(now()) lastSyncedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model ApiKey { id String @id @default(uuid()) userId String name String prefix String keyHash String @unique scopes String[] @default(["transactions:read"]) lastUsedAt DateTime? revokedAt DateTime? expiresAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, createdAt]) @@index([prefix]) } model EmailVerificationToken { id String @id @default(uuid()) userId String @unique token String @unique expiresAt DateTime createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model PasswordResetToken { id String @id @default(uuid()) userId String token String @unique expiresAt DateTime usedAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Session { id String @id @default(uuid()) userId String ipHash String userAgentHash String nonceHash String @default("") createdAt DateTime @default(now()) lastSeenAt DateTime @default(now()) expiresAt DateTime revokedAt DateTime? user User @relation(fields: [userId], references: [id], onDelete: Cascade) refreshTokens RefreshToken[] @@index([userId, revokedAt]) } model RefreshToken { id String @id @default(uuid()) userId String sessionId String? tokenHash String @unique expiresAt DateTime revokedAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) session Session? @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([sessionId]) } model Subscription { id String @id @default(uuid()) userId String @unique plan String @default("free") stripeCustomerId String? stripeSubId String? currentPeriodEnd DateTime? cancelAtPeriodEnd Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model TaxReturn { id String @id @default(uuid()) userId String taxYear Int filingType String jurisdictions Json status String @default("draft") summary Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) documents TaxDocument[] } model TaxDocument { id String @id @default(uuid()) taxReturnId String docType String metadata Json @default("{}") createdAt DateTime @default(now()) taxReturn TaxReturn @relation(fields: [taxReturnId], references: [id], onDelete: Cascade) }