Skip to content

Database Schema

Complete Prisma database schema reference for the Keshless API.

Overview

MetricCount
Models41
Enums25
Total Lines1,279

Model Categories

CategoryModels
Core EntitiesUser, Vendor, VendorSubUser, AdminEmployee, NFCCard, OTP, Notification
TransactionsWalletTransaction, UserWalletTransaction, VendorWalletTransaction, UserTransactionTracking, WithdrawalRequest
AccountingChartOfAccounts, JournalEntry, LedgerEntry, AccountBalance, UserAccountBalance, VendorAccountBalance, UserJournalEntry, VendorJournalEntry, Reconciliation
AML/ComplianceCustomerRiskProfile, Alert, AMLRule, RuleViolation, SAR, PEP, SanctionsListSync, RiskScoreHistory, MonitoringConfig, MonitoringAuditLog
SystemAuditLog, EmergencyControl, SystemEmergencyControl, Job, Integration, KeshlessUserAccess
ConfigurationFeeConfig, TransactionLimitConfig, PINConfig, FAQ
OtherTill, CardOperationRequest, ParkingRate, ParkingSession

Entity Relationship Diagrams

Core Entities

Transaction Flow

Accounting System

AML/Compliance System


Relationships Reference

User Relationships

RelationTypeTargetDescription
assignedCard1:1NFCCardPhysical NFC card
customerRiskProfile1:1CustomerRiskProfileAML risk assessment
walletTransactions1:manyUserWalletTransactionTransaction links
journalEntries1:manyUserJournalEntryAccounting entries
accountBalances1:manyUserAccountBalanceBalance records
transactionTracking1:manyUserTransactionTrackingLimit tracking
alerts1:manyAlertCompliance alerts
sars1:manySARSuspicious activity reports
referredByselfUserReferral source
referralsselfUser[]Users referred

Vendor Relationships

RelationTypeTargetDescription
subUsers1:manyVendorSubUserCashiers/employees
walletTransactions1:manyVendorWalletTransactionTransaction links
journalEntries1:manyVendorJournalEntryAccounting entries
accountBalances1:manyVendorAccountBalanceBalance records
integrations1:manyIntegrationAPI integrations
tills1:manyTillCash registers
withdrawalRequests1:manyWithdrawalRequestWithdrawal requests
alerts1:manyAlertCompliance alerts
sars1:manySARSuspicious activity reports

Transaction Relationships

ModelRelationTarget
WalletTransactionuserLinkUserWalletTransaction
WalletTransactionvendorLinkVendorWalletTransaction
WalletTransactionalertsAlert[]
JournalEntryledgerEntriesLedgerEntry[]
JournalEntryuserLinkUserJournalEntry
JournalEntryvendorLinkVendorJournalEntry
AccountBalanceuserLinkUserAccountBalance
AccountBalancevendorLinkVendorAccountBalance

Full Schema

Core Models

User

prisma
model User {
  id                    String                    @id @default(cuid())
  email                 String?                   @unique
  password              String?
  firstName             String?
  lastName              String?
  phoneNumber           String?                   @unique
  isActive              Boolean                   @default(true)
  isVerified            Boolean                   @default(false)
  role                  UserRole                  @default(USER)
  walletBalance         Decimal                   @default(0) @db.Decimal(15, 2)
  walletId              String?                   @unique
  walletPin             String?
  nfcCardNumber         String?                   @unique
  cardLinkedAt          DateTime?
  surname               String?
  names                 String?
  personalIdNumber      String?                   @unique
  dateOfBirth           DateTime?
  sex                   String?
  gender                String?
  chiefCode             String?
  sourceOfFunds         String?
  occupation            String?
  expectedMonthlySalary Decimal?                  @db.Decimal(15, 2)
  idFrontImage          String?
  idBackImage           String?
  selfieImage           String?
  verificationStatus    VerificationStatus        @default(PENDING)
  ocrExtractedData      Json?
  currentRiskScore      Int                       @default(0)
  riskRating            RiskRating                @default(LOW)
  isPEP                 Boolean                   @default(false)
  pepDetails            Json?
  accountRestrictions   Json?
  enhancedMonitoring    Boolean                   @default(false)
  rewardsPoints         Int                       @default(0)
  totalRewardsEarned    Int                       @default(0)
  referralCode          String?                   @unique
  referredById          String?
  totalReferrals        Int                       @default(0)
  profilePhoto          String?
  preferences           Json?
  deletionRequested     Boolean                   @default(false)
  deletionRequestedAt   DateTime?
  deletionScheduledFor  DateTime?
  deletedById           String?
  createdAt             DateTime                  @default(now())
  updatedAt             DateTime                  @updatedAt
  isSanctioned          Boolean                   @default(false)
  sanctionsDetails      Json?
  assignedAlerts        Alert[]                   @relation("AlertAssignedTo")
  alerts                Alert[]                   @relation("UserAlerts")
  resolvedAlerts        Alert[]                   @relation("AlertResolvedBy")
  customerRiskProfile   CustomerRiskProfile?
  assignedCard          NFCCard?
  sars                  SAR[]                     @relation("UserSARs")
  filedSars             SAR[]                     @relation("SARFiledBy")
  accountBalances       UserAccountBalance[]
  journalEntries        UserJournalEntry[]
  transactionTracking   UserTransactionTracking[]
  walletTransactions    UserWalletTransaction[]
  referredBy            User?                     @relation("UserReferrals", fields: [referredById], references: [id])
  referrals             User[]                    @relation("UserReferrals")

  @@index([verificationStatus])
  @@index([riskRating])
  @@index([currentRiskScore])
  @@index([isPEP])
  @@index([isSanctioned])
  @@index([createdAt])
  @@map("users")
}

