// Prisma schema for AI-native ERP Platform // Multi-tenant, comprehensive financial and operational management system datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } // ============================================================================ // ENUMS // ============================================================================ enum UserStatus { ACTIVE INACTIVE SUSPENDED PENDING_ACTIVATION } enum RoleType { CEO CFO CONTROLLER MANAGER ANALYST CLERK CUSTOM } enum PermissionModule { DASHBOARD CHART_OF_ACCOUNTS JOURNAL_ENTRIES INVOICES PAYMENTS VENDORS CUSTOMERS BANK_ACCOUNTS TREASURY REPORTS SETTINGS USERS INTEGRATIONS AUDIT_LOG } enum PermissionAction { VIEW CREATE EDIT APPROVE DELETE ADMIN } enum InvoiceStatus { DRAFT PENDING_APPROVAL APPROVED PARTIALLY_PAID PAID OVERDUE CANCELLED VOID } enum PaymentStatus { DRAFT SCHEDULED PROCESSING COMPLETED FAILED REVERSED CANCELLED } enum PaymentMethod { ACH WIRE CHECK CREDIT_CARD BANK_TRANSFER CASH OTHER } enum InvoiceType { ACCOUNTS_PAYABLE ACCOUNTS_RECEIVABLE } enum AccountType { ASSET LIABILITY EQUITY REVENUE EXPENSE GAIN_LOSS } enum FiscalPeriodStatus { OPEN CLOSED LOCKED } enum ConnectorStatus { ACTIVE INACTIVE ERROR PAUSED TESTING } enum ConnectorType { BANK_FEED ACCOUNTING_SOFTWARE ERP_SYSTEM PAYMENT_PROCESSOR DOCUMENT_STORAGE CUSTOM_API } enum ConnectorHealthStatus { HEALTHY WARNING ERROR UNKNOWN } enum AIActionType { CATEGORIZATION ANOMALY_DETECTION RECONCILIATION FORECAST RECOMMENDATION DATA_TRANSFORMATION VALIDATION HEALING } enum AIActionStatus { PENDING EXECUTED APPROVED REJECTED IN_PROGRESS COMPLETED FAILED } enum TransactionType { DEBIT CREDIT REVERSAL ADJUSTMENT } enum EntityType { SUBSIDIARY DIVISION DEPARTMENT LOCATION COST_CENTER } // ============================================================================ // MULTI-TENANCY & ORGANIZATION // ============================================================================ model Organization { id String @id @default(uuid()) @db.Uuid name String @db.VarChar(255) legalName String? @db.VarChar(255) taxId String? @db.VarChar(50) website String? @db.VarChar(255) description String? @db.Text logoUrl String? @db.VarChar(500) status String @default("ACTIVE") @db.VarChar(50) industry String? @db.VarChar(100) // Fiscal configuration currentFiscalYearId String? @db.Uuid fiscalYearStartMonth Int @default(1) // 1-12 fiscalYearStartDay Int @default(1) // 1-31 // Settings defaultCurrency String @default("USD") @db.VarChar(3) timezone String @default("UTC") @db.VarChar(50) language String @default("en") @db.VarChar(10) dateFormat String @default("YYYY-MM-DD") @db.VarChar(20) // Metadata settings Json? customFields Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations entities Entity[] users User[] roles Role[] permissions Permission[] groups Group[] chartOfAccounts ChartOfAccount[] journalEntries JournalEntry[] journalEntryLines JournalEntryLine[] fiscalPeriods FiscalPeriod[] invoices Invoice[] payments Payment[] paymentAllocations PaymentAllocation[] vendors Vendor[] customers Customer[] bankAccounts BankAccount[] bankTransactions BankTransaction[] bankReconciliations BankReconciliation[] currencies Currency[] connectors Connector[] aiActions AIAction[] dashboardConfigs DashboardConfig[] auditLogs AuditLog[] shareholders Shareholder[] leases Lease[] budgetLines BudgetLine[] purchaseOrders PurchaseOrder[] documents Document[] subscriptions Subscription[] forecasts Forecast[] drivers PlanningDriver[] planningScenarios PlanningScenario[] closePeriods ClosePeriod[] closeTasks CloseTask[] revenueContracts RevenueContract[] revenueScheduleLines RevenueScheduleLine[] fixedAssets FixedAsset[] eveCharacters EveCharacter[] @@index([name]) @@index([status]) @@map("organizations") } model Entity { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) type EntityType @default(SUBSIDIARY) code String @db.VarChar(50) parentEntityId String? @db.Uuid description String? @db.Text status String @default("ACTIVE") @db.VarChar(50) // Settings defaultCurrency String @default("USD") @db.VarChar(3) isConsolidated Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) parentEntity Entity? @relation("EntityHierarchy", fields: [parentEntityId], references: [id], onDelete: SetNull) childEntities Entity[] @relation("EntityHierarchy") journalEntryLines JournalEntryLine[] invoices Invoice[] bankAccounts BankAccount[] @@index([organizationId]) @@index([parentEntityId]) @@index([code]) @@map("entities") } // ============================================================================ // AUTHENTICATION & RBAC // ============================================================================ model User { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid email String @db.VarChar(255) emailVerified DateTime? passwordHash String? @db.VarChar(255) name String @db.VarChar(255) firstName String? @db.VarChar(100) lastName String? @db.VarChar(100) avatarUrl String? @db.VarChar(500) phone String? @db.VarChar(20) status UserStatus @default(ACTIVE) lastLoginAt DateTime? lastLoginIp String? @db.VarChar(50) // Preferences timezone String? @db.VarChar(50) language String? @db.VarChar(10) preferences Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) userRoles UserRole[] userGroups UserGroup[] sessions Session[] aiActionsTriggered AIAction[] @relation("TriggeredBy") aiActionsApproved AIAction[] @relation("ApprovedBy") journalEntriesPosted JournalEntry[] @relation("PostedBy") journalEntriesApproved JournalEntry[] @relation("ApprovedBy") dashboardConfigs DashboardConfig[] eveCharacters EveCharacter[] @@unique([organizationId, email]) @@index([organizationId]) @@index([email]) @@index([status]) @@map("users") } model Role { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) roleType RoleType @default(CUSTOM) description String? @db.Text isSystem Boolean @default(false) status String @default("ACTIVE") @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) permissions Permission[] userRoles UserRole[] @@unique([organizationId, name]) @@index([organizationId]) @@index([roleType]) @@map("roles") } model Permission { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid roleId String @db.Uuid module PermissionModule action PermissionAction description String? @db.Text conditions Json? // Additional conditions (e.g., entity-level restrictions) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@unique([roleId, module, action]) @@index([organizationId]) @@index([roleId]) @@map("permissions") } model Group { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) description String? @db.Text status String @default("ACTIVE") @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) userGroups UserGroup[] @@unique([organizationId, name]) @@index([organizationId]) @@map("groups") } model UserRole { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid roleId String @db.Uuid assignedAt DateTime @default(now()) expiresAt DateTime? createdBy String? @db.VarChar(255) // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@unique([userId, roleId]) @@index([userId]) @@index([roleId]) @@map("user_roles") } model UserGroup { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid groupId String @db.Uuid joinedAt DateTime @default(now()) // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) group Group @relation(fields: [groupId], references: [id], onDelete: Cascade) @@unique([userId, groupId]) @@index([userId]) @@index([groupId]) @@map("user_groups") } model Session { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid token String @db.VarChar(500) expiresAt DateTime ipAddress String? @db.VarChar(50) userAgent String? @db.Text revokedAt DateTime? createdAt DateTime @default(now()) // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([token]) @@index([expiresAt]) @@map("sessions") } // ============================================================================ // FINANCIAL CORE - CHART OF ACCOUNTS & JOURNAL // ============================================================================ model ChartOfAccount { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid accountCode String @db.VarChar(50) accountName String @db.VarChar(255) accountType AccountType parentAccountId String? @db.Uuid description String? @db.Text status String @default("ACTIVE") @db.VarChar(50) // Account settings isControlAccount Boolean @default(false) isDetailAccount Boolean @default(true) isBankAccount Boolean @default(false) currencyCode String @default("USD") @db.VarChar(3) // Hierarchy level Int @default(1) hierarchy String @db.VarChar(500) // Denormalized for performance createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) parentAccount ChartOfAccount? @relation("AccountHierarchy", fields: [parentAccountId], references: [id], onDelete: SetNull) childAccounts ChartOfAccount[] @relation("AccountHierarchy") journalEntryLines JournalEntryLine[] budgetLines BudgetLine[] @@unique([organizationId, accountCode]) @@index([organizationId]) @@index([accountType]) @@index([status]) @@index([parentAccountId]) @@map("chart_of_accounts") } model FiscalPeriod { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid fiscalYear Int periodNumber Int // 1-12 or 1-13 periodName String @db.VarChar(50) startDate DateTime endDate DateTime status FiscalPeriodStatus @default(OPEN) closedAt DateTime? closedBy String? @db.VarChar(255) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) journalEntries JournalEntry[] @@unique([organizationId, fiscalYear, periodNumber]) @@index([organizationId]) @@index([status]) @@index([startDate, endDate]) @@map("fiscal_periods") } model JournalEntry { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid fiscalPeriodId String @db.Uuid entryNumber String @db.VarChar(50) entryDate DateTime description String @db.Text sourceModule String @db.VarChar(100) // AP, AR, PAYROLL, etc. referenceId String? @db.VarChar(255) // Links to source document status String @default("DRAFT") @db.VarChar(50) totalDebits Decimal @default(0) @db.Decimal(19, 4) totalCredits Decimal @default(0) @db.Decimal(19, 4) isBalanced Boolean @default(false) postedAt DateTime? postedBy String? @db.Uuid approvedAt DateTime? approvedBy String? @db.Uuid memo String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) fiscalPeriod FiscalPeriod @relation(fields: [fiscalPeriodId], references: [id], onDelete: Restrict) postedByUser User? @relation("PostedBy", fields: [postedBy], references: [id], onDelete: SetNull) approvedByUser User? @relation("ApprovedBy", fields: [approvedBy], references: [id], onDelete: SetNull) journalEntryLines JournalEntryLine[] @@unique([organizationId, entryNumber]) @@index([organizationId]) @@index([entryDate]) @@index([fiscalPeriodId]) @@index([status]) @@index([sourceModule]) @@map("journal_entries") } model JournalEntryLine { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid journalEntryId String @db.Uuid entityId String @db.Uuid accountId String @db.Uuid debitAmount Decimal @default(0) @db.Decimal(19, 4) creditAmount Decimal @default(0) @db.Decimal(19, 4) currencyCode String @default("USD") @db.VarChar(3) exchangeRate Decimal @default(1) @db.Decimal(19, 8) lineNumber Int description String? @db.Text memo String? @db.Text department String? @db.VarChar(100) project String? @db.VarChar(100) costCenter String? @db.VarChar(100) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) journalEntry JournalEntry @relation(fields: [journalEntryId], references: [id], onDelete: Cascade) entity Entity @relation(fields: [entityId], references: [id], onDelete: Restrict) account ChartOfAccount @relation(fields: [accountId], references: [id], onDelete: Restrict) @@index([organizationId]) @@index([journalEntryId]) @@index([accountId]) @@index([entityId]) @@map("journal_entry_lines") } // ============================================================================ // CURRENCY & EXCHANGE RATES // ============================================================================ model Currency { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid currencyCode String @db.VarChar(3) currencyName String @db.VarChar(100) symbolFormat String @db.VarChar(10) decimalPlaces Int @default(2) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@unique([organizationId, currencyCode]) @@index([organizationId]) @@map("currencies") } // ============================================================================ // VENDORS & CUSTOMERS // ============================================================================ model Vendor { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid vendorCode String @db.VarChar(50) vendorName String @db.VarChar(255) vendorType String? @db.VarChar(100) // Contact Info email String? @db.VarChar(255) phone String? @db.VarChar(20) website String? @db.VarChar(255) // Address street1 String? @db.VarChar(255) street2 String? @db.VarChar(255) city String? @db.VarChar(100) stateProvince String? @db.VarChar(100) postalCode String? @db.VarChar(20) country String? @db.VarChar(100) // Tax & Legal taxId String? @db.VarChar(50) vendorTaxId String? @db.VarChar(50) // Payment Terms paymentTermDays Int @default(0) defaultCurrency String @default("USD") @db.VarChar(3) status String @default("ACTIVE") @db.VarChar(50) creditLimit Decimal? @db.Decimal(19, 4) isOnHold Boolean @default(false) // Audit createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) invoices Invoice[] payments Payment[] purchaseOrders PurchaseOrder[] @@unique([organizationId, vendorCode]) @@index([organizationId]) @@index([status]) @@map("vendors") } model Customer { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid customerCode String @db.VarChar(50) customerName String @db.VarChar(255) customerType String? @db.VarChar(100) // Contact Info email String? @db.VarChar(255) phone String? @db.VarChar(20) website String? @db.VarChar(255) contactPerson String? @db.VarChar(255) // Address street1 String? @db.VarChar(255) street2 String? @db.VarChar(255) city String? @db.VarChar(100) stateProvince String? @db.VarChar(100) postalCode String? @db.VarChar(20) country String? @db.VarChar(100) // Tax & Legal taxId String? @db.VarChar(50) // Business Terms paymentTermDays Int @default(0) defaultCurrency String @default("USD") @db.VarChar(3) creditLimit Decimal? @db.Decimal(19, 4) status String @default("ACTIVE") @db.VarChar(50) isOnCredit Boolean @default(false) // Audit createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) invoices Invoice[] payments Payment[] subscriptions Subscription[] revenueContracts RevenueContract[] @@unique([organizationId, customerCode]) @@index([organizationId]) @@index([status]) @@map("customers") } // ============================================================================ // INVOICES // ============================================================================ model Invoice { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid entityId String @db.Uuid invoiceNumber String @db.VarChar(50) invoiceType InvoiceType // Party Info vendorId String? @db.Uuid customerId String? @db.Uuid invoiceDate DateTime dueDate DateTime? expectedPaymentDate DateTime? // Amount subtotalAmount Decimal @db.Decimal(19, 4) taxAmount Decimal @default(0) @db.Decimal(19, 4) totalAmount Decimal @db.Decimal(19, 4) paidAmount Decimal @default(0) @db.Decimal(19, 4) balanceDue Decimal @db.Decimal(19, 4) currencyCode String @default("USD") @db.VarChar(3) exchangeRate Decimal @default(1) @db.Decimal(19, 8) // Details description String? @db.Text poNumber String? @db.VarChar(100) referenceNumber String? @db.VarChar(100) status InvoiceStatus @default(DRAFT) approvedAt DateTime? approvedBy String? @db.VarChar(255) cancelledAt DateTime? cancelledBy String? @db.VarChar(255) cancelReason String? @db.Text daysOverdue Int? // Audit createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) entity Entity @relation(fields: [entityId], references: [id], onDelete: Restrict) vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull) customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull) invoiceLines InvoiceLine[] payments Payment[] paymentAllocations PaymentAllocation[] @@unique([organizationId, invoiceNumber]) @@index([organizationId]) @@index([invoiceType]) @@index([status]) @@index([invoiceDate]) @@index([dueDate]) @@index([vendorId]) @@index([customerId]) @@map("invoices") } model InvoiceLine { id String @id @default(uuid()) @db.Uuid invoiceId String @db.Uuid lineNumber Int description String @db.Text quantity Decimal @db.Decimal(19, 4) unitPrice Decimal @db.Decimal(19, 4) lineAmount Decimal @db.Decimal(19, 4) glAccountCode String? @db.VarChar(50) department String? @db.VarChar(100) project String? @db.VarChar(100) taxCode String? @db.VarChar(50) taxAmount Decimal @default(0) @db.Decimal(19, 4) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) @@index([invoiceId]) @@map("invoice_lines") } // ============================================================================ // PAYMENTS // ============================================================================ model Payment { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid paymentNumber String @db.VarChar(50) // Party & Invoice Info vendorId String? @db.Uuid customerId String? @db.Uuid invoiceId String? @db.Uuid // Payment Details paymentDate DateTime paymentMethod PaymentMethod paymentReference String? @db.VarChar(255) // Account Info bankAccountId String? @db.Uuid checkNumber String? @db.VarChar(50) // Amount paymentAmount Decimal @db.Decimal(19, 4) currencyCode String @default("USD") @db.VarChar(3) exchangeRate Decimal @default(1) @db.Decimal(19, 8) feeAmount Decimal @default(0) @db.Decimal(19, 4) totalPaymentAmount Decimal @db.Decimal(19, 4) // Status status PaymentStatus @default(DRAFT) approvedAt DateTime? approvedBy String? @db.VarChar(255) processedAt DateTime? processedBy String? @db.VarChar(255) failureReason String? @db.Text // Metadata memo String? @db.Text // Audit createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull) customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull) invoice Invoice? @relation(fields: [invoiceId], references: [id], onDelete: SetNull) bankAccount BankAccount? @relation(fields: [bankAccountId], references: [id], onDelete: SetNull) paymentAllocations PaymentAllocation[] @@unique([organizationId, paymentNumber]) @@index([organizationId]) @@index([paymentDate]) @@index([status]) @@index([vendorId]) @@index([customerId]) @@map("payments") } model PaymentAllocation { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid paymentId String @db.Uuid invoiceId String @db.Uuid allocatedAmount Decimal @db.Decimal(19, 4) allocationDate DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) payment Payment @relation(fields: [paymentId], references: [id], onDelete: Cascade) invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade) @@unique([paymentId, invoiceId]) @@index([organizationId]) @@index([paymentId]) @@index([invoiceId]) @@map("payment_allocations") } // ============================================================================ // TREASURY & BANK ACCOUNTS // ============================================================================ model BankAccount { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid entityId String @db.Uuid accountName String @db.VarChar(255) bankName String @db.VarChar(255) accountNumber String @db.VarChar(50) // Typically masked accountNumberMasked String @db.VarChar(50) routingNumber String? @db.VarChar(20) swiftCode String? @db.VarChar(20) ibanCode String? @db.VarChar(50) currencyCode String @default("USD") @db.VarChar(3) currentBalance Decimal @db.Decimal(19, 4) availableBalance Decimal? @db.Decimal(19, 4) lastBalanceUpdate DateTime? accountType String @db.VarChar(50) // Checking, Savings, etc. status String @default("ACTIVE") @db.VarChar(50) // Integration connectorId String? @db.Uuid externalAccountId String? @db.VarChar(255) lastSyncAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) entity Entity @relation(fields: [entityId], references: [id], onDelete: Restrict) connector Connector? @relation(fields: [connectorId], references: [id], onDelete: SetNull) bankTransactions BankTransaction[] payments Payment[] @@unique([organizationId, accountNumber]) @@index([organizationId]) @@index([status]) @@map("bank_accounts") } model BankTransaction { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid bankAccountId String @db.Uuid transactionDate DateTime postDate DateTime? transactionType TransactionType amount Decimal @db.Decimal(19, 4) description String @db.Text // Matching invoiceId String? @db.Uuid paymentId String? @db.Uuid journalEntryId String? @db.Uuid reconciled Boolean @default(false) reconciledAt DateTime? // Bank-provided fields checkNumber String? @db.VarChar(50) referenceNumber String? @db.VarChar(255) oppositeParty String? @db.VarChar(255) status String @default("PENDING") @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) bankAccount BankAccount @relation(fields: [bankAccountId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([bankAccountId]) @@index([transactionDate]) @@index([reconciled]) @@map("bank_transactions") } model BankReconciliation { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid bankAccountId String @db.Uuid // Period covered periodStart DateTime periodEnd DateTime statementDate DateTime statementBalance Decimal @db.Decimal(19, 4) // Computed at completion beginningBalance Decimal @default(0) @db.Decimal(19, 4) endingBookBalance Decimal @default(0) @db.Decimal(19, 4) clearedDeposits Decimal @default(0) @db.Decimal(19, 4) clearedWithdrawals Decimal @default(0) @db.Decimal(19, 4) outstandingDeposits Decimal @default(0) @db.Decimal(19, 4) outstandingWithdrawals Decimal @default(0) @db.Decimal(19, 4) difference Decimal @default(0) @db.Decimal(19, 4) status String @default("DRAFT") @db.VarChar(20) // DRAFT | COMPLETED | REOPENED reconciledTransactionCount Int @default(0) notes String? @db.Text completedAt DateTime? completedBy String? @db.VarChar(255) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([bankAccountId]) @@index([statementDate]) @@map("bank_reconciliations") } model CashPosition { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid snapshotDate DateTime totalCash Decimal @db.Decimal(19, 4) totalAvailable Decimal @db.Decimal(19, 4) // Breakdown cashByEntity Json? // Map of entity -> cash amount cashByCurrency Json? // Map of currency -> cash amount createdAt DateTime @default(now()) @@index([organizationId]) @@index([snapshotDate]) @@map("cash_positions") } // ============================================================================ // INTEGRATIONS // ============================================================================ model Connector { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) type ConnectorType provider String @db.VarChar(100) description String? @db.Text status ConnectorStatus @default(INACTIVE) healthStatus ConnectorHealthStatus @default(UNKNOWN) // Configuration config Json? // Encrypted config with credentials configVersion Int @default(1) // Sync tracking lastSyncAt DateTime? lastSyncStatus String? @db.VarChar(50) lastErrorMessage String? @db.Text nextScheduledSync DateTime? syncFrequencyMinutes Int @default(60) // Features isBidirectional Boolean @default(false) supportsRealtime Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) connectorLogs ConnectorLog[] bankAccounts BankAccount[] @@unique([organizationId, name]) @@index([organizationId]) @@index([status]) @@index([type]) @@map("connectors") } model ConnectorLog { id String @id @default(uuid()) @db.Uuid connectorId String @db.Uuid syncStartTime DateTime syncEndTime DateTime? duration Int? // milliseconds status String @db.VarChar(50) // SUCCESS, PARTIAL, FAILED recordsSynced Int @default(0) recordsProcessed Int @default(0) recordsFailed Int @default(0) errorMessage String? @db.Text errorDetails Json? // AI Healing aiHealingAttempted Boolean @default(false) aiHealingAction String? @db.Text aiHealingSuccess Boolean @default(false) // Metadata syncType String @db.VarChar(50) // FULL, INCREMENTAL metadata Json? // Relations connector Connector @relation(fields: [connectorId], references: [id], onDelete: Cascade) @@index([connectorId]) @@index([syncStartTime]) @@index([status]) @@map("connector_logs") } // ============================================================================ // AI AUDIT TRAIL // ============================================================================ model AIAction { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid actionType AIActionType module String @db.VarChar(100) inputSummary String @db.Text outputSummary String @db.Text modelUsed String @db.VarChar(100) modelVersion String? @db.VarChar(50) confidence Decimal? @db.Decimal(5, 4) // 0.0-1.0 // User tracking triggeredByUserId String @db.Uuid approvedByUserId String? @db.Uuid status AIActionStatus @default(PENDING) // Approval tracking approvalComment String? @db.Text approvalDate DateTime? rejectionReason String? @db.Text rejectionDate DateTime? // Related records relatedEntityType String? @db.VarChar(100) // Journal, Invoice, Payment, etc. relatedEntityId String? @db.Uuid // Metadata parameters Json? result Json? executedAt DateTime? duration Int? // milliseconds createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) triggeredBy User @relation("TriggeredBy", fields: [triggeredByUserId], references: [id], onDelete: Restrict) approvedBy User? @relation("ApprovedBy", fields: [approvedByUserId], references: [id], onDelete: SetNull) @@index([organizationId]) @@index([actionType]) @@index([module]) @@index([status]) @@index([createdAt]) @@index([triggeredByUserId]) @@map("ai_actions") } // ============================================================================ // DASHBOARD // ============================================================================ model DashboardConfig { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid userId String @db.Uuid name String @db.VarChar(255) description String? @db.Text isDefault Boolean @default(false) layoutConfig Json // Dashboard layout and widget arrangement createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) widgets Widget[] @@unique([organizationId, userId, isDefault]) @@index([organizationId]) @@index([userId]) @@map("dashboard_configs") } model Widget { id String @id @default(uuid()) @db.Uuid dashboardConfigId String @db.Uuid widgetType String @db.VarChar(100) title String @db.VarChar(255) description String? @db.Text position Int // Position in grid rows Int @default(1) cols Int @default(1) config Json? // Widget-specific config dataSource String? @db.VarChar(255) refreshInterval Int? // seconds isVisible Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations dashboardConfig DashboardConfig @relation(fields: [dashboardConfigId], references: [id], onDelete: Cascade) @@index([dashboardConfigId]) @@map("widgets") } // ─── Audit Trail ───────────────────────────────────────────────── model AuditLog { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid userId String? @db.Uuid userName String? @db.VarChar(255) action String @db.VarChar(50) // CREATE, UPDATE, DELETE, APPROVE, REJECT, POST, VOID, LOGIN, EXPORT module String @db.VarChar(100) // general-ledger, ap, ar, treasury, etc. entityType String @db.VarChar(100) // JournalEntry, Invoice, Payment, etc. entityId String? @db.VarChar(255) entityLabel String? @db.VarChar(500) // Human-readable label (e.g., "JE-2024-001") description String @db.Text // "Created journal entry JE-2024-001" changes Json? // { field: { old: x, new: y } } metadata Json? // Extra context ipAddress String? @db.VarChar(50) userAgent String? @db.Text createdAt DateTime @default(now()) // Relations organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId, createdAt]) @@index([organizationId, module]) @@index([organizationId, entityType, entityId]) @@index([userId]) @@map("audit_logs") } // ─── Cap Table ─────────────────────────────────────────────────── model Shareholder { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) type String @db.VarChar(50) // founder, employee, investor, advisor sharesOwned Int shareClass String @db.VarChar(50) // Common, Series A, Series B, Options vestingCliffMonths Int @default(0) vestingTotalMonths Int @default(0) vestedPercent Decimal @default(0) @db.Decimal(5,2) grantDate DateTime exercisePrice Decimal @default(0) @db.Decimal(19,4) status String @default("ACTIVE") @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId]) @@map("shareholders") } // ─── Real Estate & Leases ──────────────────────────────────────── model Lease { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid description String @db.VarChar(500) type String @db.VarChar(50) // Operating, Finance startDate DateTime endDate DateTime monthlyPayment Decimal @db.Decimal(19,4) totalCommitment Decimal @db.Decimal(19,4) discountRate Decimal @db.Decimal(5,2) rouAsset Decimal @default(0) @db.Decimal(19,4) leaseLiability Decimal @default(0) @db.Decimal(19,4) currentPortion Decimal @default(0) @db.Decimal(19,4) nonCurrentPortion Decimal @default(0) @db.Decimal(19,4) status String @default("Active") @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([status]) @@map("leases") } // ─── Budget ────────────────────────────────────────────────────── model BudgetLine { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid accountId String @db.Uuid fiscalYear Int monthlyAmounts Json // Array of 12 monthly budget amounts annualBudget Decimal @db.Decimal(19,4) notes String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) account ChartOfAccount @relation(fields: [accountId], references: [id], onDelete: Cascade) @@unique([organizationId, accountId, fiscalYear]) @@index([organizationId]) @@index([fiscalYear]) @@map("budget_lines") } // ─── Procurement ───────────────────────────────────────────────── model PurchaseOrder { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid poNumber String @db.VarChar(50) vendorId String @db.Uuid orderDate DateTime expectedDate DateTime? status String @default("DRAFT") @db.VarChar(50) // DRAFT, SUBMITTED, APPROVED, RECEIVED, CANCELLED subtotal Decimal @default(0) @db.Decimal(19,4) taxAmount Decimal @default(0) @db.Decimal(19,4) totalAmount Decimal @default(0) @db.Decimal(19,4) notes String? @db.Text approvedBy String? @db.VarChar(255) approvedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) vendor Vendor @relation(fields: [vendorId], references: [id], onDelete: Restrict) lines PurchaseOrderLine[] @@unique([organizationId, poNumber]) @@index([organizationId]) @@index([vendorId]) @@index([status]) @@map("purchase_orders") } model PurchaseOrderLine { id String @id @default(uuid()) @db.Uuid purchaseOrderId String @db.Uuid lineNumber Int description String @db.Text quantity Decimal @db.Decimal(19,4) unitPrice Decimal @db.Decimal(19,4) lineAmount Decimal @db.Decimal(19,4) glAccountCode String? @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt purchaseOrder PurchaseOrder @relation(fields: [purchaseOrderId], references: [id], onDelete: Cascade) @@index([purchaseOrderId]) @@map("purchase_order_lines") } // ─── Documents ─────────────────────────────────────────────────── model Document { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) description String? @db.Text category String @db.VarChar(100) // contract, invoice, receipt, report, legal, other fileSize Int @default(0) // bytes mimeType String @db.VarChar(100) storagePath String @db.VarChar(500) // local path or URL status String @default("ACTIVE") @db.VarChar(50) tags Json? // array of tags uploadedBy String? @db.VarChar(255) // Link to related entity relatedEntityType String? @db.VarChar(100) relatedEntityId String? @db.Uuid createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([category]) @@index([status]) @@map("documents") } // ─── Billing & Subscriptions ───────────────────────────────────── model Subscription { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid customerId String @db.Uuid planName String @db.VarChar(255) status String @default("ACTIVE") @db.VarChar(50) // ACTIVE, PAUSED, CANCELLED, EXPIRED billingCycle String @db.VarChar(50) // MONTHLY, QUARTERLY, ANNUAL amount Decimal @db.Decimal(19,4) currencyCode String @default("USD") @db.VarChar(3) startDate DateTime endDate DateTime? nextBillingDate DateTime? cancelledAt DateTime? notes String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([customerId]) @@index([status]) @@map("subscriptions") } // ─── Financial Planning & Forecasting ──────────────────────────── model Forecast { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) description String? @db.Text scenarioType String @db.VarChar(50) // BASE, OPTIMISTIC, PESSIMISTIC, CUSTOM startDate DateTime endDate DateTime assumptions Json? // revenue growth rate, expense factors, etc. projections Json? // monthly projections data status String @default("DRAFT") @db.VarChar(50) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([scenarioType]) @@map("forecasts") } // ─── Driver-Based Planning ─────────────────────────────────────── model PlanningDriver { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid key String @db.VarChar(100) // stable machine key e.g. "revenue_growth" name String @db.VarChar(255) category String @db.VarChar(50) // REVENUE | COST | HEADCOUNT | PRICING | CHURN | UNIT | OTHER unit String @db.VarChar(50) // PERCENT | CURRENCY | COUNT | RATIO value Decimal @db.Decimal(19,6) // current/base value minValue Decimal? @db.Decimal(19,6) maxValue Decimal? @db.Decimal(19,6) description String? @db.Text formula String? @db.Text // optional expression for derived drivers tags Json? // array of string tags isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@unique([organizationId, key]) @@index([organizationId]) @@index([category]) @@map("planning_drivers") } model PlanningScenario { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid name String @db.VarChar(255) description String? @db.Text scenarioType String @default("CUSTOM") @db.VarChar(50) // BASE | OPTIMISTIC | PESSIMISTIC | CUSTOM horizon Int @default(12) // months driverOverrides Json // { [driverKey]: number } assumptions Json? // free-form notes / flags results Json? // cached computed output isBaseline Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([scenarioType]) @@index([isBaseline]) @@map("planning_scenarios") } // ─── Month-End Close ───────────────────────────────────────────── model ClosePeriod { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid year Int month Int // 1-12 periodLabel String @db.VarChar(50) // e.g. "Mar 2026" status String @default("OPEN") @db.VarChar(20) // OPEN | IN_PROGRESS | REVIEW | CLOSED targetCloseDate DateTime? startedAt DateTime? actualCloseDate DateTime? closedBy String? @db.VarChar(255) notes String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) tasks CloseTask[] @@unique([organizationId, year, month]) @@index([organizationId]) @@index([status]) @@map("close_periods") } model CloseTask { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid closePeriodId String? @db.Uuid // null = template task taskKey String @db.VarChar(100) // stable key for template lookup order Int @default(0) title String @db.VarChar(255) description String? @db.Text category String @db.VarChar(50) // AP | AR | BANK | PAYROLL | FIXED_ASSETS | REVENUE | PREPAID | INTERCO | RECON | REVIEW | REPORTING | LOCK owner String? @db.VarChar(255) status String @default("PENDING") @db.VarChar(20) // PENDING | IN_PROGRESS | BLOCKED | REVIEW | DONE | SKIPPED dependsOn Json? // array of taskKey strings estimatedMinutes Int? actualMinutes Int? completedAt DateTime? completedBy String? @db.VarChar(255) dueDate DateTime? notes String? @db.Text automatable Boolean @default(false) aiAssisted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) closePeriod ClosePeriod? @relation(fields: [closePeriodId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([closePeriodId]) @@index([status]) @@index([category]) @@map("close_tasks") } // ─── Revenue Recognition (ASC 606) ─────────────────────────────── model RevenueContract { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid customerId String? @db.Uuid contractNumber String @db.VarChar(100) name String @db.VarChar(255) description String? @db.Text // Performance obligation totalValue Decimal @db.Decimal(19,2) currency String @default("USD") @db.VarChar(3) // Service period startDate DateTime endDate DateTime // Recognition method recognitionMethod String @default("STRAIGHT_LINE") @db.VarChar(30) // STRAIGHT_LINE | POINT_IN_TIME | MILESTONE | USAGE_BASED billingFrequency String @default("MONTHLY") @db.VarChar(20) // ONE_TIME | MONTHLY | QUARTERLY | ANNUAL status String @default("ACTIVE") @db.VarChar(20) // DRAFT | ACTIVE | COMPLETED | CANCELLED // GL accounts for posting deferredAccountCode String? @db.VarChar(50) // liability account revenueAccountCode String? @db.VarChar(50) // P&L revenue account notes String? @db.Text metadata Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull) scheduleLines RevenueScheduleLine[] @@unique([organizationId, contractNumber]) @@index([organizationId]) @@index([customerId]) @@index([status]) @@map("revenue_contracts") } model RevenueScheduleLine { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid contractId String @db.Uuid periodYear Int periodMonth Int // 1-12 periodLabel String @db.VarChar(50) // "Mar 2026" amount Decimal @db.Decimal(19,2) cumulativeRecognized Decimal? @db.Decimal(19,2) cumulativeDeferred Decimal? @db.Decimal(19,2) status String @default("SCHEDULED") @db.VarChar(20) // SCHEDULED | RECOGNIZED | POSTED recognizedAt DateTime? postedAt DateTime? journalEntryId String? @db.Uuid createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) contract RevenueContract @relation(fields: [contractId], references: [id], onDelete: Cascade) @@index([organizationId]) @@index([contractId]) @@index([organizationId, periodYear, periodMonth]) @@index([status]) @@map("revenue_schedule_lines") } // ─── Fixed Assets Register ─────────────────────────────────────── model FixedAsset { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid // Identification assetTag String @db.VarChar(100) name String @db.VarChar(255) description String? @db.Text category String @db.VarChar(100) // Equipment | Furniture | Vehicles | Software | Leasehold | Buildings | Land | Intangible serialNumber String? @db.VarChar(100) location String? @db.VarChar(255) custodian String? @db.VarChar(255) // Optional GL link (asset & accumulated depreciation accounts) assetAccountCode String? @db.VarChar(50) accumDepreciationAccountCode String? @db.VarChar(50) depreciationExpenseAccountCode String? @db.VarChar(50) // Financials acquisitionDate DateTime originalCost Decimal @db.Decimal(19,2) salvageValue Decimal @default(0) @db.Decimal(19,2) usefulLifeYears Int depreciationMethod String @default("STRAIGHT_LINE") @db.VarChar(30) // STRAIGHT_LINE | DOUBLE_DECLINING | UNITS_OF_PRODUCTION // Tracking accumulatedDepreciation Decimal @default(0) @db.Decimal(19,2) lastDepreciationDate DateTime? // Status status String @default("ACTIVE") @db.VarChar(20) // ACTIVE | DISPOSED | FULLY_DEPRECIATED | IDLE disposalDate DateTime? disposalProceeds Decimal? @db.Decimal(19,2) disposalNotes String? @db.Text metadata Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdBy String? @db.VarChar(255) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@unique([organizationId, assetTag]) @@index([organizationId]) @@index([category]) @@index([status]) @@map("fixed_assets") } // ============================================================================ // EVE ONLINE SSO & ESI INTEGRATION // ============================================================================ enum EsiSyncStatus { IDLE RUNNING COMPLETED FAILED } /// Stores EVE SSO OAuth2 tokens for authenticated characters. /// Each ERP user can link one or more EVE characters. model EveCharacter { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid organizationId String @db.Uuid // EVE character identity (from JWT sub claim) characterId Int @unique characterName String @db.VarChar(255) characterOwnerHash String @db.VarChar(255) // Corporation affiliation corporationId Int? corporationName String? @db.VarChar(255) allianceId Int? allianceName String? @db.VarChar(255) // OAuth2 tokens (encrypted at rest in production) accessToken String @db.Text refreshToken String @db.Text tokenExpiresAt DateTime scopes String @db.Text // Space-separated granted scopes // Status isActive Boolean @default(true) isPrimary Boolean @default(false) // Primary character for this user lastSyncAt DateTime? tokenError String? @db.Text // Last token refresh error, if any createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations user User @relation(fields: [userId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) syncJobs EsiSyncJob[] @@index([userId]) @@index([organizationId]) @@index([corporationId]) @@map("eve_characters") } /// Tracks ESI data sync operations per endpoint. /// Used for rate limiting, caching (ETag/Expires), and error tracking. model EsiSyncJob { id String @id @default(uuid()) @db.Uuid eveCharacterId String @db.Uuid organizationId String @db.Uuid // What we're syncing endpoint String @db.VarChar(255) // e.g. "corporations/{id}/wallets" corporationId Int? division Int? // Wallet division (1-7) // Sync state status EsiSyncStatus @default(IDLE) lastSuccessAt DateTime? lastFailureAt DateTime? lastErrorMessage String? @db.Text consecutiveFailures Int @default(0) // ESI cache headers etag String? @db.VarChar(255) cachedUntil DateTime? // From Expires header pagesFetched Int @default(0) // For paginated endpoints totalPages Int? // Stats recordsSynced Int @default(0) lastRunDurationMs Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations eveCharacter EveCharacter @relation(fields: [eveCharacterId], references: [id], onDelete: Cascade) @@unique([eveCharacterId, endpoint, division]) @@index([organizationId]) @@index([status]) @@index([cachedUntil]) @@map("esi_sync_jobs") } /// Cached ESI wallet journal entries before they're mapped to GL journal entries. /// Raw data from /corporations/{id}/wallets/{division}/journal/ model EsiWalletJournal { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid corporationId Int division Int // ESI fields esiId BigInt // ESI journal ref_id date DateTime refType String @db.VarChar(100) // e.g. "market_transaction", "contract_price" firstPartyId Int? firstPartyType String? @db.VarChar(50) // character, corporation, etc. secondPartyId Int? secondPartyType String? @db.VarChar(50) amount Decimal @db.Decimal(19, 4) // ISK amount (positive or negative) balance Decimal @db.Decimal(19, 4) // Running balance after entry reason String? @db.Text description String? @db.Text taxAmount Decimal? @db.Decimal(19, 4) taxReceiverId Int? contextId BigInt? contextIdType String? @db.VarChar(50) // ERP mapping journalEntryId String? @db.Uuid // Link to GL JournalEntry once mapped isMapped Boolean @default(false) mappedAt DateTime? createdAt DateTime @default(now()) @@unique([corporationId, division, esiId]) @@index([organizationId]) @@index([corporationId, division, date]) @@index([isMapped]) @@index([refType]) @@map("esi_wallet_journal") } /// Cached ESI wallet transactions (market buys/sells). /// From /corporations/{id}/wallets/{division}/transactions/ model EsiWalletTransaction { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid corporationId Int division Int // ESI fields transactionId BigInt date DateTime typeId Int // EVE type ID (item) typeName String? @db.VarChar(255) quantity Int unitPrice Decimal @db.Decimal(19, 4) clientId Int // Buyer/seller character/corp clientName String? @db.VarChar(255) locationId BigInt // Station/structure ID locationName String? @db.VarChar(255) isBuy Boolean // true = purchase, false = sale journalRefId BigInt // Links to wallet journal entry // ERP mapping invoiceId String? @db.Uuid // Link to AR/AP invoice once mapped isMapped Boolean @default(false) mappedAt DateTime? createdAt DateTime @default(now()) @@unique([corporationId, division, transactionId]) @@index([organizationId]) @@index([corporationId, division, date]) @@index([isMapped]) @@index([typeId]) @@map("esi_wallet_transactions") } /// Cached ESI market orders. /// Sell orders → Accounts Receivable, Buy orders → Accounts Payable model EsiMarketOrder { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid corporationId Int // ESI fields orderId BigInt @unique typeId Int typeName String? @db.VarChar(255) locationId BigInt locationName String? @db.VarChar(255) regionId Int price Decimal @db.Decimal(19, 4) volumeTotal Int volumeRemain Int isBuyOrder Boolean issued DateTime duration Int // Days minVolume Int @default(1) state String @db.VarChar(50) // active, cancelled, expired, fulfilled escrow Decimal? @db.Decimal(19, 4) // For buy orders walletDivision Int // ERP mapping invoiceId String? @db.Uuid isMapped Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([corporationId, state]) @@index([typeId]) @@map("esi_market_orders") } /// Cached ESI industry jobs. /// Maps to Project Accounting for cost tracking per manufacturing run. model EsiIndustryJob { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid corporationId Int // ESI fields jobId Int @unique installerId Int installerName String? @db.VarChar(255) facilityId BigInt activityId Int // 1=manufacturing, 3=TE, 4=ME, 5=copying, 8=invention blueprintId BigInt blueprintTypeId Int blueprintTypeName String? @db.VarChar(255) blueprintLocationId BigInt outputLocationId BigInt runs Int cost Decimal? @db.Decimal(19, 4) // Installation cost licensedRuns Int? probability Float? productTypeId Int? productTypeName String? @db.VarChar(255) status String @db.VarChar(50) // active, cancelled, delivered, paused, ready, reverted startDate DateTime endDate DateTime pauseDate DateTime? completedDate DateTime? completedCharacterId Int? successfulRuns Int? // ERP mapping - links to Project in ERP projectId String? @db.VarChar(255) isMapped Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([corporationId, status]) @@index([activityId]) @@map("esi_industry_jobs") } /// Cached ESI blueprints. /// Maps to Fixed Assets with ME/TE as capital improvements. model EsiBlueprint { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid corporationId Int // ESI fields itemId BigInt @unique typeId Int typeName String? @db.VarChar(255) locationId BigInt locationFlag String @db.VarChar(100) quantity Int // -1 = original, -2 = copy, >0 = stack of BPCs materialEfficiency Int // 0-10 timeEfficiency Int // 0-20 runs Int // -1 = BPO (infinite), >0 = BPC runs remaining // ERP mapping - links to FixedAsset fixedAssetId String? @db.Uuid isMapped Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([corporationId]) @@index([typeId]) @@map("esi_blueprints") } /// Cached ESI corporation contracts. /// Maps to Purchase Orders with fulfillment tracking. model EsiContract { id String @id @default(uuid()) @db.Uuid organizationId String @db.Uuid corporationId Int // ESI fields contractId Int @unique issuerId Int issuerCorporationId Int assigneeId Int acceptorId Int @default(0) contractType String @db.VarChar(50) // item_exchange, courier, auction, unknown status String @db.VarChar(50) // outstanding, in_progress, finished, etc. title String? @db.VarChar(255) availability String @db.VarChar(50) // public, personal, corporation, alliance forCorporation Boolean dateIssued DateTime dateExpired DateTime dateAccepted DateTime? dateCompleted DateTime? daysToComplete Int? price Decimal @db.Decimal(19, 4) reward Decimal @default(0) @db.Decimal(19, 4) collateral Decimal @default(0) @db.Decimal(19, 4) buyout Decimal? @db.Decimal(19, 4) volume Decimal? @db.Decimal(19, 4) startLocationId BigInt? endLocationId BigInt? // ERP mapping purchaseOrderId String? @db.VarChar(255) isMapped Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([organizationId]) @@index([corporationId, status]) @@index([contractType]) @@map("esi_contracts") }