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[] ticketComments TicketComment[] @@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") authoredComments TicketComment[] @relation("CommentAuthor") 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 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 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") } // ─── Refresh Token ─────────────────────────────────── 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 latitude Float? longitude Float? areaId String? 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") } // ─── Remittance ───────────────────────────────────────── 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]) comments TicketComment[] @@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? ticketId String? createdAt DateTime @default(now()) tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) @@index([tenantId]) @@index([userId, isRead]) @@map("notifications") } // ─── Ticket Comments ───────────────────────────────── model TicketComment { id String @id @default(uuid()) tenantId String ticketId String userId String content String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) author User @relation("CommentAuthor", fields: [userId], references: [id]) tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade) attachments TicketAttachment[] @@index([ticketId]) @@map("ticket_comments") } // ─── Ticket Attachments ───────────────────────────────── model TicketAttachment { id String @id @default(uuid()) commentId String fileName String filePath String fileType String fileSize Int createdAt DateTime @default(now()) comment TicketComment @relation(fields: [commentId], references: [id], onDelete: Cascade) @@map("ticket_attachments") } // ─── 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]) employee Employee @relation(fields: [employeeId], references: [id]) @@unique([payrollRunId, employeeId]) @@index([payrollRunId]) @@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]) @@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]) account ChartOfAccount @relation(fields: [accountId], references: [id]) @@index([journalEntryId]) @@index([accountId]) @@map("journal_lines") }