fix: vendor Prisma into release for Docker
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
// Gold Banking System — core schema
|
||||
// Canonical accounting model: legacy "System A" gram ledger (see migration notes).
|
||||
// The external "Tahesab" service is retired — LedgerEntry is the sole source of truth.
|
||||
// All money/weight fields use Decimal, never Float, to avoid rounding drift in debt math.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
// native = local Windows/macOS build; linux-musl = Arvan/Alpine Docker runtime
|
||||
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Enums
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
enum AdminRole {
|
||||
SUPER_ADMIN
|
||||
SYSTEM_APPROVER // legacy: تایید مدیر سیستم
|
||||
SALES_EXPERT // legacy Customer.type=1, also acts as a referral "reagent"
|
||||
WORKSHOP_SUPERVISOR
|
||||
ACCOUNTANT
|
||||
}
|
||||
|
||||
enum VerificationStep {
|
||||
UNVERIFIED
|
||||
STEP_ONE_SUBMITTED
|
||||
STEP_TWO_SUBMITTED
|
||||
VERIFIED
|
||||
}
|
||||
|
||||
enum OrderStatus {
|
||||
SUSPENDED // credit limit (goldCreditLimitGrams) exceeded at placement
|
||||
PLACED
|
||||
ADMIN_APPROVED
|
||||
SOURCING
|
||||
PREPARING
|
||||
DELIVERED_TO_AGENT
|
||||
DELIVERED_TO_CUSTOMER // debt recognition point — mirrors legacy sts>4
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum PreOrderStatus {
|
||||
PLACED
|
||||
ADMIN_APPROVED
|
||||
WORKSHOP_APPROVED
|
||||
READY_TO_SHIP
|
||||
DELIVERED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum LedgerCurrency {
|
||||
GOLD_GRAM
|
||||
RIAL
|
||||
}
|
||||
|
||||
enum LedgerDirection {
|
||||
DEBIT // increases what the customer owes
|
||||
CREDIT // decreases what the customer owes
|
||||
}
|
||||
|
||||
enum LedgerSourceType {
|
||||
ORDER_STOCK
|
||||
ORDER_PRE_ORDER
|
||||
REPAYMENT_MELTED_GOLD // karat-normalized: amount * karatPerMille / 750
|
||||
REPAYMENT_TRANSFER
|
||||
REPAYMENT_CHEQUE
|
||||
REPAYMENT_CASH // price-per-gram-normalized via pricePerGramRialAtEntry
|
||||
WALLET_BONUS
|
||||
ADJUSTMENT
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Identity
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
model City {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
province String?
|
||||
customers Customer[]
|
||||
addresses CustomerAddress[]
|
||||
branches Branch[]
|
||||
preOrders PreOrder[]
|
||||
}
|
||||
|
||||
model AdminUser {
|
||||
id String @id @default(cuid())
|
||||
fullName String
|
||||
mobile String @unique
|
||||
passwordHash String
|
||||
role AdminRole
|
||||
branchId String?
|
||||
branch Branch? @relation(fields: [branchId], references: [id])
|
||||
wagePercent Decimal? @db.Decimal(6, 3) // legacy Customer.percent, commission on referred orders
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
referredCustomers Customer[] @relation("ReferralAgent")
|
||||
postedLedgerEntries LedgerEntry[]
|
||||
orderStatusEvents OrderStatusEvent[]
|
||||
preOrderStatusEvents PreOrderStatusEvent[]
|
||||
|
||||
@@index([role])
|
||||
}
|
||||
|
||||
model Customer {
|
||||
id String @id @default(cuid())
|
||||
fullName String
|
||||
mobile String @unique
|
||||
nationalCode String @unique
|
||||
passwordHash String
|
||||
cityId String?
|
||||
city City? @relation(fields: [cityId], references: [id])
|
||||
|
||||
referredByAgentId String?
|
||||
referredByAgent AdminUser? @relation("ReferralAgent", fields: [referredByAgentId], references: [id])
|
||||
|
||||
// Credit limit + cached ledger balances (source of truth is LedgerEntry; these
|
||||
// are denormalized for fast reads and reconciled by a scheduled job).
|
||||
goldCreditLimitGrams Decimal @default(0) @db.Decimal(14, 4) // legacy gram_level
|
||||
goldDebtGrams Decimal @default(0) @db.Decimal(14, 4) // legacy remain
|
||||
fiatDebtRial Decimal @default(0) @db.Decimal(20, 2) // legacy rial_remain
|
||||
|
||||
verificationStep VerificationStep @default(UNVERIFIED)
|
||||
nationalIdImageUrl String?
|
||||
businessLicenseUrl String?
|
||||
shopImageUrl String?
|
||||
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
addresses CustomerAddress[]
|
||||
orders Order[]
|
||||
preOrders PreOrder[]
|
||||
ledgerEntries LedgerEntry[]
|
||||
walletBonuses WalletBonus[]
|
||||
|
||||
@@index([referredByAgentId])
|
||||
}
|
||||
|
||||
model CustomerAddress {
|
||||
id String @id @default(cuid())
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
cityId String?
|
||||
city City? @relation(fields: [cityId], references: [id])
|
||||
addressLine String
|
||||
isDefault Boolean @default(false)
|
||||
}
|
||||
|
||||
model Branch {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
cityId String?
|
||||
city City? @relation(fields: [cityId], references: [id])
|
||||
adminUsers AdminUser[]
|
||||
orders Order[]
|
||||
}
|
||||
|
||||
model Workshop {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
isActive Boolean @default(true)
|
||||
preOrders PreOrder[]
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Catalog — item reference catalog
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
model Category {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
parentId String?
|
||||
parent Category? @relation("CategoryTree", fields: [parentId], references: [id])
|
||||
children Category[] @relation("CategoryTree")
|
||||
products Product[]
|
||||
}
|
||||
|
||||
model Brand {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
products Product[]
|
||||
}
|
||||
|
||||
/// Curated product groupings — replaces legacy "Ciny/CinyProduct" (a tagging
|
||||
/// feature, NOT a gold-price snapshot mechanism, despite the misleading legacy name).
|
||||
model Collection {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
description String?
|
||||
isActive Boolean @default(true)
|
||||
products CollectionProduct[]
|
||||
}
|
||||
|
||||
model CollectionProduct {
|
||||
collectionId String
|
||||
collection Collection @relation(fields: [collectionId], references: [id])
|
||||
productId String
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
|
||||
@@id([collectionId, productId])
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
description String?
|
||||
categoryId String?
|
||||
category Category? @relation(fields: [categoryId], references: [id])
|
||||
brandId String?
|
||||
brand Brand? @relation(fields: [brandId], references: [id])
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
variants ProductVariant[]
|
||||
images ProductImage[]
|
||||
collections CollectionProduct[]
|
||||
}
|
||||
|
||||
model ProductImage {
|
||||
id String @id @default(cuid())
|
||||
productId String
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
url String
|
||||
sortOrder Int @default(0)
|
||||
}
|
||||
|
||||
model ProductVariant {
|
||||
id String @id @default(cuid())
|
||||
productId String
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
title String
|
||||
sku String? @unique
|
||||
/// Base/raw weight before making charge ("rash weight").
|
||||
rashWeightGrams Decimal @db.Decimal(14, 4)
|
||||
/// Default making-charge percentage applied on top of rash weight.
|
||||
defaultWagePercent Decimal @db.Decimal(6, 3)
|
||||
isActive Boolean @default(true)
|
||||
|
||||
orderItems OrderItem[]
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Orders — stock orders (debt-bearing)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
model Order {
|
||||
id String @id @default(cuid())
|
||||
orderNumber String @unique
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
branchId String?
|
||||
branch Branch? @relation(fields: [branchId], references: [id])
|
||||
status OrderStatus @default(PLACED)
|
||||
|
||||
/// Total gold weight incl. wage across all items ("allGram" / order.w).
|
||||
totalGoldWeightGrams Decimal @db.Decimal(14, 4)
|
||||
/// Pure making-charge weight component (totalGoldWeightGrams minus raw weight sum).
|
||||
wageWeightGrams Decimal @db.Decimal(14, 4)
|
||||
/// Flat Rial charge independent of gold price (e.g. stones) — legacy rial_input.
|
||||
flatRialCharge Decimal @default(0) @db.Decimal(20, 2)
|
||||
|
||||
isSuspendedOverCreditLimit Boolean @default(false)
|
||||
deliveredAt DateTime? // setting this is what triggers debt recognition
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
items OrderItem[]
|
||||
statusEvents OrderStatusEvent[]
|
||||
ledgerEntries LedgerEntry[]
|
||||
convertedFromPreOrder PreOrder?
|
||||
|
||||
@@index([customerId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
model OrderItem {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
productVariantId String
|
||||
productVariant ProductVariant @relation(fields: [productVariantId], references: [id])
|
||||
quantity Int
|
||||
|
||||
// Snapshots at order time — never re-read from ProductVariant after placement.
|
||||
rashWeightGramsSnapshot Decimal @db.Decimal(14, 4)
|
||||
wagePercentSnapshot Decimal @db.Decimal(6, 3)
|
||||
discountPercentSnapshot Decimal @default(0) @db.Decimal(6, 3)
|
||||
/// computed: rash*(1 + (wagePercent-discount)/100)
|
||||
computedWeightGrams Decimal @db.Decimal(14, 4)
|
||||
flatRialCharge Decimal @default(0) @db.Decimal(20, 2)
|
||||
}
|
||||
|
||||
model OrderStatusEvent {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
note String?
|
||||
actorAdminUserId String?
|
||||
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orderId])
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Pre-orders — custom manufacturing requests (not debt-bearing until converted)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
model PreOrder {
|
||||
id String @id @default(cuid())
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
cityId String?
|
||||
city City? @relation(fields: [cityId], references: [id])
|
||||
workshopId String?
|
||||
workshop Workshop? @relation(fields: [workshopId], references: [id])
|
||||
status PreOrderStatus @default(PLACED)
|
||||
|
||||
designImageUrl String?
|
||||
colorName String?
|
||||
sizeSpec String?
|
||||
stoneColor String?
|
||||
minaColor String?
|
||||
estimatedWeightGrams Decimal? @db.Decimal(14, 4)
|
||||
estimatedPriceRial Decimal? @db.Decimal(20, 2)
|
||||
description String?
|
||||
|
||||
/// Once priced, an admin converts this into a real (debt-bearing) Order.
|
||||
convertedOrderId String? @unique
|
||||
convertedOrder Order? @relation(fields: [convertedOrderId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
statusEvents PreOrderStatusEvent[]
|
||||
|
||||
@@index([customerId])
|
||||
}
|
||||
|
||||
model PreOrderStatusEvent {
|
||||
id String @id @default(cuid())
|
||||
preOrderId String
|
||||
preOrder PreOrder @relation(fields: [preOrderId], references: [id])
|
||||
/// Field-level diff, e.g. {"status": {"old": "PLACED", "new": "ADMIN_APPROVED"}}
|
||||
changes Json
|
||||
actorAdminUserId String?
|
||||
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id])
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([preOrderId])
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Accounting — the dual-currency ledger (core of the system)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
model LedgerEntry {
|
||||
id String @id @default(cuid())
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
|
||||
currency LedgerCurrency
|
||||
direction LedgerDirection
|
||||
sourceType LedgerSourceType
|
||||
/// Always positive; direction determines the sign applied to the customer balance.
|
||||
amount Decimal @db.Decimal(20, 4)
|
||||
|
||||
/// Purity of the physical instrument, for REPAYMENT_MELTED_GOLD (e.g. 750, 916).
|
||||
karatPerMille Int?
|
||||
/// Gold price/gram at conversion time, for REPAYMENT_CASH.
|
||||
pricePerGramRialAtEntry Decimal? @db.Decimal(20, 2)
|
||||
|
||||
orderId String?
|
||||
order Order? @relation(fields: [orderId], references: [id])
|
||||
|
||||
/// Pending entries don't count toward the cached customer balance until confirmed.
|
||||
confirmed Boolean @default(false)
|
||||
confirmedAt DateTime?
|
||||
postedByAdminUserId String?
|
||||
postedByAdminUser AdminUser? @relation(fields: [postedByAdminUserId], references: [id])
|
||||
|
||||
note String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([customerId, currency])
|
||||
@@index([orderId])
|
||||
}
|
||||
|
||||
/// Replaces legacy WebServiceGold / ConfigStatic.gold_price — timestamped price feed.
|
||||
model GoldPriceQuote {
|
||||
id String @id @default(cuid())
|
||||
karatPerMille Int
|
||||
pricePerGramRial Decimal @db.Decimal(20, 2)
|
||||
source String
|
||||
quotedAt DateTime @default(now())
|
||||
|
||||
@@index([karatPerMille, quotedAt])
|
||||
}
|
||||
|
||||
/// Time-limited gold cashback bonus — replaces the legacy Rooyesh\Wallet gold credit.
|
||||
model WalletBonus {
|
||||
id String @id @default(cuid())
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
orderId String?
|
||||
goldGrams Decimal @db.Decimal(14, 4)
|
||||
grantedAt DateTime @default(now())
|
||||
expiresAt DateTime
|
||||
consumedAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
|
||||
@@index([customerId, isActive])
|
||||
}
|
||||
Reference in New Issue
Block a user