Vendor

prisma
model Vendor {
  id                   String                    @id @default(cuid())
  email                String?                   @unique
  phoneNumber          String?                   @unique
  password             String
  businessName         String
  slug                 String                    @unique
  subUserCounter       Int                       @default(0)
  businessType         BusinessType              @default(OTHER)
  vendorType           VendorType?
  businessRegistration String?                   @unique
  taxNumber            String?                   @unique
  primaryContact       String?
  address              Json?
  location             Json?
  leaseAgreement       String?
  directorIdFront      String?
  directorIdBack       String?
  formJ                String?
  formC                String?
  kycDocuments         Json?
  verificationStatus   VendorVerificationStatus  @default(PENDING)
  verifiedAt           DateTime?
  verifiedById         String?
  rejectionReason      String?
  walletBalance        Decimal                   @default(0) @db.Decimal(15, 2)
  walletId             String?                   @unique
  walletPin            String?
  firstLoginPin        String?
  firstLoginPinExpiry  DateTime?
  firstLoginPinUsed    Boolean                   @default(false)
  mustChangePassword   Boolean                   @default(true)
  firstLogin           Boolean                   @default(true)
  acceptsNFC           Boolean                   @default(true)
  terminalId           String?                   @unique
  isActive             Boolean                   @default(true)
  isVerified           Boolean                   @default(false)
  deletionRequested    Boolean                   @default(false)
  deletionRequestedAt  DateTime?
  deletionScheduledFor DateTime?
  deletedById          String?
  currentRiskScore     Int                       @default(0)
  riskRating           RiskRating                @default(LOW)
  isPEP                Boolean                   @default(false)
  accountRestrictions  Json?
  enhancedMonitoring   Boolean                   @default(false)
  apps                 Json?
  createdAt            DateTime                  @default(now())
  updatedAt            DateTime                  @updatedAt
  alerts               Alert[]                   @relation("VendorAlerts")
  integrations         Integration[]
  sars                 SAR[]                     @relation("VendorSARs")
  tills                Till[]
  accountBalances      VendorAccountBalance[]
  journalEntries       VendorJournalEntry[]
  subUsers             VendorSubUser[]
  walletTransactions   VendorWalletTransaction[]
  withdrawalRequests   WithdrawalRequest[]

  @@index([verificationStatus, createdAt])
  @@index([isActive, isVerified])
  @@index([riskRating])
  @@index([currentRiskScore])
  @@map("vendors")
}

VendorSubUser

prisma
model VendorSubUser {
  id                  String    @id @default(cuid())
  vendorId            String
  name                String
  username            String    @unique
  email               String?   @unique
  phoneNumber         String?   @unique
  password            String?
  pin                 String?
  role                String    @default("cashier")
  permissions         Json?
  isActive            Boolean   @default(true)
  firstLogin          Boolean   @default(true)
  mustChangePassword  Boolean   @default(true)
  firstLoginPin       String?
  firstLoginPinExpiry DateTime?
  firstLoginPinUsed   Boolean   @default(false)
  lastLoginAt         DateTime?
  createdAt           DateTime  @default(now())
  updatedAt           DateTime  @updatedAt
  vendor              Vendor    @relation(fields: [vendorId], references: [id], onDelete: Cascade)

  @@index([vendorId])
  @@map("vendor_sub_users")
}

AdminEmployee

prisma
model AdminEmployee {
  id                 String            @id @default(cuid())
  email              String            @unique
  password           String
  firstName          String
  lastName           String
  phoneNumber        String?           @unique
  role               AdminEmployeeRole @default(CUSTOM)
  permissions        Json
  isActive           Boolean           @default(true)
  createdById        String?
  firstLogin         Boolean           @default(true)
  mustChangePassword Boolean           @default(true)
  lastLoginAt        DateTime?
  createdAt          DateTime          @default(now())
  updatedAt          DateTime          @updatedAt

  @@index([role])
  @@index([isActive])
  @@index([email])
  @@map("admin_employees")
}

NFCCard

prisma
model NFCCard {
  id             String     @id @default(cuid())
  cardNumber     String     @unique
  status         CardStatus @default(AVAILABLE)
  assignedUserId String?    @unique
  linkedAt       DateTime?
  expiryMonth    Int?
  expiryYear     Int?
  blockedReason  String?
  createdAt      DateTime   @default(now())
  updatedAt      DateTime   @updatedAt
  assignedUser   User?      @relation(fields: [assignedUserId], references: [id])

  @@index([status])
  @@map("nfc_cards")
}

OTP

