feat(03-02): Collection/Remittance schema, 1030 COA account, migration, tenant scoping
- Added 1030 Cash in Transit to ISP_CHART_OF_ACCOUNTS (between 1020 and 1100) - Added CollectionStatus (COMPLETED/VOIDED) and RemittanceStatus (PENDING/VERIFIED) enums - Added Collection model with FIFO allocations, zone-scoped collector, JE link, void fields - Added CollectionAllocation model linking collections to invoices - Added Remittance model with two-party verification, variance field, JE link - Added reverse relations on User (collections, remittances) and Subscriber (collections) - Applied migration: add-collections-remittances - Extended TENANT_SCOPED_MODELS with collection, collectionAllocation, remittance - Added full 12-operation extension blocks for all three new models
This commit is contained in:
@@ -119,6 +119,16 @@ enum TicketSource {
|
||||
SUBSCRIBER
|
||||
}
|
||||
|
||||
enum CollectionStatus {
|
||||
COMPLETED
|
||||
VOIDED
|
||||
}
|
||||
|
||||
enum RemittanceStatus {
|
||||
PENDING
|
||||
VERIFIED
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MODELS
|
||||
// =============================================================================
|
||||
@@ -278,6 +288,7 @@ model Subscriber {
|
||||
invoices Invoice[]
|
||||
payments Payment[]
|
||||
tickets Ticket[]
|
||||
collections Collection[]
|
||||
|
||||
/// Account numbers must be unique within a tenant
|
||||
@@unique([tenantId, accountNumber])
|
||||
@@ -362,6 +373,14 @@ model User {
|
||||
zoneAssignments ZoneAssignment[]
|
||||
/// Tickets this user created (staff or subscriber portal)
|
||||
createdTickets Ticket[] @relation("TicketCreatedBy")
|
||||
/// Collections recorded by this collector
|
||||
collections Collection[] @relation("CollectionByCollector")
|
||||
/// Collections voided by this user
|
||||
voidedCollections Collection[] @relation("CollectionVoidedBy")
|
||||
/// Remittances this collector submitted
|
||||
remittances Remittance[] @relation("RemittanceByCollector")
|
||||
/// Remittances verified by this user (office staff)
|
||||
verifiedRemittances Remittance[] @relation("RemittanceVerifiedBy")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -473,8 +492,9 @@ model Invoice {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
lines InvoiceLine[]
|
||||
paymentAllocations PaymentAllocation[]
|
||||
lines InvoiceLine[]
|
||||
paymentAllocations PaymentAllocation[]
|
||||
collectionAllocations CollectionAllocation[]
|
||||
|
||||
/// Invoice numbers must be unique within a tenant
|
||||
@@unique([tenantId, invoiceNumber])
|
||||
@@ -608,6 +628,100 @@ model Ticket {
|
||||
@@index([subscriberId])
|
||||
}
|
||||
|
||||
/// A Collection records cash received from a subscriber by a field collector.
|
||||
/// The collector logs the lump sum; FIFO allocation maps it to outstanding invoices.
|
||||
/// Every collection creates a JE: DR 1030 Cash in Transit, CR 1100 Accounts Receivable.
|
||||
/// Voids use reversing entries — records are never deleted.
|
||||
model Collection {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
/// The collector who received the cash
|
||||
collectorId String
|
||||
collector User @relation("CollectionByCollector", fields: [collectorId], references: [id])
|
||||
/// The subscriber who paid
|
||||
subscriberId String
|
||||
subscriber Subscriber @relation(fields: [subscriberId], references: [id])
|
||||
/// Total cash received from subscriber
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
/// When the cash was collected (economic date)
|
||||
collectionDate DateTime
|
||||
status CollectionStatus @default(COMPLETED)
|
||||
notes String?
|
||||
/// Journal entry created when collection was recorded (DR 1030, CR 1100)
|
||||
journalEntryId String?
|
||||
/// Timestamp when this collection was voided
|
||||
voidedAt DateTime?
|
||||
/// User who voided this collection
|
||||
voidedById String?
|
||||
voidedBy User? @relation("CollectionVoidedBy", fields: [voidedById], references: [id])
|
||||
/// Reversing journal entry created when collection was voided
|
||||
voidJournalEntryId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
allocations CollectionAllocation[]
|
||||
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
@@index([tenantId, collectorId])
|
||||
@@index([tenantId, collectionDate])
|
||||
@@index([tenantId, subscriberId])
|
||||
}
|
||||
|
||||
/// A CollectionAllocation links a Collection to an Invoice for the allocated amount.
|
||||
/// Supports partial allocations and FIFO ordering (same pattern as PaymentAllocation).
|
||||
model CollectionAllocation {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
collectionId String
|
||||
collection Collection @relation(fields: [collectionId], references: [id])
|
||||
invoiceId String
|
||||
invoice Invoice @relation(fields: [invoiceId], references: [id])
|
||||
/// Amount of the collection allocated to this invoice
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([collectionId])
|
||||
@@index([invoiceId])
|
||||
@@index([tenantId])
|
||||
}
|
||||
|
||||
/// A Remittance records a collector turning in collected cash to the office.
|
||||
/// Two-party verification: collector declares total, office staff counts and verifies.
|
||||
/// Variance between collector total and staff count is recorded but non-blocking.
|
||||
/// Verification creates JE: DR 1010 Cash on Hand, CR 1030 Cash in Transit.
|
||||
model Remittance {
|
||||
id String @id @default(uuid())
|
||||
tenantId String
|
||||
/// The collector remitting cash
|
||||
collectorId String
|
||||
collector User @relation("RemittanceByCollector", fields: [collectorId], references: [id])
|
||||
/// When the remittance occurred
|
||||
remittanceDate DateTime
|
||||
/// Total collected by the collector (self-reported)
|
||||
collectedTotal Decimal @db.Decimal(10, 2)
|
||||
/// Total counted by office staff (set during verification)
|
||||
verifiedTotal Decimal? @db.Decimal(10, 2)
|
||||
/// Difference: verifiedTotal - collectedTotal (positive = overage, negative = shortage)
|
||||
variance Decimal? @db.Decimal(10, 2)
|
||||
status RemittanceStatus @default(PENDING)
|
||||
/// Office staff who verified the remittance
|
||||
verifiedById String?
|
||||
verifiedBy User? @relation("RemittanceVerifiedBy", fields: [verifiedById], references: [id])
|
||||
verifiedAt DateTime?
|
||||
/// Journal entry created when remittance was verified (DR 1010, CR 1030)
|
||||
journalEntryId String?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
/// RLS-ready index — always present on tenant-scoped models
|
||||
@@index([tenantId])
|
||||
@@index([tenantId, collectorId])
|
||||
@@index([tenantId, remittanceDate])
|
||||
@@index([tenantId, status])
|
||||
}
|
||||
|
||||
/// A single line item on an Invoice (e.g., "50 Mbps Monthly Service — $49.99").
|
||||
model InvoiceLine {
|
||||
id String @id @default(uuid())
|
||||
|
||||
Reference in New Issue
Block a user