YeboMart Data Models — Full Schema
Complete Prisma schema with all fields, types, and relationships.
Schema Overview
Shop (1) ──┬── (*) User (Staff)
├── (*) Product ──── (*) SaleItem
├── (*) Sale ──────── (*) SaleItem
├── (*) StockLog
├── (*) Expense
├── (*) DailyReport
├── (*) AIConversation
├── (*) AuditLog
├── (*) SyncQueue
├── (*) Customer ──── (*) CustomerCredit
├── (*) Return ────── (*) ReturnItem
├── (*) Supplier ──── (*) SupplierProduct
└── (*) PurchaseOrder ─ (*) POItemCore Models
Shop
The central entity representing a business.
prisma
model Shop {
id String @id @default(cuid())
name String
ownerName String
ownerPhone String @unique // E.164 format (+26876123456)
ownerEmail String?
password String // bcrypt hash
// Business Type
businessType String @default("general")
// Options: general, tuckshop, grocery, hardware, pharmacy,
// salon, electronics, clothing, restaurant
// AI Assistant
assistantName String @default("Yebo")
// Country & Localization
countryCode String @default("SZ") // ISO: SZ, ZA, KE, NG, etc.
phoneCountryCode String @default("+268")
currencySymbol String @default("E") // E, R, KSh, ₦, etc.
currency String @default("SZL") // ISO currency code
timezone String @default("Africa/Mbabane")
// Business Settings
address String?
logoUrl String?
// License & Subscription
tier ShopTier @default(LITE)
status ShopStatus @default(ACTIVE)
licenseKey String? @unique
licenseExpiry DateTime?
// Usage Tracking (reset monthly)
monthlyTransactions Int @default(0)
monthlyStockMoves Int @default(0)
monthlyAiQueries Int @default(0)
lastBillingReset DateTime @default(now())
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
users User[]
products Product[]
sales Sale[]
expenses Expense[]
stockLogs StockLog[]
dailyReports DailyReport[]
syncQueue SyncQueue[]
aiConversations AIConversation[]
auditLogs AuditLog[]
customers Customer[]
returns Return[]
suppliers Supplier[]
purchaseOrders PurchaseOrder[]
}
enum ShopTier {
LITE // E99/mo - Basic POS
STARTER // E499/mo - + Barcode, Alerts
BUSINESS // E2499/mo - + WhatsApp, Analytics
PRO // E4999/mo - + AI Voice, Multi-loc
ENTERPRISE // E9999/mo - + API, Support
}
enum ShopStatus {
ACTIVE
SUSPENDED
}User (Staff)
Staff members with role-based permissions.
prisma
model User {
id String @id @default(cuid())
shopId String
name String
phone String
email String?
password String? // Optional - staff can use PIN
pin String? // 4-digit PIN for quick auth
role UserRole @default(CASHIER)
isActive Boolean @default(true)
// Granular Permissions
canDiscount Boolean @default(false)
canVoid Boolean @default(false)
canViewReports Boolean @default(false)
canManageStock Boolean @default(false)
// Session
refreshToken String?
lastLoginAt DateTime?
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
sales Sale[]
stockLogs StockLog[]
auditLogs AuditLog[]
@@unique([shopId, phone])
}
enum UserRole {
OWNER // Full access
MANAGER // Most features except billing
CASHIER // POS only
}Product
Inventory items with flexible pricing.
prisma
model Product {
id String @id @default(cuid())
shopId String
barcode String?
sku String? // Internal SKU
name String
description String?
category String?
attributes Json? // Dynamic attributes {size, brand, color}
// Pricing (all in local currency)
costPrice Float // Purchase/cost price
sellPrice Float // Retail unit price
wholesalePrice Float? // Bulk discount price
wholesaleMinQty Int? // Min qty for wholesale
packPrice Float? // Pack price (e.g., 6-pack)
packSize Int? // Units per pack
// Stock (always in single units)
quantity Int @default(0)
reorderAt Int @default(10) // Low stock threshold
unit String @default("each") // each, kg, litre, pack
// Flags
isActive Boolean @default(true)
trackStock Boolean @default(true)
imageUrl String?
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Offline Sync
localId String? // Client-side ID
syncedAt DateTime?
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
saleItems SaleItem[]
stockLogs StockLog[]
suppliers SupplierProduct[]
@@unique([shopId, barcode])
@@index([shopId, name])
@@index([shopId, category])
}Transaction Models
Sale
Individual sales transactions.
prisma
model Sale {
id String @id @default(cuid())
shopId String
userId String? // Cashier
customerId String? // Credit customer
// Totals
subtotal Float
discount Float @default(0)
tax Float @default(0)
totalAmount Float
// Payment
paymentMethod PaymentMethod
amountPaid Float
change Float @default(0)
// Status
status SaleStatus @default(COMPLETED)
voidReason String?
receiptNumber String? // Format: RCP-YYMMDD-XXXX
// Timestamps
createdAt DateTime @default(now())
// Offline Sync
localId String?
offlineAt DateTime?
syncedAt DateTime?
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id])
customer Customer? @relation("CustomerSales", fields: [customerId], references: [id])
items SaleItem[]
creditEntries CustomerCredit[]
@@index([shopId, createdAt])
@@index([shopId, status])
@@index([customerId])
}
enum PaymentMethod {
CASH
MOMO // MTN Mobile Money
EMALI // Eswatini Mobile Money
CARD
MIXED
CREDIT // Store credit
}
enum SaleStatus {
PENDING
COMPLETED
VOIDED
REFUNDED
}SaleItem
Line items within a sale.
prisma
model SaleItem {
id String @id @default(cuid())
saleId String
productId String
// Snapshot at time of sale
productName String
quantity Int
unitPrice Float
costPrice Float // For profit calculation
discount Float @default(0)
totalPrice Float
// Relations
sale Sale @relation(fields: [saleId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id])
@@index([saleId])
}Inventory Models
StockLog
Audit trail for all inventory changes.
prisma
model StockLog {
id String @id @default(cuid())
shopId String
productId String
userId String? // Who made the change
type StockLogType
quantity Int // +/- change amount
// Audit trail
previousQty Int
newQty Int
note String?
reference String? // Sale ID, PO number, etc.
// Timestamps
createdAt DateTime @default(now())
// Offline Sync
localId String?
syncedAt DateTime?
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id])
@@index([shopId, createdAt])
@@index([productId, createdAt])
}
enum StockLogType {
SALE // Sold
RESTOCK // Received
ADJUSTMENT // Manual
DAMAGED // Written off
EXPIRED // Expired
TRANSFER // Between locations
RETURN // Customer return
INITIAL // First count
}Customer & Credit Models
Customer
Store credit customers.
prisma
model Customer {
id String @id @default(cuid())
shopId String
name String
phone String?
email String?
address String?
// Credit Management
creditLimit Float @default(0)
balance Float @default(0) // Positive = owes shop
isActive Boolean @default(true)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
credits CustomerCredit[]
sales Sale[] @relation("CustomerSales")
@@unique([shopId, phone])
@@index([shopId, name])
}CustomerCredit
Individual credit transactions.
prisma
model CustomerCredit {
id String @id @default(cuid())
shopId String
customerId String
type CreditType
amount Float
saleId String? // If credit from sale
note String?
userId String? // Who processed
createdAt DateTime @default(now())
// Relations
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
sale Sale? @relation(fields: [saleId], references: [id])
@@index([customerId, createdAt])
@@index([shopId, createdAt])
}
enum CreditType {
PURCHASE // Bought on credit
PAYMENT // Paid down balance
ADJUSTMENT // Manual
REFUND // Added from refund
}Returns & Refunds
Return
Refund and exchange tracking.
prisma
model Return {
id String @id @default(cuid())
shopId String
saleId String? // Original sale
customerId String?
userId String? // Staff processing
returnNumber String?
reason String // Defective, Wrong item, etc.
// Resolution
type ReturnType
refundAmount Float @default(0)
status ReturnStatus @default(PENDING)
notes String?
createdAt DateTime @default(now())
processedAt DateTime?
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
items ReturnItem[]
exchangeItems ReturnExchangeItem[]
@@index([shopId, createdAt])
@@index([saleId])
}
model ReturnItem {
id String @id @default(cuid())
returnId String
productId String
productName String
quantity Int
unitPrice Float
restockable Boolean @default(true)
restocked Boolean @default(false)
return Return @relation(fields: [returnId], references: [id], onDelete: Cascade)
@@index([returnId])
}
model ReturnExchangeItem {
id String @id @default(cuid())
returnId String
productId String
productName String
quantity Int
unitPrice Float
return Return @relation(fields: [returnId], references: [id], onDelete: Cascade)
@@index([returnId])
}
enum ReturnType {
REFUND // Money back
EXCHANGE // Swap product
STORE_CREDIT // Credit account
}
enum ReturnStatus {
PENDING
APPROVED
COMPLETED
REJECTED
}Supplier & Procurement
Supplier
Vendor management.
prisma
model Supplier {
id String @id @default(cuid())
shopId String
name String
contactName String?
phone String? // International format
email String?
website String?
// Address
address String?
city String?
country String? // "South Africa", "China"
postalCode String?
// Business Details
taxId String?
currency String? // Supplier's currency
paymentTerms String? // "Net 30", "COD"
leadTimeDays Int?
notes String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
products SupplierProduct[]
orders PurchaseOrder[]
@@unique([shopId, phone])
@@index([shopId, name])
}
model SupplierProduct {
id String @id @default(cuid())
supplierId String
productId String
costPrice Float
minOrder Int @default(1)
leadDays Int?
sku String? // Supplier's SKU
isPreferred Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
@@unique([supplierId, productId])
}
model PurchaseOrder {
id String @id @default(cuid())
shopId String
supplierId String
orderNumber String?
status POStatus @default(DRAFT)
subtotal Float
tax Float @default(0)
totalAmount Float
orderDate DateTime @default(now())
expectedDate DateTime?
receivedDate DateTime?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
supplier Supplier @relation(fields: [supplierId], references: [id])
items POItem[]
@@index([shopId, createdAt])
@@index([supplierId])
}
model POItem {
id String @id @default(cuid())
poId String
productId String
productName String
quantity Int
unitCost Float
totalCost Float
receivedQty Int @default(0)
po PurchaseOrder @relation(fields: [poId], references: [id], onDelete: Cascade)
@@index([poId])
}
enum POStatus {
DRAFT
SENT
PARTIAL // Partially received
RECEIVED
CANCELLED
}Supporting Models
Expense
Operating expense tracking.
prisma
model Expense {
id String @id @default(cuid())
shopId String
userId String?
category ExpenseCategory
amount Float
description String?
date DateTime
receiptUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
@@index([shopId, date])
}
enum ExpenseCategory {
RENT
UTILITIES
SUPPLIES
WAGES
TRANSPORT
MARKETING
REPAIRS
OTHER
}DailyReport
Pre-generated daily summaries.
prisma
model DailyReport {
id String @id @default(cuid())
shopId String
date DateTime @db.Date
// Sales Summary
totalSales Float
totalTransactions Int
averageBasket Float
// Costs & Profit
totalCost Float
grossProfit Float
totalExpenses Float
netProfit Float
// Payment Breakdown
cashSales Float @default(0)
momoSales Float @default(0)
emaliSales Float @default(0)
cardSales Float @default(0)
// JSON Data
topProducts Json // [{id, name, quantity, revenue}]
lowStock Json // [{id, name, quantity, reorderAt}]
// AI
aiInsight String?
// WhatsApp
sentViaWhatsApp Boolean @default(false)
sentAt DateTime?
createdAt DateTime @default(now())
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
@@unique([shopId, date])
@@index([shopId, date])
}AIConversation
AI chat history.
prisma
model AIConversation {
id String @id @default(cuid())
shopId String
userMessage String
aiResponse String
type AIQueryType @default(TEXT)
audioUrl String?
context Json? // Data used for response
createdAt DateTime @default(now())
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
@@index([shopId, createdAt])
}
enum AIQueryType {
TEXT
VOICE
INSIGHT
}AuditLog
Security audit trail.
prisma
model AuditLog {
id String @id @default(cuid())
shopId String
userId String
action String // LOGIN, SALE_VOID, PRODUCT_CREATE
entityType String // product, sale, user
entityId String?
details Json?
ipAddress String?
createdAt DateTime @default(now())
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([shopId, createdAt])
@@index([userId, createdAt])
@@index([action])
}SyncQueue
Offline synchronization queue.
prisma
model SyncQueue {
id String @id @default(cuid())
shopId String
entityType String // product, sale, stockLog
entityId String // Local ID
operation SyncOperation
data Json // Full entity data
status SyncStatus @default(PENDING)
attempts Int @default(0)
error String?
queuedAt DateTime @default(now())
processedAt DateTime?
shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)
@@index([shopId, status])
}
enum SyncOperation {
CREATE
UPDATE
DELETE
}
enum SyncStatus {
PENDING
PROCESSING
COMPLETED
FAILED
CONFLICT
}Admin
Platform administrators.
prisma
model Admin {
id String @id @default(cuid())
email String @unique
password String // bcrypt hash
name String
role AdminRole @default(ADMIN)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum AdminRole {
SUPER_ADMIN
ADMIN
SUPPORT
}