Merge dev to main #1
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "clients" ADD COLUMN "latitude" DOUBLE PRECISION;
|
||||||
|
ALTER TABLE "clients" ADD COLUMN "longitude" DOUBLE PRECISION;
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Multi-Tenant Foundation ────────────────────────────────────────
|
// ─── Multi-Tenant Foundation ────────────────────────────────
|
||||||
|
|
||||||
model Tenant {
|
model Tenant {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String
|
name String
|
||||||
slug String @unique
|
slug String @unique
|
||||||
@@ -28,16 +28,16 @@ model Tenant {
|
|||||||
invoices Invoice[]
|
invoices Invoice[]
|
||||||
payments Payment[]
|
payments Payment[]
|
||||||
tickets Ticket[]
|
tickets Ticket[]
|
||||||
comments TicketComment[]
|
|
||||||
notifications Notification[]
|
notifications Notification[]
|
||||||
|
ticketComments TicketComment[]
|
||||||
|
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("tenants")
|
@@map("tenants")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Auth & Users ───────────────────────────────────────────────────
|
// ─── Auth & Users ───────────────────────────────────────────
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String? // null for super_admin (platform-level user)
|
tenantId String? // null for super_admin (platform-level user)
|
||||||
email String
|
email String
|
||||||
@@ -45,7 +45,6 @@ model User {
|
|||||||
firstName String
|
firstName String
|
||||||
lastName String
|
lastName String
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
mustChangePassword Boolean @default(false)
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
@@ -64,22 +63,22 @@ model User {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserRole {
|
model UserRole {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
userId String
|
||||||
role String // super_admin (platform-level only, legacy compat)
|
role String
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([userId, role])
|
@@unique([userId, role])
|
||||||
@@map("user_roles")
|
@@map("user_roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Tenant-Scoped Role Management ─────────────────────────────────
|
// ─── Tenant-Scoped Role Management ─────────────────────────
|
||||||
|
|
||||||
model TenantRole {
|
model TenantRole {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
name String
|
name String
|
||||||
@@ -99,12 +98,12 @@ model TenantRole {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("tenant_roles")
|
@@map("tenant_roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
model RolePermission {
|
model RolePermission {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantRoleId String
|
tenantRoleId String
|
||||||
module String // clients, subscriptions, invoices, payments, tickets, employees, expenses, assets, payroll, accounts, accounting, reports, settings, users, areas, plans, dashboard, fund_transfers
|
module String
|
||||||
canView Boolean @default(false)
|
canView Boolean @default(false)
|
||||||
canCreate Boolean @default(false)
|
canCreate Boolean @default(false)
|
||||||
canUpdate Boolean @default(false)
|
canUpdate Boolean @default(false)
|
||||||
@@ -117,9 +116,9 @@ model RolePermission {
|
|||||||
@@unique([tenantRoleId, module])
|
@@unique([tenantRoleId, module])
|
||||||
@@index([tenantRoleId])
|
@@index([tenantRoleId])
|
||||||
@@map("role_permissions")
|
@@map("role_permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
model UserTenantRole {
|
model UserTenantRole {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
userId String
|
||||||
tenantRoleId String
|
tenantRoleId String
|
||||||
@@ -131,9 +130,11 @@ model UserTenantRole {
|
|||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([tenantRoleId])
|
@@index([tenantRoleId])
|
||||||
@@map("user_tenant_roles")
|
@@map("user_tenant_roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
model RefreshToken {
|
// ─── Refresh Token ───────────────────────────────────
|
||||||
|
|
||||||
|
model RefreshToken {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
token String @unique
|
token String @unique
|
||||||
userId String
|
userId String
|
||||||
@@ -143,11 +144,11 @@ model RefreshToken {
|
|||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([expiresAt])
|
@@index([expiresAt])
|
||||||
@@map("refresh_tokens")
|
@@map("refresh_tokens")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Area & Zone Management ─────────────────────────────────────────
|
// ─── Area & Zone Management ─────────────────────────────────
|
||||||
|
|
||||||
model Area {
|
model Area {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
name String
|
name String
|
||||||
@@ -164,11 +165,11 @@ model Area {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("areas")
|
@@map("areas")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Plan / Package Management ──────────────────────────────────────
|
// ─── Plan / Package Management ─────────────────────────
|
||||||
|
|
||||||
model Plan {
|
model Plan {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
name String
|
name String
|
||||||
@@ -189,11 +190,11 @@ model Plan {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("plans")
|
@@map("plans")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Client Profiling ───────────────────────────────────────────────
|
// ─── Client Profiling ─────────────────────────────────
|
||||||
|
|
||||||
model Client {
|
model Client {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
accountNumber String
|
accountNumber String
|
||||||
@@ -202,10 +203,10 @@ model Client {
|
|||||||
email String?
|
email String?
|
||||||
phone String?
|
phone String?
|
||||||
address String
|
address String
|
||||||
areaId String?
|
|
||||||
status String @default("active") // active, inactive, suspended
|
|
||||||
latitude Float?
|
latitude Float?
|
||||||
longitude Float?
|
longitude Float?
|
||||||
|
areaId String?
|
||||||
|
status String @default("active") // active, inactive, suspended
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
@@ -222,11 +223,11 @@ model Client {
|
|||||||
@@index([tenantId, status])
|
@@index([tenantId, status])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("clients")
|
@@map("clients")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Subscription Management ────────────────────────────────────────
|
// ─── Subscription Management ─────────────────────────
|
||||||
|
|
||||||
model Subscription {
|
model Subscription {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
clientId String
|
clientId String
|
||||||
@@ -250,11 +251,11 @@ model Subscription {
|
|||||||
@@index([clientId])
|
@@index([clientId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("subscriptions")
|
@@map("subscriptions")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Billing & Invoicing ────────────────────────────────────────────
|
// ─── Billing & Invoicing ───────────────────────────────────
|
||||||
|
|
||||||
model Invoice {
|
model Invoice {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
clientId String
|
clientId String
|
||||||
@@ -280,11 +281,11 @@ model Invoice {
|
|||||||
@@index([clientId])
|
@@index([clientId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("invoices")
|
@@map("invoices")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Payment & Collection ───────────────────────────────────────────
|
// ─── Payment & Collection ─────────────────────────────────
|
||||||
|
|
||||||
model Payment {
|
model Payment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
clientId String
|
clientId String
|
||||||
@@ -307,9 +308,11 @@ model Payment {
|
|||||||
@@index([invoiceId])
|
@@index([invoiceId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("payments")
|
@@map("payments")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Remittance {
|
// ─── Remittance ─────────────────────────────────────────
|
||||||
|
|
||||||
|
model Remittance {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
collectorId String
|
collectorId String
|
||||||
@@ -329,9 +332,9 @@ model Remittance {
|
|||||||
@@index([collectorId])
|
@@index([collectorId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("remittances")
|
@@map("remittances")
|
||||||
}
|
}
|
||||||
|
|
||||||
model RemittancePayment {
|
model RemittancePayment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
remittanceId String
|
remittanceId String
|
||||||
paymentId String
|
paymentId String
|
||||||
@@ -340,11 +343,11 @@ model RemittancePayment {
|
|||||||
|
|
||||||
@@unique([remittanceId, paymentId])
|
@@unique([remittanceId, paymentId])
|
||||||
@@map("remittance_payments")
|
@@map("remittance_payments")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Tickets (Work Orders) ──────────────────────────────────────────
|
// ─── Tickets (Work Orders) ─────────────────────────────────
|
||||||
|
|
||||||
model Ticket {
|
model Ticket {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
clientId String?
|
clientId String?
|
||||||
@@ -374,11 +377,11 @@ model Ticket {
|
|||||||
@@index([assigneeId])
|
@@index([assigneeId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("tickets")
|
@@map("tickets")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Notifications ──────────────────────────────────────────────────
|
// ─── Notifications ─────────────────────────────────────────
|
||||||
|
|
||||||
model Notification {
|
model Notification {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
userId String?
|
userId String?
|
||||||
@@ -397,11 +400,11 @@ model Notification {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([userId, isRead])
|
@@index([userId, isRead])
|
||||||
@@map("notifications")
|
@@map("notifications")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Ticket Comments ──────────────────────────────────────────────────
|
// ─── Ticket Comments ─────────────────────────────────
|
||||||
|
|
||||||
model TicketComment {
|
model TicketComment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
ticketId String
|
ticketId String
|
||||||
@@ -410,17 +413,18 @@ model TicketComment {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
||||||
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||||
author User @relation("CommentAuthor", fields: [userId], references: [id])
|
author User @relation("CommentAuthor", fields: [userId], references: [id])
|
||||||
|
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||||
attachments TicketAttachment[]
|
attachments TicketAttachment[]
|
||||||
|
|
||||||
@@index([ticketId])
|
@@index([ticketId])
|
||||||
@@index([tenantId])
|
|
||||||
@@map("ticket_comments")
|
@@map("ticket_comments")
|
||||||
}
|
}
|
||||||
|
|
||||||
model TicketAttachment {
|
// ─── Ticket Attachments ─────────────────────────────────
|
||||||
|
|
||||||
|
model TicketAttachment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
commentId String
|
commentId String
|
||||||
fileName String
|
fileName String
|
||||||
@@ -432,11 +436,11 @@ model TicketAttachment {
|
|||||||
comment TicketComment @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
comment TicketComment @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@map("ticket_attachments")
|
@@map("ticket_attachments")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Audit Log (append-only, no soft delete) ────────────────────────
|
// ─── Audit Log (append-only, no soft delete) ─────────────────
|
||||||
|
|
||||||
model AuditLog {
|
model AuditLog {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
userId String
|
userId String
|
||||||
@@ -451,11 +455,11 @@ model AuditLog {
|
|||||||
@@index([tenantId, entity])
|
@@index([tenantId, entity])
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@map("audit_logs")
|
@@map("audit_logs")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Employee Management ────────────────────────────────────────────
|
// ─── Employee Management ─────────────────────────
|
||||||
|
|
||||||
model Employee {
|
model Employee {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
userId String? @unique // Linked user account (optional, 1:1)
|
userId String? @unique // Linked user account (optional, 1:1)
|
||||||
@@ -483,11 +487,11 @@ model Employee {
|
|||||||
@@index([tenantId, status])
|
@@index([tenantId, status])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("employees")
|
@@map("employees")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Payroll ────────────────────────────────────────────────────────
|
// ─── Payroll ─────────────────────────────────────────────
|
||||||
|
|
||||||
model PayrollRun {
|
model PayrollRun {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
period String
|
period String
|
||||||
@@ -506,9 +510,9 @@ model PayrollRun {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("payroll_runs")
|
@@map("payroll_runs")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Payslip {
|
model Payslip {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
payrollRunId String
|
payrollRunId String
|
||||||
employeeId String
|
employeeId String
|
||||||
@@ -519,18 +523,17 @@ model Payslip {
|
|||||||
status String @default("pending") // pending, paid
|
status String @default("pending") // pending, paid
|
||||||
notes String?
|
notes String?
|
||||||
|
|
||||||
payrollRun PayrollRun @relation(fields: [payrollRunId], references: [id], onDelete: Cascade)
|
payrollRun PayrollRun @relation(fields: [payrollRunId], references: [id])
|
||||||
employee Employee @relation(fields: [employeeId], references: [id])
|
employee Employee @relation(fields: [employeeId], references: [id])
|
||||||
|
|
||||||
@@unique([payrollRunId, employeeId])
|
@@unique([payrollRunId, employeeId])
|
||||||
@@index([payrollRunId])
|
@@index([payrollRunId])
|
||||||
@@index([employeeId])
|
|
||||||
@@map("payslips")
|
@@map("payslips")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Recurring Expenses ─────────────────────────────────────────────
|
// ─── Recurring Expenses ─────────────────────────
|
||||||
|
|
||||||
model RecurringExpense {
|
model RecurringExpense {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
category String
|
category String
|
||||||
@@ -547,11 +550,11 @@ model RecurringExpense {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("recurring_expenses")
|
@@map("recurring_expenses")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Expense Management ─────────────────────────────────────────────
|
// ─── Expense Management ─────────────────────────
|
||||||
|
|
||||||
model Expense {
|
model Expense {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
createdById String
|
createdById String
|
||||||
@@ -573,11 +576,11 @@ model Expense {
|
|||||||
@@index([tenantId, category])
|
@@index([tenantId, category])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("expenses")
|
@@map("expenses")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Company Accounts & Fund Transfers ──────────────────────────────
|
// ─── Company Accounts & Fund Transfers ─────────────────
|
||||||
|
|
||||||
model CompanyAccount {
|
model CompanyAccount {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
name String
|
name String
|
||||||
@@ -596,11 +599,10 @@ model CompanyAccount {
|
|||||||
|
|
||||||
@@unique([tenantId, name])
|
@@unique([tenantId, name])
|
||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([deletedAt])
|
|
||||||
@@map("company_accounts")
|
@@map("company_accounts")
|
||||||
}
|
}
|
||||||
|
|
||||||
model FundTransfer {
|
model FundTransfer {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
fromAccountId String
|
fromAccountId String
|
||||||
@@ -615,11 +617,11 @@ model FundTransfer {
|
|||||||
|
|
||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@map("fund_transfers")
|
@@map("fund_transfers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Asset Management ───────────────────────────────────────────────
|
// ─── Asset Management ─────────────────────────────────
|
||||||
|
|
||||||
model Asset {
|
model Asset {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
name String
|
name String
|
||||||
@@ -642,11 +644,11 @@ model Asset {
|
|||||||
@@index([tenantId, category])
|
@@index([tenantId, category])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("assets")
|
@@map("assets")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Billing Settings ───────────────────────────────────────────────
|
// ─── Billing Settings ─────────────────────────
|
||||||
|
|
||||||
model BillingSetting {
|
model BillingSetting {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String @unique
|
tenantId String @unique
|
||||||
autoGenerate Boolean @default(true)
|
autoGenerate Boolean @default(true)
|
||||||
@@ -658,11 +660,11 @@ model BillingSetting {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@map("billing_settings")
|
@@map("billing_settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Chart of Accounts ──────────────────────────────────────────────
|
// ─── Chart of Accounts ─────────────────────────
|
||||||
|
|
||||||
model ChartOfAccount {
|
model ChartOfAccount {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
code String
|
code String
|
||||||
@@ -685,11 +687,11 @@ model ChartOfAccount {
|
|||||||
@@index([tenantId, type])
|
@@index([tenantId, type])
|
||||||
@@index([deletedAt])
|
@@index([deletedAt])
|
||||||
@@map("chart_of_accounts")
|
@@map("chart_of_accounts")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Journal Entries (append-only, no soft delete) ──────────────────
|
// ─── Journal Entries (append-only, no soft delete) ─────────
|
||||||
|
|
||||||
model JournalEntry {
|
model JournalEntry {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
tenantId String
|
tenantId String
|
||||||
entryDate DateTime @default(now())
|
entryDate DateTime @default(now())
|
||||||
@@ -705,19 +707,19 @@ model JournalEntry {
|
|||||||
@@index([tenantId])
|
@@index([tenantId])
|
||||||
@@index([tenantId, sourceType, sourceId])
|
@@index([tenantId, sourceType, sourceId])
|
||||||
@@map("journal_entries")
|
@@map("journal_entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
model JournalLine {
|
model JournalLine {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
journalEntryId String
|
journalEntryId String
|
||||||
accountId String
|
accountId String
|
||||||
debit Decimal @default(0) @db.Decimal(14, 2)
|
debit Decimal @default(0) @db.Decimal(14, 2)
|
||||||
credit Decimal @default(0) @db.Decimal(14, 2)
|
credit Decimal @default(0) @db.Decimal(14, 2)
|
||||||
|
|
||||||
journalEntry JournalEntry @relation(fields: [journalEntryId], references: [id], onDelete: Cascade)
|
journalEntry JournalEntry @relation(fields: [journalEntryId], references: [id])
|
||||||
account ChartOfAccount @relation(fields: [accountId], references: [id])
|
account ChartOfAccount @relation(fields: [accountId], references: [id])
|
||||||
|
|
||||||
@@index([journalEntryId])
|
@@index([journalEntryId])
|
||||||
@@index([accountId])
|
@@index([accountId])
|
||||||
@@map("journal_lines")
|
@@map("journal_lines")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,24 +14,28 @@ export class DashboardController {
|
|||||||
@Get('kpis')
|
@Get('kpis')
|
||||||
@Roles('manager')
|
@Roles('manager')
|
||||||
async getKpis(@CurrentUser() user: CurrentUserPayload) {
|
async getKpis(@CurrentUser() user: CurrentUserPayload) {
|
||||||
return this.dashboardService.getKpis(user.tenantId);
|
const data = await this.dashboardService.getKpis(user.tenantId);
|
||||||
|
return { data };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('revenue-chart')
|
@Get('revenue-chart')
|
||||||
@Roles('manager')
|
@Roles('manager')
|
||||||
async getRevenueChart(@CurrentUser() user: CurrentUserPayload) {
|
async getRevenueChart(@CurrentUser() user: CurrentUserPayload) {
|
||||||
return this.dashboardService.getRevenueChart(user.tenantId);
|
const data = await this.dashboardService.getRevenueChart(user.tenantId);
|
||||||
|
return { data };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('activity')
|
@Get('activity')
|
||||||
@Roles('manager')
|
@Roles('manager')
|
||||||
async getActivity(@CurrentUser() user: CurrentUserPayload) {
|
async getActivity(@CurrentUser() user: CurrentUserPayload) {
|
||||||
return this.dashboardService.getRecentActivity(user.tenantId);
|
const data = await this.dashboardService.getRecentActivity(user.tenantId);
|
||||||
|
return { data };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('financial-summary')
|
@Get('financial-summary')
|
||||||
@Roles('manager')
|
@Roles('manager')
|
||||||
async getFinancialSummary(@CurrentUser() user: CurrentUserPayload) {
|
async getFinancialSummary(@CurrentUser() user: CurrentUserPayload) {
|
||||||
return this.dashboardService.getFinancialSummary(user.tenantId);
|
const data = await this.dashboardService.getFinancialSummary(user.tenantId);
|
||||||
|
return { data };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
BadRequestException,
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { NotificationService } from '../notification/notification.service';
|
|
||||||
import { CreateTicketDto } from './dto/create-ticket.dto';
|
import { CreateTicketDto } from './dto/create-ticket.dto';
|
||||||
import { UpdateTicketDto } from './dto/update-ticket.dto';
|
import { UpdateTicketDto } from './dto/update-ticket.dto';
|
||||||
|
import { CreateCommentDto } from './dto/create-comment.dto';
|
||||||
|
|
||||||
export interface TicketResolvedEvent {
|
export interface TicketResolvedEvent {
|
||||||
ticketId: string;
|
ticketId: string;
|
||||||
@@ -21,10 +21,7 @@ export class TicketService {
|
|||||||
onTicketResolved: ((event: TicketResolvedEvent) => Promise<void>) | null =
|
onTicketResolved: ((event: TicketResolvedEvent) => Promise<void>) | null =
|
||||||
null;
|
null;
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly notificationService: NotificationService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async findAll(tenantId: string, filters?: { clientId?: string; status?: string; type?: string }) {
|
async findAll(tenantId: string, filters?: { clientId?: string; status?: string; type?: string }) {
|
||||||
const db = this.prisma.forTenant(tenantId);
|
const db = this.prisma.forTenant(tenantId);
|
||||||
@@ -115,18 +112,6 @@ export class TicketService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify newly assigned user
|
|
||||||
if (dto.assigneeId && dto.assigneeId !== existing.assigneeId) {
|
|
||||||
this.notificationService.create(tenantId, {
|
|
||||||
userId: dto.assigneeId,
|
|
||||||
type: 'in_app',
|
|
||||||
channel: 'ticket_assigned',
|
|
||||||
title: 'Ticket assigned to you',
|
|
||||||
message: `You were assigned to "${existing.title}"`,
|
|
||||||
ticketId: id,
|
|
||||||
}).catch(() => {}); // non-blocking
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.ticket.update({
|
return this.prisma.ticket.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -180,4 +165,50 @@ export class TicketService {
|
|||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
async getComments(tenantId: string, ticketId: string) {
|
||||||
|
const db = this.prisma.forTenant(tenantId);
|
||||||
|
const comments = await db.ticketComment.findMany({
|
||||||
|
where: { ticketId },
|
||||||
|
include: {
|
||||||
|
author: {
|
||||||
|
select: { id: true, firstName: true, lastName: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return comments.map((c: any) => ({
|
||||||
|
...c,
|
||||||
|
createdByName: c.author
|
||||||
|
? `${c.author.firstName} ${c.author.lastName}`
|
||||||
|
: 'Unknown',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async addComment(
|
||||||
|
tenantId: string,
|
||||||
|
ticketId: string,
|
||||||
|
userId: string,
|
||||||
|
dto: CreateCommentDto,
|
||||||
|
) {
|
||||||
|
const db = this.prisma.forTenant(tenantId);
|
||||||
|
const ticket = await db.ticket.findFirst({
|
||||||
|
where: { id: ticketId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ticket) {
|
||||||
|
throw new NotFoundException('Ticket not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.ticketComment.create({
|
||||||
|
data: {
|
||||||
|
tenantId,
|
||||||
|
ticketId,
|
||||||
|
userId,
|
||||||
|
content: dto.content,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user