Files
fiberops-api/packages/db/prisma/schema.prisma
2026-05-04 14:36:51 +08:00

685 lines
22 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ─── Multi-Tenant Foundation ────────────────────────────────────────
model Tenant {
id String @id @default(uuid())
name String
slug String @unique
isActive Boolean @default(true)
settings Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
users User[]
tenantRoles TenantRole[]
areas Area[]
plans Plan[]
clients Client[]
subscriptions Subscription[]
invoices Invoice[]
payments Payment[]
tickets Ticket[]
notifications Notification[]
@@index([deletedAt])
@@map("tenants")
}
// ─── Auth & Users ───────────────────────────────────────────────────
model User {
id String @id @default(uuid())
tenantId String? // null for super_admin (platform-level user)
email String
password String
firstName String
lastName String
isActive Boolean @default(true)
mustChangePassword Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
roles UserRole[]
tenantRoles UserTenantRole[]
assignedTickets Ticket[] @relation("TicketAssignee")
createdTickets Ticket[] @relation("TicketCreator")
payments Payment[]
remittances Remittance[] @relation("RemittanceCollector")
confirmedRemittances Remittance[] @relation("RemittanceConfirmer")
@@unique([tenantId, email])
@@index([tenantId])
@@index([deletedAt])
@@map("users")
}
model UserRole {
id String @id @default(uuid())
userId String
role String // super_admin (platform-level only, legacy compat)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, role])
@@map("user_roles")
}
// ─── Tenant-Scoped Role Management ─────────────────────────────────
model TenantRole {
id String @id @default(uuid())
tenantId String
name String
slug String
description String?
isSystem Boolean @default(false) // system roles can't be deleted
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
permissions RolePermission[]
users UserTenantRole[]
@@unique([tenantId, slug])
@@index([tenantId])
@@index([deletedAt])
@@map("tenant_roles")
}
model RolePermission {
id String @id @default(uuid())
tenantRoleId String
module String // clients, subscriptions, invoices, payments, tickets, employees, expenses, assets, payroll, accounts, accounting, reports, settings, users, areas, plans, dashboard, fund_transfers
canView Boolean @default(false)
canCreate Boolean @default(false)
canUpdate Boolean @default(false)
canArchive Boolean @default(false)
canApprove Boolean @default(false)
canExport Boolean @default(false)
tenantRole TenantRole @relation(fields: [tenantRoleId], references: [id], onDelete: Cascade)
@@unique([tenantRoleId, module])
@@index([tenantRoleId])
@@map("role_permissions")
}
model UserTenantRole {
id String @id @default(uuid())
userId String
tenantRoleId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tenantRole TenantRole @relation(fields: [tenantRoleId], references: [id], onDelete: Cascade)
@@unique([userId, tenantRoleId])
@@index([userId])
@@index([tenantRoleId])
@@map("user_tenant_roles")
}
model RefreshToken {
id String @id @default(uuid())
token String @unique
userId String
expiresAt DateTime
createdAt DateTime @default(now())
@@index([userId])
@@index([expiresAt])
@@map("refresh_tokens")
}
// ─── Area & Zone Management ─────────────────────────────────────────
model Area {
id String @id @default(uuid())
tenantId String
name String
description String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
clients Client[]
@@unique([tenantId, name])
@@index([tenantId])
@@index([deletedAt])
@@map("areas")
}
// ─── Plan / Package Management ──────────────────────────────────────
model Plan {
id String @id @default(uuid())
tenantId String
name String
description String?
speedDown Int // Mbps download
speedUp Int // Mbps upload
price Decimal @db.Decimal(10, 2)
billingCycle Int @default(30) // days
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
subscriptions Subscription[]
@@unique([tenantId, name])
@@index([tenantId])
@@index([deletedAt])
@@map("plans")
}
// ─── Client Profiling ───────────────────────────────────────────────
model Client {
id String @id @default(uuid())
tenantId String
accountNumber String
firstName String
lastName String
email String?
phone String?
address String
areaId String?
latitude Float?
longitude Float?
status String @default("active") // active, inactive, suspended
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
area Area? @relation(fields: [areaId], references: [id])
subscriptions Subscription[]
invoices Invoice[]
payments Payment[]
tickets Ticket[]
@@unique([tenantId, accountNumber])
@@index([tenantId])
@@index([tenantId, status])
@@index([deletedAt])
@@map("clients")
}
// ─── Subscription Management ────────────────────────────────────────
model Subscription {
id String @id @default(uuid())
tenantId String
clientId String
planId String
type String // prepaid, postpaid
status String @default("pending") // pending, active, suspended, cancelled, expired
startDate DateTime?
endDate DateTime?
installedAt DateTime?
activatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
plan Plan @relation(fields: [planId], references: [id])
@@index([tenantId])
@@index([tenantId, status])
@@index([clientId])
@@index([deletedAt])
@@map("subscriptions")
}
// ─── Billing & Invoicing ────────────────────────────────────────────
model Invoice {
id String @id @default(uuid())
tenantId String
clientId String
number String
amount Decimal @db.Decimal(10, 2)
balance Decimal @db.Decimal(10, 2) // remaining unpaid
status String @default("draft") // draft, sent, partial, paid, overdue, void
dueDate DateTime
paidAt DateTime?
periodStart DateTime?
periodEnd DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
payments Payment[]
@@unique([tenantId, number])
@@index([tenantId])
@@index([tenantId, status])
@@index([clientId])
@@index([deletedAt])
@@map("invoices")
}
// ─── Payment & Collection ───────────────────────────────────────────
model Payment {
id String @id @default(uuid())
tenantId String
clientId String
invoiceId String?
collectedById String?
amount Decimal @db.Decimal(10, 2)
method String // gcash, maya, cash, bank_transfer
referenceNo String?
notes String?
createdAt DateTime @default(now())
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
invoice Invoice? @relation(fields: [invoiceId], references: [id])
collectedBy User? @relation(fields: [collectedById], references: [id])
@@index([tenantId])
@@index([clientId])
@@index([invoiceId])
@@index([deletedAt])
@@map("payments")
}
model Remittance {
id String @id @default(uuid())
tenantId String
collectorId String
confirmedById String?
totalAmount Decimal @db.Decimal(10, 2)
status String @default("pending") // pending, confirmed, rejected
submittedAt DateTime @default(now())
confirmedAt DateTime?
notes String?
deletedAt DateTime?
collector User @relation("RemittanceCollector", fields: [collectorId], references: [id])
confirmedBy User? @relation("RemittanceConfirmer", fields: [confirmedById], references: [id])
payments RemittancePayment[]
@@index([tenantId])
@@index([collectorId])
@@index([deletedAt])
@@map("remittances")
}
model RemittancePayment {
id String @id @default(uuid())
remittanceId String
paymentId String
remittance Remittance @relation(fields: [remittanceId], references: [id], onDelete: Cascade)
@@unique([remittanceId, paymentId])
@@map("remittance_payments")
}
// ─── Tickets (Work Orders) ──────────────────────────────────────────
model Ticket {
id String @id @default(uuid())
tenantId String
clientId String?
createdById String
assigneeId String?
type String // installation, activation, support, maintenance
status String @default("open") // open, in_progress, resolved, cancelled
title String
description String?
priority String @default("normal") // low, normal, high, urgent
latitude Float?
longitude Float?
resolvedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
client Client? @relation(fields: [clientId], references: [id])
createdBy User @relation("TicketCreator", fields: [createdById], references: [id])
assignee User? @relation("TicketAssignee", fields: [assigneeId], references: [id])
@@index([tenantId])
@@index([tenantId, type, status])
@@index([clientId])
@@index([assigneeId])
@@index([deletedAt])
@@map("tickets")
}
// ─── Notifications ──────────────────────────────────────────────────
model Notification {
id String @id @default(uuid())
tenantId String
userId String?
clientId String?
type String // sms, in_app
channel String // billing_reminder, payment_confirmation, ticket_update
title String
message String
isRead Boolean @default(false)
sentAt DateTime?
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@index([userId, isRead])
@@map("notifications")
}
// ─── Audit Log (append-only, no soft delete) ────────────────────────
model AuditLog {
id String @id @default(uuid())
tenantId String
userId String
action String
entity String
entityId String
details Json @default("{}")
ipAddress String?
createdAt DateTime @default(now())
@@index([tenantId])
@@index([tenantId, entity])
@@index([userId])
@@map("audit_logs")
}
// ─── Employee Management ────────────────────────────────────────────
model Employee {
id String @id @default(uuid())
tenantId String
userId String? @unique // Linked user account (optional, 1:1)
firstName String
lastName String
email String?
phone String?
position String
department String?
employeeNo String
status String @default("active") // active, on_leave, terminated
hireDate DateTime @default(now())
terminatedAt DateTime?
salary Decimal? @db.Decimal(10, 2)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
assets Asset[] @relation("AssetAssignee")
payslips Payslip[]
@@unique([tenantId, employeeNo])
@@index([tenantId])
@@index([tenantId, status])
@@index([deletedAt])
@@map("employees")
}
// ─── Payroll ────────────────────────────────────────────────────────
model PayrollRun {
id String @id @default(uuid())
tenantId String
period String
status String @default("draft") // draft, processing, completed
totalAmount Decimal @default(0) @db.Decimal(12, 2)
processedBy String?
processedAt DateTime?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
payslips Payslip[]
@@unique([tenantId, period])
@@index([tenantId])
@@index([deletedAt])
@@map("payroll_runs")
}
model Payslip {
id String @id @default(uuid())
payrollRunId String
employeeId String
baseSalary Decimal @db.Decimal(10, 2)
deductions Decimal @default(0) @db.Decimal(10, 2)
bonuses Decimal @default(0) @db.Decimal(10, 2)
netPay Decimal @db.Decimal(10, 2)
status String @default("pending") // pending, paid
notes String?
payrollRun PayrollRun @relation(fields: [payrollRunId], references: [id], onDelete: Cascade)
employee Employee @relation(fields: [employeeId], references: [id])
@@unique([payrollRunId, employeeId])
@@index([payrollRunId])
@@index([employeeId])
@@map("payslips")
}
// ─── Recurring Expenses ─────────────────────────────────────────────
model RecurringExpense {
id String @id @default(uuid())
tenantId String
category String
description String
amount Decimal @db.Decimal(10, 2)
frequency String @default("monthly") // monthly, quarterly, yearly
isActive Boolean @default(true)
nextRunDate DateTime
lastRunDate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([tenantId])
@@index([deletedAt])
@@map("recurring_expenses")
}
// ─── Expense Management ─────────────────────────────────────────────
model Expense {
id String @id @default(uuid())
tenantId String
createdById String
approvedById String?
category String
description String
amount Decimal @db.Decimal(10, 2)
receiptUrl String?
status String @default("pending") // pending, approved, rejected
expenseDate DateTime @default(now())
approvedAt DateTime?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([tenantId])
@@index([tenantId, status])
@@index([tenantId, category])
@@index([deletedAt])
@@map("expenses")
}
// ─── Company Accounts & Fund Transfers ──────────────────────────────
model CompanyAccount {
id String @id @default(uuid())
tenantId String
name String
type String // bank, e_wallet, cash
accountNo String?
balance Decimal @default(0) @db.Decimal(12, 2)
isActive Boolean @default(true)
isSystem Boolean @default(false)
chartOfAccountId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
outgoing FundTransfer[] @relation("TransferFrom")
incoming FundTransfer[] @relation("TransferTo")
@@unique([tenantId, name])
@@index([tenantId])
@@index([deletedAt])
@@map("company_accounts")
}
model FundTransfer {
id String @id @default(uuid())
tenantId String
fromAccountId String
toAccountId String
amount Decimal @db.Decimal(12, 2)
description String?
transferredBy String
createdAt DateTime @default(now())
fromAccount CompanyAccount @relation("TransferFrom", fields: [fromAccountId], references: [id])
toAccount CompanyAccount @relation("TransferTo", fields: [toAccountId], references: [id])
@@index([tenantId])
@@map("fund_transfers")
}
// ─── Asset Management ───────────────────────────────────────────────
model Asset {
id String @id @default(uuid())
tenantId String
name String
category String // router, olt, cable, tool, vehicle, computer, other
serialNumber String?
purchaseDate DateTime?
purchasePrice Decimal? @db.Decimal(10, 2)
assignedToId String?
status String @default("available") // available, in_use, maintenance, retired
location String?
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
assignedTo Employee? @relation("AssetAssignee", fields: [assignedToId], references: [id])
@@index([tenantId])
@@index([tenantId, status])
@@index([tenantId, category])
@@index([deletedAt])
@@map("assets")
}
// ─── Billing Settings ───────────────────────────────────────────────
model BillingSetting {
id String @id @default(uuid())
tenantId String @unique
autoGenerate Boolean @default(true)
gracePeriodDays Int @default(7)
dueDateOffsetDays Int @default(15)
lateFeePercent Decimal @default(0) @db.Decimal(5, 2)
invoicePrefix String @default("INV")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("billing_settings")
}
// ─── Chart of Accounts ──────────────────────────────────────────────
model ChartOfAccount {
id String @id @default(uuid())
tenantId String
code String
name String
type String // asset, liability, equity, revenue, expense
parentId String?
isActive Boolean @default(true)
isSystem Boolean @default(false)
balance Decimal @default(0) @db.Decimal(14, 2)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
parent ChartOfAccount? @relation("AccountTree", fields: [parentId], references: [id])
children ChartOfAccount[] @relation("AccountTree")
journalLines JournalLine[]
@@unique([tenantId, code])
@@index([tenantId])
@@index([tenantId, type])
@@index([deletedAt])
@@map("chart_of_accounts")
}
// ─── Journal Entries (append-only, no soft delete) ──────────────────
model JournalEntry {
id String @id @default(uuid())
tenantId String
entryDate DateTime @default(now())
description String
reference String?
sourceType String?
sourceId String?
createdById String?
createdAt DateTime @default(now())
lines JournalLine[]
@@index([tenantId])
@@index([tenantId, sourceType, sourceId])
@@map("journal_entries")
}
model JournalLine {
id String @id @default(uuid())
journalEntryId String
accountId String
debit Decimal @default(0) @db.Decimal(14, 2)
credit Decimal @default(0) @db.Decimal(14, 2)
journalEntry JournalEntry @relation(fields: [journalEntryId], references: [id], onDelete: Cascade)
account ChartOfAccount @relation(fields: [accountId], references: [id])
@@index([journalEntryId])
@@index([accountId])
@@map("journal_lines")
}