prisma
model OTP {
  id          String     @id @default(cuid())
  phoneNumber String
  otp         String
  purpose     OTPPurpose
  isVerified  Boolean    @default(false)
  attempts    Int        @default(0)
  expiresAt   DateTime
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt

  @@index([phoneNumber, purpose, isVerified])
  @@index([expiresAt])
  @@map("otps")
}

Notification

prisma
model Notification {
  id                String    @id @default(cuid())
  recipientType     String
  recipientUserId   String?
  recipientVendorId String?
  title             String
  message           String
  type              String
  data              Json?
  isRead            Boolean   @default(false)
  readAt            DateTime?
  createdAt         DateTime  @default(now())
  updatedAt         DateTime  @updatedAt

  @@index([recipientUserId, isRead, createdAt])
  @@index([recipientVendorId, isRead, createdAt])
  @@map("notifications")
}

Transaction Models

WalletTransaction

prisma
model WalletTransaction {
  id                   String                   @id @default(cuid())
  type                 WalletTransactionType
  amount               Decimal                  @db.Decimal(15, 2)
  balanceBefore        Decimal                  @db.Decimal(15, 2)
  balanceAfter         Decimal                  @db.Decimal(15, 2)
  status               WalletTransactionStatus  @default(PENDING)
  description          String?
  reference            String?                  @unique
  counterpartyUserId   String?
  counterpartyVendorId String?
  performedById        String?
  performedByType      String?
  vendorContextId      String?
  metadata             Json?
  processedAt          DateTime?
  failureReason        String?
  amlScreened          Boolean                  @default(false)
  amlStatus            AMLStatus                @default(NOT_SCREENED)
  amlScreenedAt        DateTime?
  riskFlags            String[]
  suspicionScore       Int?
  createdAt            DateTime                 @default(now())
  updatedAt            DateTime                 @updatedAt
  counterpartyType     String?
  entityType           String?
  entityUserId         String?
  entityVendorId       String?
  userLink             UserWalletTransaction?
  vendorLink           VendorWalletTransaction?
  alerts               Alert[]                  @relation("AlertToWalletTransaction")

  @@index([status, createdAt])
  @@index([type, status, createdAt])
  @@index([entityUserId, createdAt])
  @@index([entityVendorId, createdAt])
  @@index([entityType, createdAt])
  @@map("wallet_transactions")
}

UserWalletTransaction

prisma
model UserWalletTransaction {
  id            String            @id @default(cuid())
  userId        String
  transactionId String            @unique
  transaction   WalletTransaction @relation(fields: [transactionId], references: [id])
  user          User              @relation(fields: [userId], references: [id])

  @@index([userId])
  @@map("user_wallet_transactions")
}

VendorWalletTransaction

prisma
model VendorWalletTransaction {
  id            String            @id @default(cuid())
  vendorId      String
  transactionId String            @unique
  transaction   WalletTransaction @relation(fields: [transactionId], references: [id])
  vendor        Vendor            @relation(fields: [vendorId], references: [id])

  @@index([vendorId])
  @@map("vendor_wallet_transactions")
}

UserTransactionTracking

prisma
model UserTransactionTracking {
  id              String   @id @default(cuid())
  userId          String
  transactionType String
  periodStart     DateTime
  periodEnd       DateTime
  dailyAmount     Decimal  @default(0) @db.Decimal(15, 2)
  dailyCount      Int      @default(0)
  monthlyAmount   Decimal  @default(0) @db.Decimal(15, 2)
  monthlyCount    Int      @default(0)
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  userType        String?
  user            User     @relation(fields: [userId], references: [id])

  @@unique([userId, transactionType, periodStart])
  @@index([userId, transactionType])
  @@map("user_transaction_trackings")
}

WithdrawalRequest

prisma
model WithdrawalRequest {
  id                  String                  @id @default(cuid())
  requesterType       String
  requesterUserId     String?
  requesterVendorId   String?
  amount              Decimal                 @db.Decimal(15, 2)
  fee                 Decimal                 @default(0) @db.Decimal(15, 2)
  netAmount           Decimal                 @db.Decimal(15, 2)
  status              WithdrawalRequestStatus @default(PENDING)
  bankName            String?
  accountNumber       String?
  accountHolder       String?
  processedById       String?
  processedAt         DateTime?
  rejectionReason     String?
  walletTransactionId String?
  createdAt           DateTime                @default(now())
  updatedAt           DateTime                @updatedAt
  vendor              Vendor?                 @relation(fields: [requesterVendorId], references: [id])

  @@index([requesterVendorId, status])
  @@index([status, createdAt])
  @@map("withdrawal_requests")
}

Accounting Models

ChartOfAccounts

prisma
model ChartOfAccounts {
  id            String        @id @default(cuid())
  accountCode   String        @unique
  accountName   String
  accountType   AccountType
  normalBalance NormalBalance
  parentCode    String?
  level         Int           @default(1)
  description   String?
  isActive      Boolean       @default(true)
  isSummary     Boolean       @default(false)
  createdAt     DateTime      @default(now())
  updatedAt     DateTime      @updatedAt

  @@index([accountType])
  @@index([isActive])
  @@map("chart_of_accounts")
}

