feat(04-03): Vendor, ExpenseCategory, Expense schema + category seeding + COA additions
- Add ExpenseStatus and ExpensePaymentMethod enums - Add Vendor model (@@unique([tenantId, name])) - Add ExpenseCategory model with accountCode linking to COA - Add Expense model with approval workflow and JE reference - Add User relations: createdExpenses, approvedExpenses - Add COA accounts 5080 Fuel/Transportation, 5085 Rent Expense (31 total) - Seed 9 default expense categories in createTenant transaction - Add Vendor subject to CASL types, Expense/Vendor permissions for OFFICE_STAFF Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,19 @@ enum LocationType {
|
||||
SUBSCRIBER
|
||||
}
|
||||
|
||||
enum ExpenseStatus {
|
||||
DRAFT
|
||||
APPROVED
|
||||
POSTED
|
||||
VOIDED
|
||||
}
|
||||
|
||||
enum ExpensePaymentMethod {
|
||||
CASH
|
||||
BANK_TRANSFER
|
||||
CHECK
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MODELS
|
||||
// =============================================================================
|
||||
@@ -429,6 +442,10 @@ model User {
|
||||
technicianProfiles TechnicianProfile[]
|
||||
/// Stock movements performed/recorded by this user
|
||||
recordedMovements StockMovement[] @relation("MovementPerformedBy")
|
||||
/// Expenses created by this user
|
||||
createdExpenses Expense[] @relation("ExpenseCreatedBy")
|
||||
/// Expenses approved by this user
|
||||
approvedExpenses Expense[] @relation("ExpenseApprovedBy")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -953,3 +970,96 @@ model StockMovement {
|
||||
@@index([inventoryItemId])
|
||||
@@index([tenantId, movementType])
|
||||
}
|
||||
|
||||
/// A Vendor represents an external supplier or service provider.
|
||||
/// Vendors are optional on expenses but useful for tracking who was paid.
|
||||
model Vendor {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
name String
|
||||
contactPerson String?
|
||||
phone String?
|
||||
email String?
|
||||
address String?
|
||||
/// Freetext description of what this vendor provides
|
||||
servicesProvided String?
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
expenses Expense[]
|
||||
|
||||
/// Vendor names must be unique within a tenant
|
||||
@@unique([tenantId, name])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// An ExpenseCategory classifies expenses and links them to COA accounts.
|
||||
/// Default categories are pre-seeded at tenant creation (isSystemCategory=true).
|
||||
/// Admins can add custom categories; system categories cannot be deleted.
|
||||
model ExpenseCategory {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
name String
|
||||
description String?
|
||||
/// Maps to a COA expense account code (e.g., "5040" for bandwidth)
|
||||
accountCode String
|
||||
/// True for pre-seeded categories — cannot be deleted by admin
|
||||
isSystemCategory Boolean @default(false)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
expenses Expense[]
|
||||
|
||||
/// Category names must be unique within a tenant
|
||||
@@unique([tenantId, name])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// An Expense tracks money spent by the ISP business.
|
||||
/// Follows an optional approval workflow: DRAFT -> APPROVED -> POSTED (when enabled).
|
||||
/// When approval is disabled, expenses go directly from DRAFT to POSTED.
|
||||
/// Every POSTED expense creates a balanced journal entry (DR expense account, CR cash/bank).
|
||||
model Expense {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
/// Auto-generated sequential identifier per tenant (e.g., "EXP-0001")
|
||||
expenseNumber String
|
||||
categoryId String
|
||||
category ExpenseCategory @relation(fields: [categoryId], references: [id])
|
||||
vendorId String?
|
||||
vendor Vendor? @relation(fields: [vendorId], references: [id])
|
||||
/// Total expense amount
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
/// When the expense occurred (economic date)
|
||||
expenseDate DateTime
|
||||
description String
|
||||
paymentMethod ExpensePaymentMethod
|
||||
status ExpenseStatus @default(DRAFT)
|
||||
/// File path for receipt image/PDF
|
||||
attachmentPath String?
|
||||
/// Journal entry created when expense is posted (DR expense account, CR cash/bank)
|
||||
journalEntryId String?
|
||||
/// The user who recorded this expense
|
||||
createdById String
|
||||
createdBy User @relation("ExpenseCreatedBy", fields: [createdById], references: [id])
|
||||
/// The user who approved this expense (if approval workflow enabled)
|
||||
approvedById String?
|
||||
approvedBy User? @relation("ExpenseApprovedBy", fields: [approvedById], references: [id])
|
||||
approvedAt DateTime?
|
||||
postedAt DateTime?
|
||||
voidedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// Expense numbers must be unique within a tenant
|
||||
@@unique([tenantId, expenseNumber])
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
@@index([tenantId, categoryId])
|
||||
@@index([tenantId, vendorId])
|
||||
@@index([tenantId, status])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user