Files
NeoCom/prisma/schema.prisma
2026-04-09 20:36:10 -07:00

2072 lines
71 KiB
Plaintext

// 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")
}