JournalEntry

prisma
model JournalEntry {
  id                  String              @id @default(cuid())
  journalId           String              @unique
  entryType           JournalEntryType
  transactionDate     DateTime
  postingDate         DateTime?
  status              JournalEntryStatus  @default(PENDING)
  totalDebits         Decimal             @db.Decimal(15, 2)
  totalCredits        Decimal             @db.Decimal(15, 2)
  currency            String              @default("E")
  walletTransactionId String?
  transactionId       String?
  reversedByJournalId String?
  reversalOfJournalId String?
  description         String
  notes               String?
  metadata            Json?
  amlScreened         Boolean             @default(false)
  amlStatus           String?
  riskScore           Int?
  createdById         String?
  postedById          String?
  reversedById        String?
  ipAddress           String?
  deviceId            String?
  createdAt           DateTime            @default(now())
  updatedAt           DateTime            @updatedAt
  ledgerEntries       LedgerEntry[]
  userLink            UserJournalEntry?
  vendorLink          VendorJournalEntry?

  @@index([status, transactionDate])
  @@index([entryType, status, transactionDate])
  @@index([walletTransactionId])
  @@index([amlStatus, status])
  @@index([createdAt])
  @@map("journal_entries")
}

LedgerEntry

prisma
model LedgerEntry {
  id                   String       @id @default(cuid())
  entryId              String       @unique
  journalEntryId       String
  accountCode          String
  accountName          String
  debit                Decimal      @default(0) @db.Decimal(15, 2)
  credit               Decimal      @default(0) @db.Decimal(15, 2)
  runningBalance       Decimal      @default(0) @db.Decimal(15, 2)
  currency             String       @default("E")
  entityType           String?
  entityUserId         String?
  entityVendorId       String?
  counterpartyType     String?
  counterpartyUserId   String?
  counterpartyVendorId String?
  walletTransactionId  String?
  transactionId        String?
  transactionDate      DateTime
  postingDate          DateTime?
  description          String?
  createdById          String?
  ipAddress            String?
  deviceId             String?
  isPosted             Boolean      @default(false)
  createdAt            DateTime     @default(now())
  updatedAt            DateTime     @updatedAt
  journalEntry         JournalEntry @relation(fields: [journalEntryId], references: [id])

  @@index([journalEntryId])
  @@index([accountCode, isPosted])
  @@index([entityUserId, transactionDate])
  @@index([entityVendorId, transactionDate])
  @@map("ledger_entries")
}

AccountBalance

prisma
model AccountBalance {
  id                  String                @id @default(cuid())
  accountCode         String
  accountName         String
  balance             Decimal               @default(0) @db.Decimal(15, 2)
  debitTotal          Decimal               @default(0) @db.Decimal(15, 2)
  creditTotal         Decimal               @default(0) @db.Decimal(15, 2)
  currency            String                @default("E")
  lastLedgerEntryId   String?
  lastJournalEntryId  String?
  lastTransactionDate DateTime?
  version             Int                   @default(0)
  isReconciled        Boolean               @default(true)
  lastReconciledAt    DateTime?
  createdAt           DateTime              @default(now())
  updatedAt           DateTime              @updatedAt
  userLink            UserAccountBalance?
  vendorLink          VendorAccountBalance?

  @@index([accountCode])
  @@map("account_balances")
}

UserAccountBalance

prisma
model UserAccountBalance {
  id               String         @id @default(cuid())
  userId           String
  accountBalanceId String         @unique
  accountBalance   AccountBalance @relation(fields: [accountBalanceId], references: [id])
  user             User           @relation(fields: [userId], references: [id])

  @@unique([userId, accountBalanceId])
  @@index([userId])
  @@map("user_account_balances")
}

VendorAccountBalance

prisma
model VendorAccountBalance {
  id               String         @id @default(cuid())
  vendorId         String
  accountBalanceId String         @unique
  accountBalance   AccountBalance @relation(fields: [accountBalanceId], references: [id])
  vendor           Vendor         @relation(fields: [vendorId], references: [id])

  @@unique([vendorId, accountBalanceId])
  @@index([vendorId])
  @@map("vendor_account_balances")
}

UserJournalEntry

prisma
model UserJournalEntry {
  id             String       @id @default(cuid())
  userId         String
  journalEntryId String       @unique
  journalEntry   JournalEntry @relation(fields: [journalEntryId], references: [id])
  user           User         @relation(fields: [userId], references: [id])

  @@index([userId])
  @@map("user_journal_entries")
}

VendorJournalEntry

prisma
model VendorJournalEntry {
  id             String       @id @default(cuid())
  vendorId       String
  journalEntryId String       @unique
  journalEntry   JournalEntry @relation(fields: [journalEntryId], references: [id])
  vendor         Vendor       @relation(fields: [vendorId], references: [id])

  @@index([vendorId])
  @@map("vendor_journal_entries")
}

Reconciliation

