@valentine-efagene/qshelter-common 2.0.21 → 2.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/generated/client/browser.d.ts +45 -30
  2. package/dist/generated/client/client.d.ts +45 -30
  3. package/dist/generated/client/commonInputTypes.d.ts +40 -0
  4. package/dist/generated/client/internal/class.d.ts +93 -60
  5. package/dist/generated/client/internal/class.js +2 -2
  6. package/dist/generated/client/internal/prismaNamespace.d.ts +1058 -720
  7. package/dist/generated/client/internal/prismaNamespace.js +321 -190
  8. package/dist/generated/client/internal/prismaNamespaceBrowser.d.ts +352 -215
  9. package/dist/generated/client/internal/prismaNamespaceBrowser.js +321 -190
  10. package/dist/generated/client/models/Amenity.d.ts +168 -1
  11. package/dist/generated/client/models/Contract.d.ts +2390 -309
  12. package/dist/generated/client/models/ContractDocument.d.ts +299 -12
  13. package/dist/generated/client/models/ContractEvent.d.ts +1052 -0
  14. package/dist/generated/client/models/ContractEvent.js +1 -0
  15. package/dist/generated/client/models/ContractInstallment.d.ts +1656 -0
  16. package/dist/generated/client/models/ContractInstallment.js +1 -0
  17. package/dist/generated/client/models/ContractPayment.d.ts +2026 -0
  18. package/dist/generated/client/models/ContractPayment.js +1 -0
  19. package/dist/generated/client/models/ContractPhase.d.ts +2467 -0
  20. package/dist/generated/client/models/ContractPhase.js +1 -0
  21. package/dist/generated/client/models/ContractPhaseStep.d.ts +1678 -0
  22. package/dist/generated/client/models/ContractPhaseStep.js +1 -0
  23. package/dist/generated/client/models/ContractPhaseStepApproval.d.ts +1249 -0
  24. package/dist/generated/client/models/ContractPhaseStepApproval.js +1 -0
  25. package/dist/generated/client/models/ContractTransition.d.ts +1118 -0
  26. package/dist/generated/client/models/ContractTransition.js +1 -0
  27. package/dist/generated/client/models/DomainEvent.d.ts +1240 -0
  28. package/dist/generated/client/models/DomainEvent.js +1 -0
  29. package/dist/generated/client/models/PaymentPlan.d.ts +467 -971
  30. package/dist/generated/client/models/Property.d.ts +372 -626
  31. package/dist/generated/client/models/PropertyPaymentMethod.d.ts +1714 -0
  32. package/dist/generated/client/models/PropertyPaymentMethod.js +1 -0
  33. package/dist/generated/client/models/PropertyPaymentMethodLink.d.ts +1158 -0
  34. package/dist/generated/client/models/PropertyPaymentMethodLink.js +1 -0
  35. package/dist/generated/client/models/PropertyPaymentMethodPhase.d.ts +1656 -0
  36. package/dist/generated/client/models/PropertyPaymentMethodPhase.js +1 -0
  37. package/dist/generated/client/models/PropertyUnit.d.ts +1598 -0
  38. package/dist/generated/client/models/PropertyUnit.js +1 -0
  39. package/dist/generated/client/models/PropertyVariant.d.ts +2079 -0
  40. package/dist/generated/client/models/PropertyVariant.js +1 -0
  41. package/dist/generated/client/models/PropertyVariantAmenity.d.ts +1080 -0
  42. package/dist/generated/client/models/PropertyVariantAmenity.js +1 -0
  43. package/dist/generated/client/models/PropertyVariantMedia.d.ts +1189 -0
  44. package/dist/generated/client/models/PropertyVariantMedia.js +1 -0
  45. package/dist/generated/client/models/Tenant.d.ts +482 -0
  46. package/dist/generated/client/models/User.d.ts +684 -427
  47. package/dist/generated/client/models/index.d.ts +15 -12
  48. package/dist/generated/client/models/index.js +15 -12
  49. package/dist/generated/client/models.d.ts +15 -12
  50. package/dist/src/index.d.ts +2 -0
  51. package/dist/src/index.js +2 -0
  52. package/dist/src/middleware/error-handler.d.ts +6 -0
  53. package/dist/src/middleware/error-handler.js +26 -0
  54. package/dist/src/middleware/index.d.ts +3 -0
  55. package/dist/src/middleware/index.js +3 -0
  56. package/dist/src/middleware/request-logger.d.ts +6 -0
  57. package/dist/src/middleware/request-logger.js +17 -0
  58. package/dist/src/middleware/tenant.d.ts +61 -0
  59. package/dist/src/middleware/tenant.js +85 -0
  60. package/dist/src/prisma/tenant.d.ts +51 -0
  61. package/dist/src/prisma/tenant.js +211 -0
  62. package/package.json +16 -2
  63. package/prisma/migrations/20251230104059_add_property_variants/migration.sql +622 -0
  64. package/prisma/migrations/20251230113413_add_multitenancy/migration.sql +54 -0
  65. package/prisma/schema.prisma +561 -267
