@valentine-efagene/qshelter-common 2.0.125 → 2.0.127

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.
@@ -10,6 +10,7 @@ export * from './ApplicationTermination';
10
10
  export * from './ApprovalRequest';
11
11
  export * from './DeviceEndpoint';
12
12
  export * from './DocumentRequirementRule';
13
+ export * from './DocumentReview';
13
14
  export * from './DocumentTemplate';
14
15
  export * from './DocumentationPhase';
15
16
  export * from './DocumentationPlan';
@@ -25,6 +26,8 @@ export * from './EventHandlerExecution';
25
26
  export * from './EventType';
26
27
  export * from './OAuthState';
27
28
  export * from './OfferLetter';
29
+ export * from './Organization';
30
+ export * from './OrganizationMember';
28
31
  export * from './PasswordReset';
29
32
  export * from './PaymentInstallment';
30
33
  export * from './PaymentMethodChangeRequest';
@@ -10,6 +10,7 @@ export * from './ApplicationTermination';
10
10
  export * from './ApprovalRequest';
11
11
  export * from './DeviceEndpoint';
12
12
  export * from './DocumentRequirementRule';
13
+ export * from './DocumentReview';
13
14
  export * from './DocumentTemplate';
14
15
  export * from './DocumentationPhase';
15
16
  export * from './DocumentationPlan';
@@ -25,6 +26,8 @@ export * from './EventHandlerExecution';
25
26
  export * from './EventType';
26
27
  export * from './OAuthState';
27
28
  export * from './OfferLetter';
29
+ export * from './Organization';
30
+ export * from './OrganizationMember';
28
31
  export * from './PasswordReset';
29
32
  export * from './PaymentInstallment';
30
33
  export * from './PaymentMethodChangeRequest';
@@ -54,6 +54,7 @@ export type * from './models/DocumentationStepApproval.js';
54
54
  export type * from './models/PaymentInstallment.js';
55
55
  export type * from './models/ApplicationPayment.js';
56
56
  export type * from './models/ApplicationDocument.js';
57
+ export type * from './models/DocumentReview.js';
57
58
  export type * from './models/DocumentTemplate.js';
58
59
  export type * from './models/OfferLetter.js';
59
60
  export type * from './models/ApplicationTermination.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentine-efagene/qshelter-common",
3
- "version": "2.0.125",
3
+ "version": "2.0.127",
4
4
  "description": "Shared database schemas and utilities for QShelter services",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -0,0 +1,59 @@