prisma
model Reconciliation {
  id                 String    @id @default(cuid())
  reconciliationType String
  periodStart        DateTime
  periodEnd          DateTime
  accountCode        String?
  entityType         String?
  entityUserId       String?
  entityVendorId     String?
  systemBalance      Decimal   @db.Decimal(15, 2)
  calculatedBalance  Decimal   @db.Decimal(15, 2)
  difference         Decimal   @db.Decimal(15, 2)
  isReconciled       Boolean   @default(false)
  discrepancies      Json?
  resolution         String?
  reconciledById     String?
  reconciledAt       DateTime?
  notes              String?
  createdAt          DateTime  @default(now())
  updatedAt          DateTime  @updatedAt

  @@index([periodStart, periodEnd])
  @@index([accountCode])
  @@map("reconciliations")
}

AML/Compliance Models

CustomerRiskProfile

prisma
model CustomerRiskProfile {
  id                   String     @id @default(cuid())
  userId               String     @unique
  overallRiskScore     Int        @default(0)
  riskRating           RiskRating @default(UNRATED)
  identityRiskScore    Int        @default(0)
  behavioralRiskScore  Int        @default(0)
  geographicRiskScore  Int        @default(0)
  transactionRiskScore Int        @default(0)
  pepStatus            Boolean    @default(false)
  pepMatches           Json?
  sanctionsScreened    Boolean    @default(false)
  sanctionsMatches     Json?
  lastAssessmentDate   DateTime?
  nextReviewDate       DateTime?
  enhancedDueDiligence Boolean    @default(false)
  riskFactors          Json?
  mitigatingFactors    Json?
  notes                String?
  createdAt            DateTime   @default(now())
  updatedAt            DateTime   @updatedAt
  user                 User       @relation(fields: [userId], references: [id])

  @@index([riskRating])
  @@index([overallRiskScore])
  @@map("customer_risk_profiles")
}

Alert

prisma
model Alert {
  id                  String              @id @default(cuid())
  alertId             String              @unique
  entityType          String
  entityUserId        String?
  entityVendorId      String?
  alertType           String
  severity            AlertSeverity       @default(MEDIUM)
  status              AlertStatus         @default(OPEN)
  title               String
  description         String
  triggerData         Json?
  relatedTransactions Json?
  riskIndicators      Json?
  assignedTo          String?
  assignedAt          DateTime?
  escalatedTo         String?
  escalatedAt         DateTime?
  escalationReason    String?
  resolvedById        String?
  resolvedAt          DateTime?
  resolutionNotes     String?
  createdAt           DateTime            @default(now())
  updatedAt           DateTime            @updatedAt
  attachments         Json?
  escalationHistory   Json?
  linkedAlertIds      String[]
  notes               Json?
  priority            String?             @default("medium")
  resolution          Json?
  assignedToUser      User?               @relation("AlertAssignedTo", fields: [assignedTo], references: [id])
  entityUser          User?               @relation("UserAlerts", fields: [entityUserId], references: [id])
  entityVendor        Vendor?             @relation("VendorAlerts", fields: [entityVendorId], references: [id])
  resolvedByUser      User?               @relation("AlertResolvedBy", fields: [resolvedById], references: [id])
  transactions        WalletTransaction[] @relation("AlertToWalletTransaction")
  alerts_A            Alert[]             @relation("LinkedAlerts")
  alerts_B            Alert[]             @relation("LinkedAlerts")

  @@index([status, severity, createdAt])
  @@index([entityUserId])
  @@index([entityVendorId])
  @@index([alertType, status])
  @@map("alerts")
}

AMLRule

prisma
model AMLRule {
  id          String        @id @default(cuid())
  ruleId      String        @unique
  name        String
  description String?
  category    String
  conditions  Json
  actions     Json
  severity    AlertSeverity @default(MEDIUM)
  isActive    Boolean       @default(true)
  priority    Int           @default(100)
  createdById String?
  updatedById String?
  createdAt   DateTime      @default(now())
  updatedAt   DateTime      @updatedAt

  @@index([category, isActive])
  @@index([priority])
  @@map("aml_rules")
}

RuleViolation

prisma
model RuleViolation {
  id               String        @id @default(cuid())
  ruleId           String
  alertId          String?
  entityType       String
  entityUserId     String?
  entityVendorId   String?
  transactionId    String?
  violationDetails Json?
  severity         AlertSeverity
  createdAt        DateTime      @default(now())

  @@index([ruleId])
  @@index([alertId])
  @@index([entityUserId])
  @@index([entityVendorId])
  @@map("rule_violations")
}

SAR

prisma
model SAR {
  id                  String    @id @default(cuid())
  sarId               String    @unique
  entityType          String
  entityUserId        String?
  entityVendorId      String?
  alertIds            String[]
  reportingReason     String
  suspiciousActivity  Json
  supportingDocuments Json?
  status              String    @default("DRAFT")
  submittedAt         DateTime?
  submittedById       String?
  fiuReference        String?
  fiuResponseDate     DateTime?
  fiuResponse         Json?
  createdAt           DateTime  @default(now())
  updatedAt           DateTime  @updatedAt
  approvedAt          DateTime?
  approvedBy          String?
  draftedAt           DateTime?
  filedById           String?
  reviewedAt          DateTime?
  reviewedBy          String?
  entityUser          User?     @relation("UserSARs", fields: [entityUserId], references: [id])
  entityVendor        Vendor?   @relation("VendorSARs", fields: [entityVendorId], references: [id])
  filedBy             User?     @relation("SARFiledBy", fields: [filedById], references: [id])

  @@index([status])
  @@index([entityUserId])
  @@index([entityVendorId])
  @@map("sars")
}