@@ -49,12 +49,15 @@ model User {
49
49
  socials Social[]
50
50
 
51
51
  // Relations to other domains
52
- properties Property[]
53
- mortgages Mortgage[] @relation("MortgageBorrower")
54
- paymentPlans PaymentPlan[]
55
- contracts Contract[] @relation("ContractBuyer")
56
- soldContracts Contract[] @relation("ContractSeller")
57
- payments Payment[]
52
+ properties Property[]
53
+ contracts Contract[] @relation("ContractBuyer")
54
+ soldContracts Contract[] @relation("ContractSeller")
55
+ contractPayments ContractPayment[] @relation("ContractPayer")
56
+
57
+ // Phase step assignments and approvals
58
+ assignedSteps ContractPhaseStep[] @relation("PhaseStepAssignee")
59
+ stepApprovals ContractPhaseStepApproval[] @relation("PhaseStepApprover")
60
+ uploadedDocs ContractDocument[] @relation("DocumentUploader")
58
61
 
59
62
  @@index([email])
60
63
  @@index([tenantId])
@@ -115,10 +118,16 @@ model Tenant {
115
118
  name String
116
119
  subdomain String @unique
117
120
  isActive Boolean @default(true)
118
- users User[]
119
121
  createdAt DateTime @default(now())
120
122
  updatedAt DateTime @updatedAt
121
123
 
124
+ // Back-relations for multitenancy
125
+ users User[]
126
+ properties Property[]
127
+ paymentPlans PaymentPlan[]
128
+ paymentMethods PropertyPaymentMethod[]
129
+ contracts Contract[]
130
+
122
131
  @@index([subdomain])
123
132
  @@map("tenants")
124
133
  }
@@ -262,28 +271,29 @@ model Settings {
262
271
  // =============================================================================
263
272
  // PROPERTY DOMAIN
264
273
  // =============================================================================
274
+ // Property = listing/project (e.g., "Sunrise Estate")
275
+ // PropertyVariant = configuration with specs & price (e.g., "3-Bed Corner - Finished")
276
+ // PropertyUnit = individual sellable unit (e.g., "Unit A1")
277
+ // =============================================================================
265
278
 
266
279
  model Property {
267
280
  id String @id @default(cuid())
281
+ tenantId String
282
+ tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
268
283
  userId String
269
284
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
270
285
  title String
271
286
  category String // SALE, RENT, LEASE
272
- propertyType String // APARTMENT, HOUSE, LAND, COMMERCIAL
287
+ propertyType String // APARTMENT, HOUSE, LAND, COMMERCIAL, ESTATE, TOWNHOUSE
273
288
  country String
274
289
  currency String // USD, NGN, etc
275
290
  city String
276
291
  district String?
277
292
  zipCode String?
278
293
  streetAddress String?
279
- nBedrooms String
280
- nBathrooms String
281
- nParkingSpots String
282
- price Float
283
294
  longitude Float?
284
295
  latitude Float?
285
- area Float?
286
- status String @default("DRAFT") // DRAFT, PUBLISHED, SOLD, RENTED
296
+ status String @default("DRAFT") // DRAFT, PUBLISHED, SOLD_OUT, ARCHIVED
287
297
  description String? @db.Text
288
298
  displayImageId String?
289
299
  displayImage PropertyMedia? @relation("DisplayImage", fields: [displayImageId], references: [id], onDelete: SetNull)
@@ -292,17 +302,19 @@ model Property {
292
302
  createdAt DateTime @default(now())
293
303
  updatedAt DateTime @updatedAt
294
304
 
295
- documents PropertyDocument[]
296
- media PropertyMedia[] @relation("PropertyMedia")
297
- amenities PropertyAmenity[]
298
- mortgages Mortgage[]
299
- paymentPlans PaymentPlan[]
300
- contracts Contract[]
305
+ // Relations
306
+ documents PropertyDocument[]
307
+ media PropertyMedia[] @relation("PropertyMedia")
308
+ amenities PropertyAmenity[] // Shared amenities (gym, pool, security)
309
+ paymentMethods PropertyPaymentMethodLink[]
310
+ variants PropertyVariant[]
301
311
 
312
+ @@index([tenantId])
302
313
  @@index([userId])
303
314
  @@index([category])
304
315
  @@index([propertyType])
305
316
  @@index([city])
317
+ @@index([status])
306
318
  @@map("properties")
307
319
  }
308
320
 
@@ -338,15 +350,134 @@ model PropertyDocument {
338
350
  }
339
351
 
340
352
  model Amenity {
341
- id String @id @default(cuid())
342
- name String @unique
343
- createdAt DateTime @default(now())
344
- updatedAt DateTime @updatedAt
353
+ id String @id @default(cuid())
354
+ name String @unique
355
+ category String? // PROPERTY, VARIANT, BOTH - helps filter which amenities to show
356
+ icon String? // Icon name/URL for UI
357
+ createdAt DateTime @default(now())
358
+ updatedAt DateTime @updatedAt
345
359
  properties PropertyAmenity[]
360
+ variants PropertyVariantAmenity[]
346
361
 
362
+ @@index([category])
347
363
  @@map("amenities")
348
364
  }
349
365
 
366
+ // =============================================================================
367
+ // PROPERTY VARIANT & UNIT MODELS
368
+ // =============================================================================
369
+
370
+ // PropertyVariant = specific configuration with its own price and amenities
371
+ // e.g., "3-Bedroom Corner Piece - Fully Finished", "2-Bedroom Standard - Carcass"
372
+ model PropertyVariant {
373
+ id String @id @default(cuid())
374
+ propertyId String
375
+ property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
376
+
377
+ name String // "Corner Piece - Finished", "Standard - Carcass"
378
+ description String? @db.Text
379
+
380
+ // Specifications
381
+ nBedrooms Int?
382
+ nBathrooms Int?
383
+ nParkingSpots Int?
384
+ area Float? // Square meters/feet
385
+
386
+ // Pricing
387
+ price Float
388
+ pricePerSqm Float? // Computed or set manually
389
+
390
+ // Inventory counters (denormalized for performance, updated via triggers/service)
391
+ totalUnits Int @default(1)
392
+ availableUnits Int @default(1)
393
+ reservedUnits Int @default(0)
394
+ soldUnits Int @default(0)
395
+
396
+ // Status
397
+ status String @default("AVAILABLE") // AVAILABLE, LOW_STOCK, SOLD_OUT, ARCHIVED
398
+ isActive Boolean @default(true)
399
+ createdAt DateTime @default(now())
400
+ updatedAt DateTime @updatedAt
401
+
402
+ // Relations
403
+ amenities PropertyVariantAmenity[]
404
+ units PropertyUnit[]
405
+ media PropertyVariantMedia[]
406
+
407
+ @@index([propertyId])
408
+ @@index([status])
409
+ @@index([price])
410
+ @@map("property_variants")
411
+ }
412
+
413
+ // PropertyVariantAmenity = amenities specific to a variant
414
+ // e.g., "Finished Kitchen", "Smart Home System", "Private Garden"
415
+ model PropertyVariantAmenity {
416
+ variantId String
417
+ amenityId String
418
+ variant PropertyVariant @relation(fields: [variantId], references: [id], onDelete: Cascade)
419
+ amenity Amenity @relation(fields: [amenityId], references: [id], onDelete: Cascade)
420
+ createdAt DateTime @default(now())
421
+
422
+ @@id([variantId, amenityId])
423
+ @@map("property_variant_amenities")
424
+ }
425
+
426
+ // PropertyVariantMedia = images/videos specific to a variant
427
+ model PropertyVariantMedia {
428
+ id String @id @default(cuid())
429
+ variantId String
430
+ variant PropertyVariant @relation(fields: [variantId], references: [id], onDelete: Cascade)
431
+ url String
432
+ type String // IMAGE, VIDEO, FLOOR_PLAN, 3D_TOUR
433
+ caption String?
434
+ order Int @default(0)
435
+ createdAt DateTime @default(now())
436
+ updatedAt DateTime @updatedAt
437
+
438
+ @@index([variantId])
439
+ @@map("property_variant_media")
440
+ }
441
+
442
+ // PropertyUnit = individual sellable/rentable unit within a variant
443
+ // e.g., "Unit A1", "Block B - Flat 3", "Plot 15"
444
+ model PropertyUnit {
445
+ id String @id @default(cuid())
446
+ variantId String
447
+ variant PropertyVariant @relation(fields: [variantId], references: [id], onDelete: Cascade)
448
+
449
+ unitNumber String // "A1", "B-3", "Plot 15"
450
+ floorNumber Int? // For apartments
451
+ blockName String? // "Block A", "Tower 1"
452
+
453
+ // Unit-specific overrides (if different from variant)
454
+ priceOverride Float? // If this specific unit has a different price
455
+ areaOverride Float? // If this specific unit has a different area
456
+ notes String? @db.Text // Internal notes about this unit
457
+
458
+ // Status tracking
459
+ status String @default("AVAILABLE") // AVAILABLE, RESERVED, SOLD, RENTED, UNAVAILABLE
460
+
461
+ // Reservation/hold
462
+ reservedAt DateTime?
463
+ reservedUntil DateTime?
464
+ reservedById String?
465
+
466
+ // Ownership tracking (once sold)
467
+ ownerId String?
468
+
469
+ createdAt DateTime @default(now())
470
+ updatedAt DateTime @updatedAt
471
+
472
+ // Relations
473
+ contracts Contract[]
474
+
475
+ @@unique([variantId, unitNumber])
476
+ @@index([variantId])
477
+ @@index([status])
478
+ @@map("property_units")
479
+ }
480
+
350
481
  model PropertyAmenity {
351
482
  propertyId String
352
483
  amenityId String
@@ -359,305 +490,468 @@ model PropertyAmenity {
359
490
  }
360
491
 
361
492
  // =============================================================================
362
- // MORTGAGE DOMAIN
493
+ // PAYMENT PLAN DOMAIN - Reusable installment structure templates
363
494
  // =============================================================================
364
495
 
365
- model Mortgage {
366
- id String @id @default(cuid())
367
- propertyId String
368
- property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
369
- borrowerId String
370
- borrower User @relation("MortgageBorrower", fields: [borrowerId], references: [id], onDelete: Cascade)
371
- mortgageTypeId String?
372
- mortgageType MortgageType? @relation(fields: [mortgageTypeId], references: [id])
373
- downpaymentPlanId String?
374
- downpaymentPlan MortgageDownpaymentPlan? @relation(fields: [downpaymentPlanId], references: [id])
375
- principal Float
376
- downPayment Float
377
- downPaymentPaid Float @default(0)
378
- termMonths Int
379
- interestRate Float
380
- monthlyPayment Float
381
- status String @default("DRAFT") // DRAFT, PENDING, ACTIVE, COMPLETED, CANCELLED
382
- state String @default("DRAFT") // FSM state
383
- stateMetadata String? @db.Text // JSON metadata
384
- lastReminderSentAt DateTime?
385
- createdAt DateTime @default(now())
386
- updatedAt DateTime @updatedAt
496
+ // PaymentPlan = reusable structure for how payments are scheduled
497
+ // Examples: "Monthly360" (360 monthly payments), "Weekly52", "OneTime"
498
+ model PaymentPlan {
499
+ id String @id @default(cuid())
500
+ tenantId String? // NULL = global template available to all tenants
501
+ tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: Cascade)
502
+ name String
503
+ description String? @db.Text
504
+ isActive Boolean @default(true)
387
505
 
388
- documents MortgageDocument[]
389
- steps MortgageStep[]
390
- transitions MortgageTransition[]
391
- transitionEvents MortgageTransitionEvent[]
506
+ // Structure configuration
507
+ paymentFrequency String // MONTHLY, BIWEEKLY, WEEKLY, ONE_TIME, CUSTOM
508
+ customFrequencyDays Int?
509
+ numberOfInstallments Int // 1 for one-time, 360 for 30yr monthly, etc
510
+ calculateInterestDaily Boolean @default(false)
511
+ gracePeriodDays Int @default(0)
392
512
 
393
- @@index([propertyId])
394
- @@index([borrowerId])
395
- @@index([status])
396
- @@index([state])
397
- @@map("mortgages")
398
- }
513
+ createdAt DateTime @default(now())
514
+ updatedAt DateTime @updatedAt
399
515
 
400
- model MortgageType {
401
- id String @id @default(cuid())
402
- name String @unique
403
- description String? @db.Text
404
- createdAt DateTime @default(now())
405
- updatedAt DateTime @updatedAt
406
- mortgages Mortgage[]
516
+ // Used by property payment method phases (templates)
517
+ methodPhases PropertyPaymentMethodPhase[]
518
+ // Used by instantiated contract phases
519
+ contractPhases ContractPhase[]
407
520
 
408
- @@map("mortgage_types")
521
+ @@unique([tenantId, name]) // Unique per tenant, or globally if tenantId is null
522
+ @@index([tenantId])
523
+ @@map("payment_plans")
409
524
  }
410
525
 
411
- model MortgageDocument {
412
- id String @id @default(cuid())
413
- mortgageId String
414
- mortgage Mortgage @relation(fields: [mortgageId], references: [id], onDelete: Cascade)
415
- name String
416
- url String
417
- type String // INCOME_PROOF, ID, etc
418
- createdAt DateTime @default(now())
419
- updatedAt DateTime @updatedAt
526
+ // =============================================================================
527
+ // PROPERTY PAYMENT METHOD DOMAIN - Product offerings per property
528
+ // =============================================================================
420
529
 
421
- @@index([mortgageId])
422
- @@map("mortgage_documents")
423
- }
530
+ // PropertyPaymentMethod = how a property can be purchased (e.g., "Standard Mortgage", "Cash", "Rent-to-Own")
531
+ model PropertyPaymentMethod {
532
+ id String @id @default(cuid())
533
+ tenantId String
534
+ tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
535
+ name String // "Standard Mortgage", "Flexible Payment", "Cash Purchase"
536
+ description String? @db.Text
537
+ isActive Boolean @default(true)
538
+
539
+ // Global method configuration
540
+ allowEarlyPayoff Boolean @default(true)
541
+ earlyPayoffPenaltyRate Float?
542
+ autoActivatePhases Boolean @default(true)
543
+ requiresManualApproval Boolean @default(false)
424
544
 
425
- model MortgageStep {
426
- id String @id @default(cuid())
427
- mortgageId String
428
- mortgage Mortgage @relation(fields: [mortgageId], references: [id], onDelete: Cascade)
429
- name String
430
- description String? @db.Text
431
- order Int
432
- isCompleted Boolean @default(false)
433
- completedAt DateTime?
434
- createdAt DateTime @default(now())
435
- updatedAt DateTime @updatedAt
545
+ createdAt DateTime @default(now())
546
+ updatedAt DateTime @updatedAt
547
+
548
+ // Many-to-many with properties
549
+ properties PropertyPaymentMethodLink[]
550
+ // Phases that make up this method (templates)
551
+ phases PropertyPaymentMethodPhase[]
552
+ // Contracts using this method
553
+ contracts Contract[]
436
554
 
437
- @@index([mortgageId])
438
- @@map("mortgage_steps")
555
+ @@unique([tenantId, name]) // Unique per tenant
556
+ @@index([tenantId])
557
+ @@map("property_payment_methods")
439
558
  }
440
559
 
441
- model MortgageDownpaymentPlan {
442
- id String @id @default(cuid())
443
- totalAmount Float
444
- paidAmount Float @default(0)
445
- status String @default("PENDING") // PENDING, ACTIVE, COMPLETED
446
- startDate DateTime
447
- endDate DateTime
448
- createdAt DateTime @default(now())
449
- updatedAt DateTime @updatedAt
560
+ // Many-to-many link between Property and PaymentMethod
561
+ model PropertyPaymentMethodLink {
562
+ propertyId String
563
+ property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
564
+ paymentMethodId String
565
+ paymentMethod PropertyPaymentMethod @relation(fields: [paymentMethodId], references: [id], onDelete: Cascade)
450
566
 
451
- mortgages Mortgage[]
452
- installments MortgageDownpaymentInstallment[]
453
- payments MortgageDownpaymentPayment[]
567
+ // Method-specific overrides for this property
568
+ isDefault Boolean @default(false)
569
+ isActive Boolean @default(true)
570
+ createdAt DateTime @default(now())
454
571
 
455
- @@map("mortgage_downpayment_plans")
572
+ @@id([propertyId, paymentMethodId])
573
+ @@map("property_payment_method_links")
456
574
  }
457
575
 
458
- model MortgageDownpaymentInstallment {
459
- id String @id @default(cuid())
460
- planId String
461
- plan MortgageDownpaymentPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
462
- amount Float
463
- dueDate DateTime
464
- isPaid Boolean @default(false)
465
- paidDate DateTime?
466
- createdAt DateTime @default(now())
467
- updatedAt DateTime @updatedAt
576
+ // Phase template within a PropertyPaymentMethod (e.g., documentation, downpayment, mortgage)
577
+ // phaseCategory determines the FSM type: DOCUMENTATION or PAYMENT
578
+ model PropertyPaymentMethodPhase {
579
+ id String @id @default(cuid())
580
+ paymentMethodId String
581
+ paymentMethod PropertyPaymentMethod @relation(fields: [paymentMethodId], references: [id], onDelete: Cascade)
582
+ paymentPlanId String? // Only for PAYMENT phases
583
+ paymentPlan PaymentPlan? @relation(fields: [paymentPlanId], references: [id])
468
584
 
469
- @@index([planId])
470
- @@map("mortgage_downpayment_installments")
471
- }
585
+ name String
586
+ description String? @db.Text
472
587
 
473
- model MortgageDownpaymentPayment {
474
- id String @id @default(cuid())
475
- planId String
476
- plan MortgageDownpaymentPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
477
- amount Float
478
- paymentMethod String
479
- reference String?
480
- status String @default("PENDING")
481
- createdAt DateTime @default(now())
482
- updatedAt DateTime @updatedAt
588
+ // Phase classification
589
+ phaseCategory String // DOCUMENTATION, PAYMENT
590
+ phaseType String // Admin-defined: KYC, VERIFICATION, DOWNPAYMENT, MORTGAGE, BALLOON, CUSTOM, etc.
591
+ order Int
483
592
 
484
- @@index([planId])
485
- @@map("mortgage_downpayment_payments")
486
- }
593
+ // Financial configuration (for PAYMENT phases)
594
+ interestRate Float?
595
+ percentOfPrice Float? // e.g., 10.0 for 10% downpayment
487
596
 
488
- model MortgageTransition {
489
- id String @id @default(cuid())
490
- mortgageId String
491
- mortgage Mortgage @relation(fields: [mortgageId], references: [id], onDelete: Cascade)
492
- fromState String
493
- toState String
494
- trigger String
495
- metadata String? @db.Text // JSON
496
- transitionedAt DateTime @default(now())
597
+ // Activation rules
598
+ requiresPreviousPhaseCompletion Boolean @default(true)
599
+ minimumCompletionPercentage Float?
497
600
 
498
- @@index([mortgageId])
499
- @@map("mortgage_transitions")
500
- }
601
+ // For DOCUMENTATION phases: define required steps
602
+ requiredDocumentTypes String? // CSV: ID,BANK_STATEMENT,INCOME_PROOF
603
+ stepDefinitions String? @db.Text // JSON: [{name, stepType, order}]
501
604
 
502
- model MortgageTransitionEvent {
503
- id String @id @default(cuid())
504
- mortgageId String
505
- mortgage Mortgage @relation(fields: [mortgageId], references: [id], onDelete: Cascade)
506
- event String
507
- data String? @db.Text // JSON
508
- createdAt DateTime @default(now())
605
+ createdAt DateTime @default(now())
606
+ updatedAt DateTime @updatedAt
509
607
 
510
- @@index([mortgageId])
511
- @@map("mortgage_transition_events")
608
+ @@index([paymentMethodId])
609
+ @@index([paymentPlanId])
610
+ @@index([phaseCategory])
611
+ @@map("property_payment_method_phases")
512
612
  }
513
613
 
514
614
  // =============================================================================
515
- // PAYMENT & CONTRACT DOMAIN
615
+ // CONTRACT DOMAIN - Unified agreement model (replaces Mortgage, PurchasePlan, etc.)
616
+ // =============================================================================
617
+ // Contract is the canonical agreement. "Mortgage" is just a product configuration
618
+ // that creates a Contract with specific phases (documentation, downpayment, long-term payment).
619
+ // Phases can be DOCUMENTATION (FSM for approvals) or PAYMENT (PaymentPlan-driven installments).
516
620
  // =============================================================================
517
621
 
518
- model PaymentPlan {
519
- id String @id @default(cuid())
520
- propertyId String
521
- property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
522
- buyerId String?
523
- buyer User? @relation(fields: [buyerId], references: [id])
524
- planType String // MORTGAGE, INSTALLMENT, RENT_TO_OWN, LEASE, OUTRIGHT_PURCHASE, CUSTOM
525
- name String
526
- description String? @db.Text
527
- totalAmount Float
528
- downPaymentAmount Float @default(0)
529
- downPaymentPaid Float @default(0)
530
- principalAmount Float
531
- interestRate Float @default(0)
532
- totalInterest Float @default(0)
533
- state String @default("DRAFT") // FSM state
534
- stateMetadata String? @db.Text
535
- createdAt DateTime @default(now())
536
- updatedAt DateTime @updatedAt
537
-
538
- contract Contract?
539
- schedules PaymentSchedule[]
540
- payments Payment[]
622
+ model Contract {
623
+ id String @id @default(cuid())
624
+ tenantId String
625
+ tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
626
+ // Link to specific unit being purchased/rented
627
+ propertyUnitId String
628
+ propertyUnit PropertyUnit @relation(fields: [propertyUnitId], references: [id], onDelete: Cascade)
629
+ buyerId String
630
+ buyer User @relation("ContractBuyer", fields: [buyerId], references: [id], onDelete: Cascade)
631
+ sellerId String?
632
+ seller User? @relation("ContractSeller", fields: [sellerId], references: [id])
633
+ paymentMethodId String? // PropertyPaymentMethod used to create this contract
634
+ paymentMethod PropertyPaymentMethod? @relation(fields: [paymentMethodId], references: [id])
635
+
636
+ // Contract identification
637
+ contractNumber String @unique
638
+ title String
639
+ description String? @db.Text
640
+ contractType String // Admin-defined: MORTGAGE, INSTALLMENT, RENT_TO_OWN, CASH, LEASE, etc.
641
+
642
+ // Financial summary (computed from phases)
643
+ totalAmount Float // Total contract value (from unit price or negotiated)
644
+ downPayment Float @default(0)
645
+ downPaymentPaid Float @default(0)
646
+ principal Float? // Financed amount (if applicable)
647
+ interestRate Float? // Overall interest rate (if applicable)
648
+ termMonths Int? // Total term (if applicable)
649
+ periodicPayment Float? // Computed periodic payment (if applicable)
650
+ totalPaidToDate Float @default(0)
651
+ totalInterestPaid Float @default(0)
652
+
653
+ // FSM state
654
+ status String @default("DRAFT") // DRAFT, PENDING, ACTIVE, COMPLETED, CANCELLED, TERMINATED
655
+ state String @default("DRAFT") // FSM state for workflow
656
+ currentPhaseId String?
657
+
658
+ // Timing
659
+ nextPaymentDueDate DateTime?
660
+ lastReminderSentAt DateTime?
661
+ startDate DateTime?
662
+ endDate DateTime?
663
+ signedAt DateTime?
664
+ terminatedAt DateTime?
665
+ createdAt DateTime @default(now())
666
+ updatedAt DateTime @updatedAt
667
+
668
+ // Relations
669
+ phases ContractPhase[]
670
+ documents ContractDocument[]
671
+ payments ContractPayment[]
672
+ transitions ContractTransition[]
673
+ events ContractEvent[]
541
674
 
542
- @@index([propertyId])
675
+ @@index([tenantId])
676
+ @@index([propertyUnitId])
543
677
  @@index([buyerId])
678
+ @@index([sellerId])
679
+ @@index([paymentMethodId])
680
+ @@index([status])
544
681
  @@index([state])
545
- @@map("payment_plans")
682
+ @@map("contracts")
546
683
  }
547
684
 
548
- model PaymentSchedule {
549
- id String @id @default(cuid())
550
- planId String
551
- plan PaymentPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
552
- name String
553
- frequency String // MONTHLY, WEEKLY, QUARTERLY, CUSTOM
554
- startDate DateTime
555
- endDate DateTime?
556
- isActive Boolean @default(true)
557
- createdAt DateTime @default(now())
558
- updatedAt DateTime @updatedAt
685
+ // Phase within a contract - can be DOCUMENTATION or PAYMENT type
686
+ // Admin names phases freely (e.g., "KYC Documents", "Downpayment", "Monthly Mortgage")
687
+ model ContractPhase {
688
+ id String @id @default(cuid())
689
+ contractId String
690
+ contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
691
+ paymentPlanId String? // Only for PAYMENT phases
692
+ paymentPlan PaymentPlan? @relation(fields: [paymentPlanId], references: [id])
693
+
694
+ // Admin-defined naming
695
+ name String
696
+ description String? @db.Text
697
+
698
+ // Phase classification
699
+ phaseCategory String // DOCUMENTATION, PAYMENT
700
+ phaseType String // Admin-defined: DOWNPAYMENT, MORTGAGE, KYC, VERIFICATION, BALLOON, CUSTOM, etc.
701
+ order Int
702
+
703
+ // FSM state for this phase
704
+ status String @default("PENDING") // PENDING, IN_PROGRESS, AWAITING_APPROVAL, ACTIVE, COMPLETED, SKIPPED, FAILED
705
+
706
+ // Financial details (for PAYMENT phases)
707
+ totalAmount Float?
708
+ paidAmount Float @default(0)
709
+ remainingAmount Float?
710
+ interestRate Float?
711
+
712
+ // Timing
713
+ dueDate DateTime?
714
+ startDate DateTime?
715
+ endDate DateTime?
716
+ activatedAt DateTime?
717
+ completedAt DateTime?
559
718
 
560
- installments PaymentInstallment[]
561
- payments Payment[]
719
+ // Activation rules
720
+ requiresPreviousPhaseCompletion Boolean @default(true)
721
+ minimumCompletionPercentage Float?
562
722
 
563
- @@index([planId])
564
- @@map("payment_schedules")
723
+ createdAt DateTime @default(now())
724
+ updatedAt DateTime @updatedAt
725
+
726
+ // Relations
727
+ installments ContractInstallment[]
728
+ payments ContractPayment[]
729
+ steps ContractPhaseStep[] // For DOCUMENTATION phases (FSM steps)
730
+
731
+ @@index([contractId])
732
+ @@index([paymentPlanId])
733
+ @@index([phaseCategory])
734
+ @@index([status])
735
+ @@index([order])
736
+ @@map("contract_phases")
565
737
  }
566
738
 
567
- model PaymentInstallment {
568
- id String @id @default(cuid())
569
- scheduleId String
570
- schedule PaymentSchedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
739
+ // Steps within a DOCUMENTATION phase (FSM for document collection/approval)
740
+ model ContractPhaseStep {
741
+ id String @id @default(cuid())
742
+ phaseId String
743
+ phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
744
+
745
+ name String
746
+ description String? @db.Text
747
+ stepType String // UPLOAD, REVIEW, SIGNATURE, APPROVAL, EXTERNAL_CHECK, WAIT
748
+ order Int
749
+
750
+ status String @default("PENDING") // PENDING, IN_PROGRESS, COMPLETED, FAILED, SKIPPED
751
+
752
+ // Assignment
753
+ assigneeId String?
754
+ assignee User? @relation("PhaseStepAssignee", fields: [assigneeId], references: [id])
755
+
756
+ // Required document types for UPLOAD steps
757
+ requiredDocumentTypes String? // CSV: ID,BANK_STATEMENT,INCOME_PROOF
758
+
759
+ // Timing
760
+ dueDate DateTime?
761
+ completedAt DateTime?
762
+
763
+ createdAt DateTime @default(now())
764
+ updatedAt DateTime @updatedAt
765
+
766
+ approvals ContractPhaseStepApproval[]
767
+
768
+ @@index([phaseId])
769
+ @@index([status])
770
+ @@index([order])
771
+ @@map("contract_phase_steps")
772
+ }
773
+
774
+ // Approvals for documentation steps
775
+ model ContractPhaseStepApproval {
776
+ id String @id @default(cuid())
777
+ stepId String
778
+ step ContractPhaseStep @relation(fields: [stepId], references: [id], onDelete: Cascade)
779
+ approverId String?
780
+ approver User? @relation("PhaseStepApprover", fields: [approverId], references: [id])
781
+
782
+ decision String // APPROVED, REJECTED, REQUEST_CHANGES
783
+ comment String? @db.Text
784
+ decidedAt DateTime @default(now())
785
+
786
+ createdAt DateTime @default(now())
787
+
788
+ @@index([stepId])
789
+ @@map("contract_phase_step_approvals")
790
+ }
791
+
792
+ // Installments within a PAYMENT phase
793
+ model ContractInstallment {
794
+ id String @id @default(cuid())
795
+ phaseId String
796
+ phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
797
+
571
798
  installmentNumber Int
572
- amount Float
573
- principalAmount Float
574
- interestAmount Float
575
- dueDate DateTime
576
- status String @default("PENDING") // PENDING, PAID, OVERDUE, WAIVED
577
- paidAmount Float @default(0)
578
- paidDate DateTime?
579
- lateFee Float @default(0)
580
- createdAt DateTime @default(now())
581
- updatedAt DateTime @updatedAt
582
-
583
- payments Payment[]
584
-
585
- @@index([scheduleId])
799
+
800
+ amount Float
801
+ principalAmount Float @default(0)
802
+ interestAmount Float @default(0)
803
+
804
+ dueDate DateTime
805
+ status String @default("PENDING") // PENDING, PAID, OVERDUE, PARTIALLY_PAID, WAIVED
806
+
807
+ paidAmount Float @default(0)
808
+ paidDate DateTime?
809
+
810
+ lateFee Float @default(0)
811
+ lateFeeWaived Boolean @default(false)
812
+ gracePeriodDays Int @default(0)
813
+ gracePeriodEndDate DateTime?
814
+
815
+ createdAt DateTime @default(now())
816
+ updatedAt DateTime @updatedAt
817
+
818
+ payments ContractPayment[]
819
+
820
+ @@index([phaseId])
586
821
  @@index([dueDate])
587
822
  @@index([status])
588
- @@map("payment_installments")
589
- }
590
-
591
- model Payment {
592
- id String @id @default(cuid())
593
- planId String
594
- plan PaymentPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
595
- scheduleId String?
596
- schedule PaymentSchedule? @relation(fields: [scheduleId], references: [id])
597
- installmentId String?
598
- installment PaymentInstallment? @relation(fields: [installmentId], references: [id])
599
- payerId String?
600
- payer User? @relation(fields: [payerId], references: [id])
823
+ @@map("contract_installments")
824
+ }
825
+
826
+ // Unified payment record for contracts
827
+ model ContractPayment {
828
+ id String @id @default(cuid())
829
+ contractId String
830
+ contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
831
+ phaseId String?
832
+ phase ContractPhase? @relation(fields: [phaseId], references: [id])
833
+ installmentId String?
834
+ installment ContractInstallment? @relation(fields: [installmentId], references: [id])
835
+ payerId String?
836
+ payer User? @relation("ContractPayer", fields: [payerId], references: [id])
837
+
601
838
  amount Float
602
- principalAmount Float @default(0)
603
- interestAmount Float @default(0)
604
- lateFeeAmount Float @default(0)
605
- paymentMethod String // BANK_TRANSFER, CREDIT_CARD, WALLET, etc
606
- status String @default("INITIATED") // INITIATED, PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED, REFUNDED
607
- reference String? @unique
608
- gatewayResponse String? @db.Text // JSON
609
- processedAt DateTime?
610
- createdAt DateTime @default(now())
611
- updatedAt DateTime @updatedAt
612
-
613
- @@index([planId])
839
+ principalAmount Float @default(0)
840
+ interestAmount Float @default(0)
841
+ lateFeeAmount Float @default(0)
842
+
843
+ paymentMethod String // BANK_TRANSFER, CREDIT_CARD, WALLET, CASH, CHECK
844
+ status String @default("INITIATED") // INITIATED, PENDING, COMPLETED, FAILED, REFUNDED
845
+
846
+ reference String? @unique
847
+ gatewayResponse String? @db.Text // JSON
848
+
849
+ processedAt DateTime?
850
+ createdAt DateTime @default(now())
851
+ updatedAt DateTime @updatedAt
852
+
853
+ @@index([contractId])
854
+ @@index([phaseId])
855
+ @@index([installmentId])
614
856
  @@index([payerId])
615
857
  @@index([status])
616
858
  @@index([reference])
617
- @@map("payments")
859
+ @@map("contract_payments")
618
860
  }
619
861
 
620
- model Contract {
621
- id String @id @default(cuid())
622
- propertyId String
623
- property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
624
- paymentPlanId String? @unique
625
- paymentPlan PaymentPlan? @relation(fields: [paymentPlanId], references: [id])
626
- buyerId String?
627
- buyer User? @relation("ContractBuyer", fields: [buyerId], references: [id])
628
- sellerId String?
629
- seller User? @relation("ContractSeller", fields: [sellerId], references: [id])
630
- contractType String // MORTGAGE, SALE_AGREEMENT, LEASE_AGREEMENT, etc
631
- contractNumber String @unique
632
- title String
633
- description String? @db.Text
634
- status String @default("DRAFT") // DRAFT, PENDING_SIGNATURE, ACTIVE, COMPLETED, TERMINATED
635
- startDate DateTime?
636
- endDate DateTime?
637
- signedAt DateTime?
638
- terminatedAt DateTime?
639
- createdAt DateTime @default(now())
640
- updatedAt DateTime @updatedAt
862
+ // Contract documents (owned by contract, linked to phases/steps as needed)
863
+ model ContractDocument {
864
+ id String @id @default(cuid())
865
+ contractId String
866
+ contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
867
+ phaseId String? // Optional link to specific phase
868
+ stepId String? // Optional link to specific step
641
869
 
642
- documents ContractDocument[]
870
+ name String
871
+ url String
872
+ type String // ID, BANK_STATEMENT, INCOME_PROOF, TITLE_DEED, SIGNATURE, etc.
873
+ uploadedById String?
874
+ uploadedBy User? @relation("DocumentUploader", fields: [uploadedById], references: [id])
643
875
 
644
- @@index([propertyId])
645
- @@index([buyerId])
646
- @@index([sellerId])
876
+ status String @default("PENDING") // PENDING, APPROVED, REJECTED
877
+
878
+ createdAt DateTime @default(now())
879
+ updatedAt DateTime @updatedAt
880
+
881
+ @@index([contractId])
882
+ @@index([phaseId])
883
+ @@index([stepId])
884
+ @@index([type])
647
885
  @@index([status])
648
- @@map("contracts")
886
+ @@map("contract_documents")
649
887
  }
650
888
 
651
- model ContractDocument {
889
+ // FSM transitions for audit
890
+ model ContractTransition {
891
+ id String @id @default(cuid())
892
+ contractId String
893
+ contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
894
+ fromState String
895
+ toState String
896
+ trigger String
897
+ metadata String? @db.Text // JSON
898
+ transitionedAt DateTime @default(now())
899
+
900
+ @@index([contractId])
901
+ @@map("contract_transitions")
902
+ }
903
+
904
+ // Domain events for audit and integration
905
+ model ContractEvent {
652
906
  id String @id @default(cuid())
653
907
  contractId String
654
908
  contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
655
- name String
656
- url String
657
- type String
909
+ event String
910
+ data String? @db.Text // JSON
658
911
  createdAt DateTime @default(now())
659
- updatedAt DateTime @updatedAt
660
912
 
661
913
  @@index([contractId])
662
- @@map("contract_documents")
914
+ @@map("contract_events")
915
+ }
916
+
917
+ // =============================================================================
918
+ // EVENT OUTBOX - For guaranteed event delivery to SQS queues
919
+ // =============================================================================
920
+
921
+ model DomainEvent {
922
+ id String @id @default(cuid())
923
+
924
+ // Event identification
925
+ eventType String // MORTGAGE.CREATED, PHASE.ACTIVATED, PAYMENT.COMPLETED, etc
926
+ aggregateType String // Mortgage, MortgagePhase, MortgagePayment, Property, etc
927
+ aggregateId String
928
+
929
+ // Routing - which queue(s) should receive this
930
+ queueName String // notifications, payments, mortgage-steps, accounting, etc
931
+
932
+ // Event payload (all data needed by consumers)
933
+ payload String @db.Text // JSON
934
+
935
+ // Metadata
936
+ occurredAt DateTime @default(now())
937
+ actorId String? // User who triggered the event
938
+ actorRole String? // Role of the actor
939
+
940
+ // Processing status
941
+ status String @default("PENDING") // PENDING, PROCESSING, SENT, FAILED
942
+ processedAt DateTime?
943
+ sentAt DateTime?
944
+ failureCount Int @default(0)
945
+ lastError String? @db.Text
946
+ nextRetryAt DateTime?
947
+
948
+ createdAt DateTime @default(now())
949
+ updatedAt DateTime @updatedAt
950
+
951
+ @@index([status, nextRetryAt])
952
+ @@index([eventType])
953
+ @@index([aggregateType, aggregateId])
954
+ @@index([queueName])
955
+ @@index([occurredAt])
956
+ @@map("domain_events")
663
957
  }