generator client { provider = "prisma-client" output = "../generated/prisma" moduleFormat = "cjs" } datasource db { provider = "sqlite" } model User { id String @id email String @unique passwordHash String bankCards BankCard[] expenseCategories ExpenseCategory[] bankCardOperations BankCardOperation[] sessions Session[] tbankConnection TbankConnection? tbankValueHistory TbankPortfolioValueHistory[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model TbankConnection { userId String @unique encryptedToken String connectedAt DateTime revision String @default(uuid()) invalidToken Boolean @default(false) membershipAt DateTime? membershipRevision Int @default(0) user User @relation(fields: [userId], references: [id], onDelete: Cascade) accounts TbankAccount[] forecasts TbankForecast[] work TbankSyncWork[] @@unique([userId, revision]) } model TbankAccount { userId String revision String accountId String type String name String openedOn String? upstreamOrder Int operationsRevision Int @default(0) historyFrom DateTime? historyBoundary String @default("unknown") connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict) portfolio TbankPortfolio? operations TbankOperation[] coverage TbankOperationCoverage[] @@id([userId, revision, accountId]) } model TbankPortfolio { userId String revision String accountId String payload Json mappingVersion Int @default(1) fetchedAt DateTime dataRevision Int @default(1) account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict) @@id([userId, revision, accountId]) } // Account valuation history survives connection replacement and stays hidden // until the current connection confirms the account again. model TbankPortfolioValueHistory { userId String accountId String deletedAt DateTime? updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) points TbankPortfolioValuePoint[] @@id([userId, accountId]) } // One row per currency of a fully valued successful portfolio attempt. model TbankPortfolioValuePoint { userId String accountId String attemptId String currency String value String capturedAt DateTime createdAt DateTime @default(now()) history TbankPortfolioValueHistory @relation(fields: [userId, accountId], references: [userId, accountId], onDelete: Cascade, onUpdate: Restrict) @@id([userId, accountId, attemptId, currency]) @@index([userId, accountId, capturedAt]) } model TbankOperation { userId String revision String accountId String brokerOperationId String occurredAt DateTime instrumentUid String? category String payload Json mappingVersion Int @default(1) account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict) @@id([userId, revision, accountId, brokerOperationId]) @@index([userId, revision, accountId, occurredAt, brokerOperationId]) @@index([userId, revision, accountId, instrumentUid, occurredAt]) @@index([userId, revision, accountId, category, occurredAt]) } model TbankOperationCoverage { userId String revision String accountId String instrumentPredicate String @default("") from DateTime to DateTime fetchedAt DateTime asOf DateTime account TbankAccount @relation(fields: [userId, revision, accountId], references: [userId, revision, accountId], onDelete: Cascade, onUpdate: Restrict) @@id([userId, revision, accountId, instrumentPredicate, from, to]) } model TbankForecast { userId String revision String scopeKey String membershipRevision Int payload Json dependencyRevisions Json sourceTimestamps Json calculatedAt DateTime dataRevision Int @default(1) connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict) @@id([userId, revision, scopeKey]) } model TbankInstrument { provider String uid String figi String? ticker String? instrumentType String metadata Json fetchedAt DateTime dataRevision Int @default(1) sources TbankInstrumentSource[] candles TbankCandle[] candleCoverage TbankCandleCoverage[] @@id([provider, uid]) @@index([provider, figi]) @@index([provider, ticker, instrumentType]) } model TbankCatalogDiscovery { provider String instrumentType String query String instrumentUids Json fetchedAt DateTime dataRevision Int @default(1) @@id([provider, instrumentType, query]) } // Empty and ambiguous successful UID/FIGI/ticker lookups are distinct from failures. model TbankInstrumentLookup { provider String identifierType String identifier String matchedUids Json fetchedAt DateTime dataRevision Int @default(1) @@id([provider, identifierType, identifier]) } // Public per-instrument classification; no holdings or credentials. model TbankInstrumentClassification { provider String instrumentKey String sector String? sectorFetchedAt DateTime? assetUid String? brandUid String? brandName String? brandFetchedAt DateTime? @@id([provider, instrumentKey]) } // Source records contain no holdings, personal estimates or credentials errors. model TbankInstrumentSource { provider String instrumentUid String source String kind String rangeKey String payload Json fetchedAt DateTime dataRevision Int @default(1) instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade) @@id([provider, instrumentUid, source, kind, rangeKey]) } model TbankCandle { provider String instrumentUid String interval String time DateTime close String payload Json isComplete Boolean fetchedAt DateTime instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade) @@id([provider, instrumentUid, interval, time]) } model TbankCandleCoverage { provider String instrumentUid String interval String from DateTime to DateTime isComplete Boolean fetchedAt DateTime dataRevision Int @default(1) instrument TbankInstrument @relation(fields: [provider, instrumentUid], references: [provider, uid], onDelete: Cascade) @@id([provider, instrumentUid, interval, from, to]) } model TbankSyncWork { id String @id userId String revision String scopeKey String intent Json state String attemptId String processRunId String priority Int progress Json errorCode String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt connection TbankConnection @relation(fields: [userId, revision], references: [userId, revision], onDelete: Cascade, onUpdate: Restrict) staged TbankSyncStage[] @@unique([userId, revision, scopeKey]) @@index([userId, revision, state, priority]) @@unique([id, attemptId]) } model TbankSyncStage { workId String attemptId String sourceKey String itemKey String payload Json work TbankSyncWork @relation(fields: [workId, attemptId], references: [id, attemptId], onDelete: Cascade, onUpdate: Restrict) @@id([workId, attemptId, sourceKey, itemKey]) } model Session { id String @id userId String refreshTokenHash String expiresAt DateTime revokedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) } model BankCard { id String @id userId String name String bankName String? lastFour String? paymentSystem String? openingBalanceKopecks BigInt openingDate String version Int @default(1) archivedAt DateTime? createFingerprint String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Restrict) operations BankCardOperation[] @relation("SourceCard") incomingOperations BankCardOperation[] @relation("TargetCard") @@index([userId]) } model ExpenseCategory { id String @id userId String name String nameKey String seedKey String? version Int @default(1) user User @relation(fields: [userId], references: [id], onDelete: Restrict) operations BankCardOperation[] @@unique([userId, nameKey]) @@unique([userId, seedKey]) } model BankCardOperation { id String @id userId String kind String cardId String targetCardId String? categoryId String? originalExpenseId String? amountKopecks BigInt date String description String? version Int @default(1) deletedAt DateTime? createFingerprint String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Restrict) card BankCard @relation("SourceCard", fields: [cardId], references: [id], onDelete: Restrict) targetCard BankCard? @relation("TargetCard", fields: [targetCardId], references: [id], onDelete: Restrict) category ExpenseCategory? @relation(fields: [categoryId], references: [id], onDelete: Restrict) originalExpense BankCardOperation? @relation("RefundExpense", fields: [originalExpenseId], references: [id], onDelete: Restrict, onUpdate: NoAction) refunds BankCardOperation[] @relation("RefundExpense") @@index([userId, cardId, date]) @@index([userId, targetCardId, date]) @@index([originalExpenseId]) }