PEP

prisma
model PEP {
  id             String    @id @default(cuid())
  name           String
  normalizedName String
  category       String
  position       String?
  country        String?
  source         String?
  sourceDate     DateTime?
  additionalInfo Json?
  isActive       Boolean   @default(true)
  createdAt      DateTime  @default(now())
  updatedAt      DateTime  @updatedAt

  @@index([normalizedName])
  @@index([category])
  @@index([isActive])
  @@map("peps")
}

SanctionsListSync

prisma
model SanctionsListSync {
  id               String    @id @default(cuid())
  listType         String
  syncedAt         DateTime  @default(now())
  status           String
  etag             String?
  lastModified     DateTime?
  fileHash         String?
  individualsCount Int?
  entitiesCount    Int?
  errorMessage     String?
  syncDuration     Int?

  @@index([listType, syncedAt])
  @@map("sanctions_list_syncs")
}

RiskScoreHistory

prisma
model RiskScoreHistory {
  id             String     @id @default(cuid())
  entityType     String
  entityUserId   String?
  entityVendorId String?
  previousScore  Int
  newScore       Int
  previousRating RiskRating
  newRating      RiskRating
  changeReason   String
  changeFactors  Json?
  changedById    String?
  createdAt      DateTime   @default(now())

  @@index([entityUserId, createdAt])
  @@index([entityVendorId, createdAt])
  @@map("risk_score_histories")
}

MonitoringConfig

prisma
model MonitoringConfig {
  id          String   @id @default(cuid())
  configKey   String   @unique
  configValue Json
  description String?
  isActive    Boolean  @default(true)
  updatedById String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@map("monitoring_configs")
}

MonitoringAuditLog

prisma
model MonitoringAuditLog {
  id            String   @id @default(cuid())
  action        String
  performedById String?
  targetType    String?
  targetId      String?
  details       Json?
  ipAddress     String?
  createdAt     DateTime @default(now())

  @@index([action, createdAt])
  @@index([targetType, targetId])
  @@map("monitoring_audit_logs")
}

System Models

AuditLog

prisma
model AuditLog {
  id             String         @id @default(cuid())
  eventType      AuditEventType
  description    String
  actorId        String?
  actorType      String?
  targetType     String?
  targetId       String?
  entityType     String?
  entityUserId   String?
  entityVendorId String?
  amount         Decimal?       @db.Decimal(15, 2)
  currency       String?
  ipAddress      String?
  userAgent      String?
  deviceId       String?
  requestId      String?
  metadata       Json?
  previousState  Json?
  newState       Json?
  hashPrevious   String?
  hashCurrent    String?
  createdAt      DateTime       @default(now())

  @@index([eventType, createdAt])
  @@index([targetType, targetId])
  @@index([entityUserId])
  @@index([entityVendorId])
  @@index([createdAt])
  @@map("audit_logs")
}

EmergencyControl

prisma
model EmergencyControl {
  id               String    @id @default(cuid())
  controlType      String    @unique
  isActive         Boolean   @default(false)
  activatedAt      DateTime?
  activatedById    String?
  reason           String?
  affectedServices String[]
  createdAt        DateTime  @default(now())
  updatedAt        DateTime  @updatedAt

  @@map("emergency_controls")
}

SystemEmergencyControl

prisma
model SystemEmergencyControl {
  id              String                @id @default(cuid())
  activatedAt     DateTime              @default(now())
  activatedById   String
  reason          String
  createdAt       DateTime              @default(now())
  updatedAt       DateTime              @updatedAt
  auditLog        Json?
  controlType     SystemEmergencyType
  deactivatedAt   DateTime?
  deactivatedById String?
  parameters      Json?
  resolution      String?
  scope           String                @default("global")
  status          SystemEmergencyStatus @default(ACTIVE)
  triggerEvent    Json?

  @@index([controlType, status])
  @@index([status, activatedAt(sort: Desc)])
  @@map("system_emergency_controls")
}

Job

prisma
model Job {
  id             String    @id @default(cuid())
  jobType        String
  status         String    @default("PENDING")
  entityType     String?
  entityUserId   String?
  entityVendorId String?
  inputData      Json?
  outputData     Json?
  errorMessage   String?
  startedAt      DateTime?
  completedAt    DateTime?
  retryCount     Int       @default(0)
  createdAt      DateTime  @default(now())
  updatedAt      DateTime  @updatedAt

  @@index([jobType, status])
  @@index([entityUserId])
  @@index([entityVendorId])
  @@map("jobs")
}

Integration

