Files
api/prisma/schema.prisma
T

345 lines
13 KiB
Plaintext
Raw Normal View History

2026-07-23 10:25:32 +03:30
generator client {
provider = "prisma-client-js"
}
datasource db {
// Production + shared host: PostgreSQL. Local: Docker Postgres or same server DB.
provider = "postgresql"
2026-07-23 10:25:32 +03:30
url = env("DATABASE_URL")
}
enum ProductType {
hamkar
khanegi
}
/// Khanegi front-end appearance family (ignored for hamkar).
enum KhanegiUiVariant {
modular
newTheme
}
2026-07-23 10:25:32 +03:30
enum UserRole {
super_admin
admin
2026-07-23 10:25:32 +03:30
}
2026-07-23 19:41:21 +03:30
enum SmsLedgerType {
charge
debit
adjust
}
2026-07-23 10:25:32 +03:30
model User {
id String @id @default(cuid())
mobile String @unique
firstName String @map("first_name")
lastName String @map("last_name")
/// Optional legacy/contact email — login uses mobile
email String? @unique
2026-07-23 10:25:32 +03:30
passwordHash String @map("password_hash")
role UserRole @default(super_admin)
/// Permission codes for role=admin (ignored when role=super_admin)
permissions Json @default("[]")
2026-07-23 10:25:32 +03:30
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
auditLogs AuditLog[]
@@map("users")
}
model Tenant {
id String @id @default(cuid())
product ProductType
slug String @unique
nameFa String @map("name_fa")
nameEn String? @map("name_en")
domain String @unique
domains Json?
avatarUrl String? @map("avatar_url")
primaryColor String? @map("primary_color")
secondaryColor String? @map("secondary_color")
backgroundColor String? @map("background_color")
nodeServer String @map("node_server")
nodeWs String? @map("node_ws")
appUrl String? @map("app_url")
adminUrl String? @map("admin_url")
remoteApiToken String? @map("remote_api_token")
2026-07-23 19:41:21 +03:30
/// Legacy cache from older sync attempts — SuperAdmin SMS account is now source of truth
smsSettingsCache Json? @map("sms_settings_cache")
smsSyncedAt DateTime? @map("sms_synced_at")
2026-07-23 10:25:32 +03:30
/// Full Khanegi UI JSON (theme, navigation, pages). Hamkar: unused for now.
appUiConfig Json? @map("app_ui_config")
/// Khanegi only: which appearance editor / get_user payload shape to use.
uiVariant KhanegiUiVariant @default(modular) @map("ui_variant")
/// Client cache-bust / force-reload signal for this sold version (GET /get_version).
appVersion String @default("1") @map("app_version")
2026-07-23 10:25:32 +03:30
isActive Boolean @default(true) @map("is_active")
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
auditLogs AuditLog[]
2026-07-23 19:41:21 +03:30
smsAccount SmsAccount?
/// 0..N hosting/billing servers per sold version (not the same as nodeServer API URL)
serverAccounts ServerAccount[]
pluginSubscriptions PluginSubscription[]
supportSubscriptions SupportSubscription[]
2026-07-23 10:25:32 +03:30
@@index([product])
@@index([isActive])
@@map("tenants")
}
2026-07-23 19:41:21 +03:30
/// SuperAdmin-owned SMS wallet + gateway config per sold version.
/// Does NOT replace Hamkar/Khanegi internal SMS panels yet.
model SmsAccount {
id String @id @default(cuid())
tenantId String @unique @map("tenant_id")
balanceRials Int @default(0) @map("balance_rials")
pricePerPartRials Int @default(2500) @map("price_per_part_rials")
isBlocked Boolean @default(false) @map("is_blocked")
/// If set: block non-alert SMS when balanceRials < this. Null = only manual isBlocked.
autoBlockBelowRials Int? @map("auto_block_below_rials")
2026-07-23 19:41:21 +03:30
/// below this => red/critical
alertCriticalRials Int @default(10000) @map("alert_critical_rials")
/// at/below this (and above critical) => yellow; above => green
alertWarningRials Int @default(100000) @map("alert_warning_rials")
gatewayProvider String? @map("gateway_provider")
melipayamakUser String? @map("melipayamak_user")
melipayamakPassword String? @map("melipayamak_password")
melipayamakFrom String? @map("melipayamak_from")
panelUsername String? @map("panel_username")
panelPassword String? @map("panel_password")
panelFrom String? @map("panel_from")
/// Deposit instructions shown to tenant managers (parity with Hamkar sms-panel)
chargeCard String? @map("charge_card")
chargeIban String? @map("charge_iban")
chargeHolder String? @map("charge_holder")
chargeInstruction String? @map("charge_instruction")
2026-07-23 19:41:21 +03:30
extraSettings Json? @map("extra_settings")
todayMessages Int @default(0) @map("today_messages")
todayParts Int @default(0) @map("today_parts")
todayCostRials Int @default(0) @map("today_cost_rials")
monthMessages Int @default(0) @map("month_messages")
monthParts Int @default(0) @map("month_parts")
monthCostRials Int @default(0) @map("month_cost_rials")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
ledgers SmsLedger[]
sendLogs SmsSendLog[]
2026-07-23 19:41:21 +03:30
@@map("sms_accounts")
}
model SmsLedger {
id String @id @default(cuid())
smsAccountId String @map("sms_account_id")
type SmsLedgerType
amountRials Int @map("amount_rials")
balanceAfterRials Int? @map("balance_after_rials")
parts Int?
note String?
createdById String? @map("created_by_id")
createdAt DateTime @default(now()) @map("created_at")
2026-07-23 19:41:21 +03:30
smsAccount SmsAccount @relation(fields: [smsAccountId], references: [id], onDelete: Cascade)
@@index([smsAccountId])
@@index([createdAt])
@@index([type])
2026-07-23 19:41:21 +03:30
@@map("sms_ledgers")
}
/// Per-tenant SMS send history (filled when products hook consume; empty until then)
model SmsSendLog {
id String @id @default(cuid())
smsAccountId String @map("sms_account_id")
receiver String
body String
parts Int @default(1)
pricePerPartRials Int @map("price_per_part_rials")
totalAmountRials Int @map("total_amount_rials")
provider String?
source String?
isAlert Boolean @default(false) @map("is_alert")
createdAt DateTime @default(now()) @map("created_at")
smsAccount SmsAccount @relation(fields: [smsAccountId], references: [id], onDelete: Cascade)
@@index([smsAccountId])
@@index([createdAt])
@@index([provider])
@@map("sms_send_logs")
}
2026-07-23 10:25:32 +03:30
model AuditLog {
id String @id @default(cuid())
action String
entityType String @map("entity_type")
entityId String? @map("entity_id")
tenantId String? @map("tenant_id")
userId String? @map("user_id")
before Json?
after Json?
meta Json?
createdAt DateTime @default(now()) @map("created_at")
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: SetNull)
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([tenantId])
@@index([userId])
@@index([createdAt])
@@map("audit_logs")
}
/// Server hosting status for a sold version.
/// isDedicated=true → customer-owned; SuperAdmin only tracks that flag.
/// isDedicated=false → we manage charge/renewal/expiry alerts.
model ServerAccount {
id String @id @default(cuid())
tenantId String @map("tenant_id")
isDedicated Boolean @default(false) @map("is_dedicated")
serverName String? @map("server_name")
specs String?
/// Price of the current/last plan period (rials)
priceRials Int? @map("price_rials")
/// Start of current paid period
chargedAt DateTime? @map("charged_at")
/// Plan length in months: 1 | 3 | 6 | 12
planMonths Int? @map("plan_months")
expiresAt DateTime? @map("expires_at")
description String?
/// Days before expiry to start SMS alerts (default 3)
alertDaysBefore Int @default(3) @map("alert_days_before")
/// Last calendar day an expiry alert was sent (avoid multi-send same day)
lastAlertDate DateTime? @map("last_alert_date")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
charges ServerCharge[]
@@index([tenantId])
@@index([expiresAt])
@@index([isDedicated])
@@map("server_accounts")
}
model ServerCharge {
id String @id @default(cuid())
serverAccountId String @map("server_account_id")
chargedAt DateTime @map("charged_at")
planMonths Int @map("plan_months")
priceRials Int @map("price_rials")
expiresAt DateTime @map("expires_at")
note String?
createdById String? @map("created_by_id")
createdAt DateTime @default(now()) @map("created_at")
serverAccount ServerAccount @relation(fields: [serverAccountId], references: [id], onDelete: Cascade)
@@index([serverAccountId])
@@index([createdAt])
@@map("server_charges")
}
/// Paid plugin/addon subscription for a sold version (0..N per tenant).
model PluginSubscription {
id String @id @default(cuid())
tenantId String @map("tenant_id")
/// Plugin display name (required; each tenant can have many)
name String
specs String?
priceRials Int? @map("price_rials")
chargedAt DateTime? @map("charged_at")
/// Plan length in months: 1 | 3 | 6 | 12
planMonths Int? @map("plan_months")
expiresAt DateTime? @map("expires_at")
description String?
alertDaysBefore Int @default(3) @map("alert_days_before")
lastAlertDate DateTime? @map("last_alert_date")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
charges PluginCharge[]
@@index([tenantId])
@@index([expiresAt])
@@index([isActive])
@@map("plugin_subscriptions")
}
model PluginCharge {
id String @id @default(cuid())
pluginId String @map("plugin_id")
chargedAt DateTime @map("charged_at")
planMonths Int @map("plan_months")
priceRials Int @map("price_rials")
expiresAt DateTime @map("expires_at")
note String?
createdById String? @map("created_by_id")
createdAt DateTime @default(now()) @map("created_at")
plugin PluginSubscription @relation(fields: [pluginId], references: [id], onDelete: Cascade)
@@index([pluginId])
@@index([createdAt])
@@map("plugin_charges")
}
/// Support/maintenance subscription for a sold version (0..N per tenant).
/// Can be free (isFree) for 1|3|6|12 months gift periods.
model SupportSubscription {
id String @id @default(cuid())
tenantId String @map("tenant_id")
name String
specs String?
priceRials Int? @map("price_rials")
/// When true, current period is complimentary (price should be 0)
isFree Boolean @default(false) @map("is_free")
chargedAt DateTime? @map("charged_at")
/// Plan length in months: 1 | 3 | 6 | 12
planMonths Int? @map("plan_months")
expiresAt DateTime? @map("expires_at")
description String?
alertDaysBefore Int @default(3) @map("alert_days_before")
lastAlertDate DateTime? @map("last_alert_date")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
charges SupportCharge[]
@@index([tenantId])
@@index([expiresAt])
@@index([isActive])
@@index([isFree])
@@map("support_subscriptions")
}
model SupportCharge {
id String @id @default(cuid())
supportId String @map("support_id")
chargedAt DateTime @map("charged_at")
planMonths Int @map("plan_months")
priceRials Int @map("price_rials")
isFree Boolean @default(false) @map("is_free")
expiresAt DateTime @map("expires_at")
note String?
createdById String? @map("created_by_id")
createdAt DateTime @default(now()) @map("created_at")
support SupportSubscription @relation(fields: [supportId], references: [id], onDelete: Cascade)
@@index([supportId])
@@index([createdAt])
@@map("support_charges")
}