1
+ -- AlterTable
2
+ ALTER TABLE `application_documents` ADD COLUMN `replacesDocumentId` VARCHAR(191) NULL,
3
+ ADD COLUMN `version` INTEGER NOT NULL DEFAULT 1;
4
+
5
+ -- AlterTable
6
+ ALTER TABLE `documentation_plan_steps` ADD COLUMN `reviewOrder` VARCHAR(191) NULL,
7
+ ADD COLUMN `reviewRequirements` JSON NULL;
8
+
9
+ -- AlterTable
10
+ ALTER TABLE `documentation_steps` ADD COLUMN `reviewOrder` VARCHAR(191) NULL,
11
+ ADD COLUMN `reviewRequirements` JSON NULL;
12
+
13
+ -- CreateTable
14
+ CREATE TABLE `document_reviews` (
15
+ `id` VARCHAR(191) NOT NULL,
16
+ `tenantId` VARCHAR(191) NOT NULL,
17
+ `documentId` VARCHAR(191) NOT NULL,
18
+ `reviewParty` ENUM('INTERNAL', 'BANK', 'DEVELOPER', 'LEGAL', 'GOVERNMENT', 'INSURER', 'CUSTOMER') NOT NULL,
19
+ `organizationId` VARCHAR(191) NULL,
20
+ `reviewerId` VARCHAR(191) NULL,
21
+ `reviewerName` VARCHAR(191) NULL,
22
+ `decision` ENUM('PENDING', 'APPROVED', 'REJECTED', 'CHANGES_REQUESTED', 'WAIVED') NOT NULL DEFAULT 'PENDING',
23
+ `comments` TEXT NULL,
24
+ `concerns` JSON NULL,
25
+ `requestedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
26
+ `dueAt` DATETIME(3) NULL,
27
+ `reviewedAt` DATETIME(3) NULL,
28
+ `reviewOrder` INTEGER NOT NULL DEFAULT 0,
29
+ `parentReviewId` VARCHAR(191) NULL,
30
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
31
+ `updatedAt` DATETIME(3) NOT NULL,
32
+
33
+ INDEX `document_reviews_tenantId_idx`(`tenantId`),
34
+ INDEX `document_reviews_documentId_idx`(`documentId`),
35
+ INDEX `document_reviews_reviewParty_idx`(`reviewParty`),
36
+ INDEX `document_reviews_decision_idx`(`decision`),
37
+ INDEX `document_reviews_reviewerId_idx`(`reviewerId`),
38
+ INDEX `document_reviews_parentReviewId_idx`(`parentReviewId`),
39
+ UNIQUE INDEX `document_reviews_documentId_reviewParty_organizationId_key`(`documentId`, `reviewParty`, `organizationId`),
40
+ PRIMARY KEY (`id`)
41
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
42
+
43
+ -- CreateIndex
44
+ CREATE INDEX `application_documents_replacesDocumentId_idx` ON `application_documents`(`replacesDocumentId`);
45
+
46
+ -- AddForeignKey
47
+ ALTER TABLE `application_documents` ADD CONSTRAINT `application_documents_replacesDocumentId_fkey` FOREIGN KEY (`replacesDocumentId`) REFERENCES `application_documents`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
48
+
49
+ -- AddForeignKey
50
+ ALTER TABLE `document_reviews` ADD CONSTRAINT `document_reviews_tenantId_fkey` FOREIGN KEY (`tenantId`) REFERENCES `tenants`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
51
+
52
+ -- AddForeignKey
53
+ ALTER TABLE `document_reviews` ADD CONSTRAINT `document_reviews_documentId_fkey` FOREIGN KEY (`documentId`) REFERENCES `application_documents`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
54
+
55
+ -- AddForeignKey
56
+ ALTER TABLE `document_reviews` ADD CONSTRAINT `document_reviews_reviewerId_fkey` FOREIGN KEY (`reviewerId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
57
+
58
+ -- AddForeignKey
59
+ ALTER TABLE `document_reviews` ADD CONSTRAINT `document_reviews_parentReviewId_fkey` FOREIGN KEY (`parentReviewId`) REFERENCES `document_reviews`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -201,8 +201,12 @@ enum ApprovalDecision {
201
201
 
202
202
  /// Type of organization on the platform
203
203
  enum OrganizationType {
204
+ PLATFORM // The platform operator (e.g., QShelter) - the tenant's own organization
204
205
  BANK // Financial institution providing mortgages (e.g., Access Bank, GTBank)
205
206
  DEVELOPER // Property developer building and selling properties
207
+ LEGAL // Legal firms handling conveyancing and documentation
208
+ INSURER // Insurance companies providing property or mortgage insurance
209
+ GOVERNMENT // Government agencies (e.g., land registry, tax authorities)
206
210
  }
207
211
 
208
212
  /// Status of an organization
@@ -262,6 +266,30 @@ enum CompletionCriterion {
262
266
  STEPS_COMPLETED
263
267
  }
264
268
 
269
+ // =============================================================================
270
+ // MULTI-PARTY DOCUMENT REVIEW ENUMS
271
+ // =============================================================================
272
+ // Enables different organizations to review the same document independently
273
+ // =============================================================================
274
+
275
+ enum ReviewParty {
276
+ INTERNAL // Your internal team (compliance, underwriting, etc.)
277
+ BANK // Partner bank/lender
278
+ DEVELOPER // Property developer
279
+ LEGAL // Legal team
280
+ GOVERNMENT // Government agencies (e.g., land registry)
281
+ INSURER // Insurance company
282
+ CUSTOMER // Customer acknowledgment/signature
283
+ }
284
+
285
+ enum ReviewDecision {
286
+ PENDING // Review not yet performed
287
+ APPROVED // Document approved by this party
288
+ REJECTED // Document rejected (permanent)
289
+ CHANGES_REQUESTED // Document needs modifications
290
+ WAIVED // Review not required by this party
291
+ }
292
+
265
293
  enum DocumentStatus {
266
294
  DRAFT
267
295
  PENDING
@@ -460,6 +488,9 @@ model User {
460
488
  approvedRefunds ApplicationRefund[] @relation("RefundApprover")
461
489
  processedRefunds ApplicationRefund[] @relation("RefundProcessor")
462
490
 
491
+ // Document reviews by this user
492
+ documentReviews DocumentReview[] @relation("DocumentReviewer")
493
+
463
494
  // Organization memberships (user can be employee of banks/developers)
464
495
  organizationMemberships OrganizationMember[]
465
496
 
@@ -578,9 +609,12 @@ model Organization {
578
609
  id String @id @default(cuid())
579
610
  tenantId String
580
611
  name String // e.g., "Access Bank PLC", "Lekki Gardens Ltd"
581
- type OrganizationType // BANK or DEVELOPER
612
+ type OrganizationType // PLATFORM, BANK, DEVELOPER, LEGAL, INSURER, GOVERNMENT
582
613
  status OrganizationStatus @default(PENDING)
583
614
 
615
+ // Platform organization flag - marks the tenant's own organization
616
+ isPlatformOrg Boolean @default(false)
617
+
584
618
  // Common fields
585
619
  email String? // Primary contact email
586
620
  phone String? // Primary contact phone
@@ -747,6 +781,7 @@ model Tenant {
747
781
  domainEvents DomainEvent[]
748
782
  workflowBlockers WorkflowBlocker[]
749
783
  questionnairePlans QuestionnairePlan[]
784
+ documentReviews DocumentReview[]
750
785
 
751
786
  // Organizations (Banks, Developers) operating on this tenant
752
787
  organizations Organization[]
@@ -1271,6 +1306,17 @@ model DocumentationPlanStep {
1271
1306
  // { "all": [{ "questionKey": "has_spouse", "operator": "EQUALS", "value": "YES" }, { "questionKey": "spouse_employed", "operator": "EQUALS", "value": "YES" }] }
1272
1307
  condition Json?
1273
1308
 
1309
+ // =========================================================================
1310
+ // MULTI-PARTY REVIEW CONFIGURATION (for UPLOAD steps)
1311
+ // =========================================================================
1312
+ // Defines which parties must review documents uploaded in this step
1313
+ // Format: [{ party: "INTERNAL", required: true }, { party: "BANK", required: true }]
1314
+ // If null, defaults to single internal review (backward compatible)
1315
+ reviewRequirements Json?
1316
+
1317
+ // Review order: 'SEQUENTIAL' = parties review in order, 'PARALLEL' = all review simultaneously
1318
+ reviewOrder String? // 'SEQUENTIAL' | 'PARALLEL'
1319
+
1274
1320
  // =========================================================================
1275
1321
  // GATE STEP CONFIGURATION (only applicable when stepType = GATE)
1276
1322
  // =========================================================================
@@ -2290,6 +2336,17 @@ model DocumentationStep {
2290
2336
  // Or: { "questionKey": "employment_status", "operator": "IN", "values": ["SELF_EMPLOYED", "BUSINESS_OWNER"] }
2291
2337
  condition Json?
2292
2338
 
2339
+ // =========================================================================
2340
+ // MULTI-PARTY REVIEW CONFIGURATION (for UPLOAD steps)
2341
+ // =========================================================================
2342
+ // Defines which parties must review documents uploaded in this step
2343
+ // Format: [{ party: "INTERNAL", required: true }, { party: "BANK", required: true }]
2344
+ // If null, defaults to single internal review (backward compatible)
2345
+ reviewRequirements Json?
2346
+
2347
+ // Review order: 'SEQUENTIAL' = parties review in order, 'PARALLEL' = all review simultaneously
2348
+ reviewOrder String? // 'SEQUENTIAL' | 'PARALLEL'
2349
+
2293
2350
  // Assignment
2294
2351
  assigneeId String?
2295
2352
  assignee User? @relation("DocumentationStepAssignee", fields: [assigneeId], references: [id])
@@ -2465,18 +2522,83 @@ model ApplicationDocument {
2465
2522
 
2466
2523
  status DocumentStatus @default(PENDING)
2467
2524
 
2525
+ // Document versioning (for re-uploads after changes requested)
2526
+ version Int @default(1)
2527
+ replacesDocumentId String? // If this is a revision, points to original
2528
+ replacesDocument ApplicationDocument? @relation("DocumentRevisions", fields: [replacesDocumentId], references: [id])
2529
+ revisions ApplicationDocument[] @relation("DocumentRevisions")
2530
+
2468
2531
  createdAt DateTime @default(now())
2469
2532
  updatedAt DateTime @updatedAt
2470
2533
 
2534
+ // Multi-party reviews for this document
2535
+ reviews DocumentReview[]
2536
+
2471
2537
  @@index([tenantId])
2472
2538
  @@index([applicationId])
2473
2539
  @@index([phaseId])
2474
2540
  @@index([stepId])
2475
2541
  @@index([type])
2476
2542
  @@index([status])
2543
+ @@index([replacesDocumentId])
2477
2544
  @@map("application_documents")
2478
2545
  }
2479
2546
 
2547
+ // =============================================================================
2548
+ // DOCUMENT REVIEW - Multi-party review tracking
2549
+ // =============================================================================
2550
+ // Tracks reviews by different organizations for the same document
2551
+ // Enables internal approval -> external approval workflows
2552
+ // =============================================================================
2553
+
2554
+ model DocumentReview {
2555
+ id String @id @default(cuid())
2556
+ tenantId String
2557
+ tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
2558
+ documentId String
2559
+ document ApplicationDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
2560
+
2561
+ // Who is reviewing
2562
+ reviewParty ReviewParty // INTERNAL, BANK, DEVELOPER, LEGAL, etc.
2563
+ organizationId String? // External organization ID if applicable
2564
+ reviewerId String? // User who performed the review
2565
+ reviewer User? @relation("DocumentReviewer", fields: [reviewerId], references: [id])
2566
+ reviewerName String? // Denormalized for audit trail
2567
+
2568
+ // Review state
2569
+ decision ReviewDecision @default(PENDING)
2570
+ comments String? @db.Text // General comments
2571
+
2572
+ // Structured concerns for change requests
2573
+ // Format: [{ field: "income_period", issue: "Missing months April-June" }]
2574
+ concerns Json?
2575
+
2576
+ // Review timing
2577
+ requestedAt DateTime @default(now())
2578
+ dueAt DateTime? // Optional deadline for this party
2579
+ reviewedAt DateTime? // When decision was made
2580
+
2581
+ // Review sequence (which order should parties review?)
2582
+ reviewOrder Int @default(0) // 0 = parallel, 1/2/3 = sequential
2583
+
2584
+ // For review chains (re-reviews after changes)
2585
+ parentReviewId String? // If this is a re-review after changes
2586
+ parentReview DocumentReview? @relation("ReviewChain", fields: [parentReviewId], references: [id])
2587
+ childReviews DocumentReview[] @relation("ReviewChain")
2588
+
2589
+ createdAt DateTime @default(now())
2590
+ updatedAt DateTime @updatedAt
2591
+
2592
+ @@unique([documentId, reviewParty, organizationId]) // One review per party per org per doc
2593
+ @@index([tenantId])
2594
+ @@index([documentId])
2595
+ @@index([reviewParty])
2596
+ @@index([decision])
2597
+ @@index([reviewerId])
2598
+ @@index([parentReviewId])
2599
+ @@map("document_reviews")
2600
+ }
2601
+
2480
2602
  // =============================================================================
2481
2603
  // OFFER LETTERS - Provisional and Final offer documents
2482
2604
  // =============================================================================