prisma
model Integration {
  id           String            @id @default(cuid())
  name         String?           @unique
  type         String?
  config       Json?
  credentials  Json?
  isActive     Boolean           @default(true)
  lastSyncAt   DateTime?
  createdAt    DateTime          @default(now())
  updatedAt    DateTime          @updatedAt
  apiKey       String?
  apiKeyPrefix String?
  appName      String?
  expiresAt    DateTime?
  ipWhitelist  String[]
  lastUsedAt   DateTime?
  requestCount Int               @default(0)
  status       IntegrationStatus @default(ACTIVE)
  vendorId     String?
  vendor       Vendor?           @relation(fields: [vendorId], references: [id])

  @@index([vendorId])
  @@index([apiKeyPrefix])
  @@map("integrations")
}

KeshlessUserAccess

prisma
model KeshlessUserAccess {
  id          String   @id @default(cuid())
  userId      String
  accessType  String
  permissions Json?
  isActive    Boolean  @default(true)
  grantedById String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([userId, accessType])
  @@map("keshless_user_accesses")
}

Configuration Models

FeeConfig

prisma
model FeeConfig {
  id          String   @id @default(cuid())
  type        FeeType  @unique
  name        String
  description String?
  tiers       Json
  currency    String   @default("SZL")
  isActive    Boolean  @default(true)
  createdById String?
  updatedById String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([type, isActive])
  @@map("fee_configs")
}

TransactionLimitConfig

prisma
model TransactionLimitConfig {
  id                 String               @id @default(cuid())
  limitType          TransactionLimitType @unique
  name               String
  description        String?
  dailyAmountLimit   Decimal              @default(5000) @db.Decimal(15, 2)
  monthlyAmountLimit Decimal              @default(100000) @db.Decimal(15, 2)
  dailyCountLimit    Int                  @default(50)
  monthlyCountLimit  Int                  @default(500)
  isActive           Boolean              @default(true)
  createdById        String?
  updatedById        String?
  createdAt          DateTime             @default(now())
  updatedAt          DateTime             @updatedAt

  @@index([limitType, isActive])
  @@map("transaction_limit_configs")
}

PINConfig

prisma
model PINConfig {
  id                   String   @id @default(cuid())
  name                 String   @default("Default PIN Configuration")
  description          String?
  pinlessAmountLimit   Decimal  @default(50) @db.Decimal(15, 2)
  pinlessDailyTapLimit Int      @default(10)
  isActive             Boolean  @default(true)
  createdById          String?
  updatedById          String?
  createdAt            DateTime @default(now())
  updatedAt            DateTime @updatedAt

  @@index([isActive])
  @@map("pin_configs")
}

FAQ

prisma
model FAQ {
  id        String   @id @default(cuid())
  question  String
  answer    String
  category  String?
  order     Int      @default(0)
  isActive  Boolean  @default(true)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([category, order])
  @@index([isActive])
  @@map("faqs")
}

Other Models

Till

prisma
model Till {
  id             String    @id @default(cuid())
  vendorId       String
  name           String
  openingBalance Decimal   @default(0) @db.Decimal(15, 2)
  currentBalance Decimal   @default(0) @db.Decimal(15, 2)
  isOpen         Boolean   @default(false)
  openedAt       DateTime?
  closedAt       DateTime?
  openedById     String?
  closedById     String?
  createdAt      DateTime  @default(now())
  updatedAt      DateTime  @updatedAt
  vendor         Vendor    @relation(fields: [vendorId], references: [id])

  @@index([vendorId, isOpen])
  @@map("tills")
}

CardOperationRequest

prisma
model CardOperationRequest {
  id              String    @id @default(cuid())
  userId          String
  cardNumber      String?
  operationType   String
  status          String    @default("PENDING")
  otpVerified     Boolean   @default(false)
  pinVerified     Boolean   @default(false)
  processedAt     DateTime?
  processedById   String?
  rejectionReason String?
  metadata        Json?
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt

  @@index([userId, status])
  @@index([cardNumber])
  @@map("card_operation_requests")
}

ParkingRate

prisma
model ParkingRate {
  id           String           @id @default(cuid())
  name         String
  ratePerHour  Decimal          @db.Decimal(10, 2)
  ratePerDay   Decimal?         @db.Decimal(10, 2)
  maxDailyRate Decimal?         @db.Decimal(10, 2)
  isActive     Boolean          @default(true)
  createdAt    DateTime         @default(now())
  updatedAt    DateTime         @updatedAt
  sessions     ParkingSession[]

  @@map("parking_rates")
}

ParkingSession

prisma
model ParkingSession {
  id                  String      @id @default(cuid())
  userId              String
  vehicleNumber       String
  rateId              String
  startTime           DateTime
  endTime             DateTime?
  duration            Int?
  amount              Decimal?    @db.Decimal(10, 2)
  status              String      @default("ACTIVE")
  paymentStatus       String      @default("PENDING")
  walletTransactionId String?
  createdAt           DateTime    @default(now())
  updatedAt           DateTime    @updatedAt
  rate                ParkingRate @relation(fields: [rateId], references: [id])

  @@index([userId, status])
  @@index([vehicleNumber])
  @@map("parking_sessions")
}

Enums

User & Authentication

prisma
enum UserRole {
  USER
  ADMIN
  SUPER_ADMIN
}

enum VerificationStatus {
  PENDING
  IN_PROGRESS
  VERIFIED
  REJECTED
  NEEDS_REVIEW
}

enum VendorVerificationStatus {
  PENDING
  IN_PROGRESS
  VERIFIED
  REJECTED
  NEEDS_REVIEW
}

enum AdminEmployeeRole {
  SUPER_ADMIN
  ADMIN
  SUPPORT_AGENT
  COMPLIANCE_OFFICER
  FINANCE_MANAGER
  AUDITOR
  CUSTOM
}

enum OTPPurpose {
  REGISTRATION
  PASSWORD_RESET
  PHONE_VERIFICATION
  TRANSACTION_VERIFICATION
  CARD_OPERATION
  PIN_RESET
  CARD_BLOCK
  CARD_UNBLOCK
  CARD_RELINK
  CARD_LINK
  VENDOR_PIN_SETUP
  LOGOUT
}

Risk & Compliance

prisma
enum RiskRating {
  LOW
  MEDIUM
  HIGH
  CRITICAL
  UNRATED
  MEDIUM_LOW
  MEDIUM_HIGH
}

enum AMLStatus {
  NOT_SCREENED
  CLEAR
  FLAGGED
  BLOCKED
  PENDING_REVIEW
}

enum AlertStatus {
  OPEN
  UNDER_REVIEW
  ESCALATED
  RESOLVED
  FALSE_POSITIVE
  RESOLVED_FALSE_POSITIVE
  RESOLVED_TRUE_POSITIVE
  CLOSED
}

enum AlertSeverity {
  LOW
  MEDIUM
  HIGH
  CRITICAL
}

Cards & NFC

prisma
enum CardStatus {
  AVAILABLE
  ASSIGNED
  BLOCKED
  EXPIRED
  LOST
  DEACTIVATED
}

Transactions

prisma
enum WalletTransactionType {
  VENDOR_PAYMENT_RECEIVED
  VENDOR_TOPUP_ISSUED
  VENDOR_TOPUP_RECEIVED
  VENDOR_WITHDRAWAL_PROCESSED
  VENDOR_COMMISSION_EARNED
  VENDOR_TRANSFER_SENT
  VENDOR_TRANSFER_RECEIVED
  USER_PAYMENT_SENT
  USER_TOPUP_RECEIVED
  USER_WITHDRAWAL_SENT
  USER_TRANSFER_SENT
  USER_TRANSFER_RECEIVED
  USER_REFUND_RECEIVED
  FEE
  ADMIN_TOPUP
  AIRTIME
  BILL_PAYMENT
  TICKET_PURCHASE
  REFUND
  ADJUSTMENT
  REVERSAL
}

enum WalletTransactionStatus {
  PENDING
  COMPLETED
  FAILED
  CANCELLED
  REVERSED
}

enum WithdrawalRequestStatus {
  PENDING
  APPROVED
  COMPLETED
  REJECTED
  CANCELLED
}

enum TransactionLimitType {
  TOPUP
  WITHDRAWAL
  SEND_MONEY
  RECEIVE_MONEY
}

enum FeeType {
  P2P
  VENDOR_WITHDRAWAL
}

Accounting

prisma
enum JournalEntryStatus {
  PENDING
  POSTED
  REVERSED
  CANCELLED
}

enum JournalEntryType {
  USER_TRANSFER
  USER_PAYMENT
  USER_TOPUP
  USER_WITHDRAWAL
  USER_REFUND
  VENDOR_COMMISSION
  VENDOR_PAYOUT
  FEE_COLLECTION
  REVERSAL
  ADJUSTMENT
  AIRTIME_PURCHASE
  BILL_PAYMENT
  OPENING_BALANCE
}

enum AccountType {
  ASSET
  LIABILITY
  EQUITY
  REVENUE
  EXPENSE
}

enum NormalBalance {
  DEBIT
  CREDIT
}

enum AuditEventType {
  JOURNAL_CREATED
  JOURNAL_POSTED
  JOURNAL_REVERSED
  JOURNAL_CANCELLED
  BALANCE_UPDATED
  RECONCILIATION_COMPLETED
  MANUAL_ADJUSTMENT
}

Business Types

prisma
enum VendorType {
  COMPANY
  PARTNERSHIP
  SOLE_TRADER
  COOPERATIVE
  CHURCH
  SPORTS_TEAM
  SCHOOL
  NGO
}

enum BusinessType {
  RETAIL
  RESTAURANT
  SERVICE
  WHOLESALE
  OTHER
}

System & Integrations

prisma
enum SystemEmergencyType {
  SYSTEM_SHUTDOWN
  DISABLE_ALL_TRANSACTIONS
  DISABLE_WITHDRAWALS
  DISABLE_P2P_TRANSFERS
  DISABLE_BILL_PAYMENTS
  DISABLE_TOPUPS
  READ_ONLY_MODE
  RATE_LIMIT_EXTREME
}

enum SystemEmergencyStatus {
  ACTIVE
  INACTIVE
}

enum IntegrationStatus {
  ACTIVE
  REVOKED
  EXPIRED
}

Internal use only - Keshless Payment Platform