@valentine-efagene/qshelter-common 2.0.119 → 2.0.121
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.
- package/dist/generated/client/browser.d.ts +11 -0
- package/dist/generated/client/client.d.ts +11 -0
- package/dist/generated/client/commonInputTypes.d.ts +260 -0
- package/dist/generated/client/enums.d.ts +79 -0
- package/dist/generated/client/enums.js +71 -1
- package/dist/generated/client/internal/class.d.ts +22 -0
- package/dist/generated/client/internal/class.js +2 -2
- package/dist/generated/client/internal/prismaNamespace.d.ts +277 -2
- package/dist/generated/client/internal/prismaNamespace.js +109 -4
- package/dist/generated/client/internal/prismaNamespaceBrowser.d.ts +110 -1
- package/dist/generated/client/internal/prismaNamespaceBrowser.js +109 -4
- package/dist/generated/client/models/DocumentationPhase.d.ts +243 -1
- package/dist/generated/client/models/DocumentationPlanStep.d.ts +241 -1
- package/dist/generated/client/models/DocumentationStep.d.ts +1017 -1
- package/dist/generated/client/models/Organization.d.ts +1930 -0
- package/dist/generated/client/models/Organization.js +1 -0
- package/dist/generated/client/models/OrganizationMember.d.ts +1598 -0
- package/dist/generated/client/models/OrganizationMember.js +1 -0
- package/dist/generated/client/models/QuestionnairePhase.d.ts +165 -0
- package/dist/generated/client/models/QuestionnairePlanQuestion.d.ts +25 -22
- package/dist/generated/client/models/Tenant.d.ts +555 -0
- package/dist/generated/client/models/User.d.ts +776 -0
- package/dist/generated/client/models.d.ts +2 -0
- package/dist/src/middleware/auth-context.d.ts +30 -1
- package/dist/src/middleware/auth-context.js +34 -1
- package/dist/src/prisma/tenant.js +2 -0
- package/dist/src/utils/condition-operators.d.ts +5 -23
- package/dist/src/utils/condition-operators.js +6 -19
- package/dist/src/utils/documentation-enums.d.ts +6 -0
- package/dist/src/utils/documentation-enums.js +6 -0
- package/package.json +1 -1
- package/prisma/migrations/20260115123703_add_organizations/migration.sql +69 -0
- package/prisma/migrations/20260115125519_add_requires_manual_review_to_step/migration.sql +2 -0
- package/prisma/migrations/20260115134106_add_conditional_step_support/migration.sql +11 -0
- package/prisma/schema.prisma +191 -1
|
@@ -4,6 +4,8 @@ export type * from './models/Permission.js';
|
|
|
4
4
|
export type * from './models/RolePermission.js';
|
|
5
5
|
export type * from './models/UserRole.js';
|
|
6
6
|
export type * from './models/TenantMembership.js';
|
|
7
|
+
export type * from './models/Organization.js';
|
|
8
|
+
export type * from './models/OrganizationMember.js';
|
|
7
9
|
export type * from './models/Tenant.js';
|
|
8
10
|
export type * from './models/ApiKey.js';
|
|
9
11
|
export type * from './models/RefreshToken.js';
|
|
@@ -71,7 +71,7 @@ export declare function getAuthContext(req: Request): AuthContext;
|
|
|
71
71
|
* .send({ name: 'John' });
|
|
72
72
|
* ```
|
|
73
73
|
*
|
|
74
|
-
* @deprecated Use Authorization header directly with JWT token.
|
|
74
|
+
* @deprecated Use `mockAuthHeaders` for E2E tests or pass Authorization header directly with JWT token.
|
|
75
75
|
* This helper is kept for backward compatibility.
|
|
76
76
|
*/
|
|
77
77
|
export declare function authHeaders(userId: string, tenantId: string, extras?: {
|
|
@@ -79,6 +79,29 @@ export declare function authHeaders(userId: string, tenantId: string, extras?: {
|
|
|
79
79
|
roles?: string[];
|
|
80
80
|
token?: string;
|
|
81
81
|
}): Record<string, string>;
|
|
82
|
+
/**
|
|
83
|
+
* Generate mock authorization headers for E2E tests.
|
|
84
|
+
*
|
|
85
|
+
* This is the preferred helper for E2E tests that bypass JWT authentication
|
|
86
|
+
* and instead use mock headers that simulate what the Lambda authorizer would set.
|
|
87
|
+
*
|
|
88
|
+
* @param userId - The user ID to set in the mock headers
|
|
89
|
+
* @param tenantId - The tenant ID to set in the mock headers
|
|
90
|
+
* @param options - Optional additional header values
|
|
91
|
+
* @returns Headers object to pass to supertest .set()
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const response = await api
|
|
96
|
+
* .post('/applications')
|
|
97
|
+
* .set(mockAuthHeaders(userId, tenantId, { roles: [ROLES.CUSTOMER] }))
|
|
98
|
+
* .send({ ... });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function mockAuthHeaders(userId: string, tenantId: string, options?: {
|
|
102
|
+
email?: string;
|
|
103
|
+
roles?: string[];
|
|
104
|
+
}): Record<string, string>;
|
|
82
105
|
/**
|
|
83
106
|
* Standard role names used across the platform.
|
|
84
107
|
*/
|
|
@@ -88,6 +111,12 @@ export declare const ROLES: {
|
|
|
88
111
|
readonly LOAN_OFFICER: "LOAN_OFFICER";
|
|
89
112
|
readonly CUSTOMER: "CUSTOMER";
|
|
90
113
|
readonly VIEWER: "VIEWER";
|
|
114
|
+
/** Property developers who list properties and upload sales offer letters */
|
|
115
|
+
readonly DEVELOPER: "DEVELOPER";
|
|
116
|
+
/** Bank/financial institution representatives who upload preapproval and mortgage offer letters */
|
|
117
|
+
readonly LENDER: "LENDER";
|
|
118
|
+
/** Legal officers who upload final offer letters and handle legal documentation */
|
|
119
|
+
readonly LEGAL: "LEGAL";
|
|
91
120
|
};
|
|
92
121
|
export type RoleName = (typeof ROLES)[keyof typeof ROLES];
|
|
93
122
|
/**
|
|
@@ -128,7 +128,7 @@ export function getAuthContext(req) {
|
|
|
128
128
|
* .send({ name: 'John' });
|
|
129
129
|
* ```
|
|
130
130
|
*
|
|
131
|
-
* @deprecated Use Authorization header directly with JWT token.
|
|
131
|
+
* @deprecated Use `mockAuthHeaders` for E2E tests or pass Authorization header directly with JWT token.
|
|
132
132
|
* This helper is kept for backward compatibility.
|
|
133
133
|
*/
|
|
134
134
|
export function authHeaders(userId, tenantId, extras) {
|
|
@@ -145,6 +145,33 @@ export function authHeaders(userId, tenantId, extras) {
|
|
|
145
145
|
...(extras?.roles && { 'x-authorizer-roles': JSON.stringify(extras.roles) }),
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate mock authorization headers for E2E tests.
|
|
150
|
+
*
|
|
151
|
+
* This is the preferred helper for E2E tests that bypass JWT authentication
|
|
152
|
+
* and instead use mock headers that simulate what the Lambda authorizer would set.
|
|
153
|
+
*
|
|
154
|
+
* @param userId - The user ID to set in the mock headers
|
|
155
|
+
* @param tenantId - The tenant ID to set in the mock headers
|
|
156
|
+
* @param options - Optional additional header values
|
|
157
|
+
* @returns Headers object to pass to supertest .set()
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const response = await api
|
|
162
|
+
* .post('/applications')
|
|
163
|
+
* .set(mockAuthHeaders(userId, tenantId, { roles: [ROLES.CUSTOMER] }))
|
|
164
|
+
* .send({ ... });
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export function mockAuthHeaders(userId, tenantId, options) {
|
|
168
|
+
return {
|
|
169
|
+
'x-authorizer-user-id': userId,
|
|
170
|
+
'x-authorizer-tenant-id': tenantId,
|
|
171
|
+
...(options?.email && { 'x-authorizer-email': options.email }),
|
|
172
|
+
...(options?.roles && { 'x-authorizer-roles': JSON.stringify(options.roles) }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
148
175
|
/**
|
|
149
176
|
* Standard role names used across the platform.
|
|
150
177
|
*/
|
|
@@ -154,6 +181,12 @@ export const ROLES = {
|
|
|
154
181
|
LOAN_OFFICER: 'LOAN_OFFICER',
|
|
155
182
|
CUSTOMER: 'CUSTOMER',
|
|
156
183
|
VIEWER: 'VIEWER',
|
|
184
|
+
/** Property developers who list properties and upload sales offer letters */
|
|
185
|
+
DEVELOPER: 'DEVELOPER',
|
|
186
|
+
/** Bank/financial institution representatives who upload preapproval and mortgage offer letters */
|
|
187
|
+
LENDER: 'LENDER',
|
|
188
|
+
/** Legal officers who upload final offer letters and handle legal documentation */
|
|
189
|
+
LEGAL: 'LEGAL',
|
|
157
190
|
};
|
|
158
191
|
/**
|
|
159
192
|
* Roles that have admin privileges (can manage resources).
|
|
@@ -20,6 +20,8 @@ const GLOBAL_MODELS = [
|
|
|
20
20
|
// User preferences/devices (linked to user, not tenant)
|
|
21
21
|
"emailPreference",
|
|
22
22
|
"deviceEndpoint",
|
|
23
|
+
// Organization members (tenant access via organization.tenantId)
|
|
24
|
+
"organizationMember",
|
|
23
25
|
];
|
|
24
26
|
/**
|
|
25
27
|
* Models that have OPTIONAL tenant scoping (nullable tenantId).
|
|
@@ -2,30 +2,12 @@
|
|
|
2
2
|
* Condition operators for evaluating step conditions against questionnaire answers.
|
|
3
3
|
* Used in DocumentationPlanStep.condition to conditionally require documents
|
|
4
4
|
* based on prequalification answers.
|
|
5
|
+
*
|
|
6
|
+
* ConditionOperator is a Prisma enum - this file provides TypeScript interfaces
|
|
7
|
+
* for building condition objects.
|
|
5
8
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export declare const CONDITION_OPERATORS: {
|
|
10
|
-
/** Value equals the specified value */
|
|
11
|
-
readonly EQUALS: "EQUALS";
|
|
12
|
-
/** Value does not equal the specified value */
|
|
13
|
-
readonly NOT_EQUALS: "NOT_EQUALS";
|
|
14
|
-
/** Value is in the specified array of values */
|
|
15
|
-
readonly IN: "IN";
|
|
16
|
-
/** Value is not in the specified array of values */
|
|
17
|
-
readonly NOT_IN: "NOT_IN";
|
|
18
|
-
/** Numeric value is greater than the specified value */
|
|
19
|
-
readonly GREATER_THAN: "GREATER_THAN";
|
|
20
|
-
/** Numeric value is less than the specified value */
|
|
21
|
-
readonly LESS_THAN: "LESS_THAN";
|
|
22
|
-
/** Value exists (is not null/undefined) */
|
|
23
|
-
readonly EXISTS: "EXISTS";
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Type representing a valid condition operator.
|
|
27
|
-
*/
|
|
28
|
-
export type ConditionOperator = keyof typeof CONDITION_OPERATORS;
|
|
9
|
+
import { ConditionOperator } from '../../generated/client/enums';
|
|
10
|
+
export { ConditionOperator };
|
|
29
11
|
/**
|
|
30
12
|
* Interface for a simple condition that checks a single question answer.
|
|
31
13
|
*/
|
|
@@ -2,23 +2,10 @@
|
|
|
2
2
|
* Condition operators for evaluating step conditions against questionnaire answers.
|
|
3
3
|
* Used in DocumentationPlanStep.condition to conditionally require documents
|
|
4
4
|
* based on prequalification answers.
|
|
5
|
+
*
|
|
6
|
+
* ConditionOperator is a Prisma enum - this file provides TypeScript interfaces
|
|
7
|
+
* for building condition objects.
|
|
5
8
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const CONDITION_OPERATORS = {
|
|
10
|
-
/** Value equals the specified value */
|
|
11
|
-
EQUALS: 'EQUALS',
|
|
12
|
-
/** Value does not equal the specified value */
|
|
13
|
-
NOT_EQUALS: 'NOT_EQUALS',
|
|
14
|
-
/** Value is in the specified array of values */
|
|
15
|
-
IN: 'IN',
|
|
16
|
-
/** Value is not in the specified array of values */
|
|
17
|
-
NOT_IN: 'NOT_IN',
|
|
18
|
-
/** Numeric value is greater than the specified value */
|
|
19
|
-
GREATER_THAN: 'GREATER_THAN',
|
|
20
|
-
/** Numeric value is less than the specified value */
|
|
21
|
-
LESS_THAN: 'LESS_THAN',
|
|
22
|
-
/** Value exists (is not null/undefined) */
|
|
23
|
-
EXISTS: 'EXISTS',
|
|
24
|
-
};
|
|
9
|
+
import { ConditionOperator } from '../../generated/client/enums';
|
|
10
|
+
// Re-export for convenience (the Prisma enum is the source of truth)
|
|
11
|
+
export { ConditionOperator };
|
|
@@ -10,8 +10,14 @@ export declare const UPLOADED_BY: {
|
|
|
10
10
|
readonly CUSTOMER: "CUSTOMER";
|
|
11
11
|
/** Document is uploaded by an admin/staff member */
|
|
12
12
|
readonly ADMIN: "ADMIN";
|
|
13
|
+
/** Document is uploaded by a lender/bank representative */
|
|
14
|
+
readonly LENDER: "LENDER";
|
|
15
|
+
/** Document is uploaded by the developer */
|
|
16
|
+
readonly DEVELOPER: "DEVELOPER";
|
|
13
17
|
/** Document is uploaded by the system (auto-generated) */
|
|
14
18
|
readonly SYSTEM: "SYSTEM";
|
|
19
|
+
/** Document is uploaded by a legal officer */
|
|
20
|
+
readonly LEGAL: "LEGAL";
|
|
15
21
|
};
|
|
16
22
|
/**
|
|
17
23
|
* Type representing a valid uploadedBy value.
|
|
@@ -10,6 +10,12 @@ export const UPLOADED_BY = {
|
|
|
10
10
|
CUSTOMER: 'CUSTOMER',
|
|
11
11
|
/** Document is uploaded by an admin/staff member */
|
|
12
12
|
ADMIN: 'ADMIN',
|
|
13
|
+
/** Document is uploaded by a lender/bank representative */
|
|
14
|
+
LENDER: 'LENDER',
|
|
15
|
+
/** Document is uploaded by the developer */
|
|
16
|
+
DEVELOPER: 'DEVELOPER',
|
|
13
17
|
/** Document is uploaded by the system (auto-generated) */
|
|
14
18
|
SYSTEM: 'SYSTEM',
|
|
19
|
+
/** Document is uploaded by a legal officer */
|
|
20
|
+
LEGAL: 'LEGAL',
|
|
15
21
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE `organizations` (
|
|
3
|
+
`id` VARCHAR(191) NOT NULL,
|
|
4
|
+
`tenantId` VARCHAR(191) NOT NULL,
|
|
5
|
+
`name` VARCHAR(191) NOT NULL,
|
|
6
|
+
`type` ENUM('BANK', 'DEVELOPER') NOT NULL,
|
|
7
|
+
`status` ENUM('PENDING', 'ACTIVE', 'SUSPENDED', 'INACTIVE') NOT NULL DEFAULT 'PENDING',
|
|
8
|
+
`email` VARCHAR(191) NULL,
|
|
9
|
+
`phone` VARCHAR(191) NULL,
|
|
10
|
+
`address` VARCHAR(191) NULL,
|
|
11
|
+
`city` VARCHAR(191) NULL,
|
|
12
|
+
`state` VARCHAR(191) NULL,
|
|
13
|
+
`country` VARCHAR(191) NULL DEFAULT 'Nigeria',
|
|
14
|
+
`website` VARCHAR(191) NULL,
|
|
15
|
+
`logoUrl` VARCHAR(191) NULL,
|
|
16
|
+
`description` TEXT NULL,
|
|
17
|
+
`bankCode` VARCHAR(191) NULL,
|
|
18
|
+
`bankLicenseNo` VARCHAR(191) NULL,
|
|
19
|
+
`swiftCode` VARCHAR(191) NULL,
|
|
20
|
+
`sortCode` VARCHAR(191) NULL,
|
|
21
|
+
`cacNumber` VARCHAR(191) NULL,
|
|
22
|
+
`cacCertificateUrl` VARCHAR(191) NULL,
|
|
23
|
+
`taxId` VARCHAR(191) NULL,
|
|
24
|
+
`approvedAt` DATETIME(3) NULL,
|
|
25
|
+
`approvedById` VARCHAR(191) NULL,
|
|
26
|
+
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
27
|
+
`updatedAt` DATETIME(3) NOT NULL,
|
|
28
|
+
|
|
29
|
+
INDEX `organizations_tenantId_idx`(`tenantId`),
|
|
30
|
+
INDEX `organizations_type_idx`(`type`),
|
|
31
|
+
INDEX `organizations_status_idx`(`status`),
|
|
32
|
+
UNIQUE INDEX `organizations_tenantId_bankCode_key`(`tenantId`, `bankCode`),
|
|
33
|
+
UNIQUE INDEX `organizations_tenantId_cacNumber_key`(`tenantId`, `cacNumber`),
|
|
34
|
+
PRIMARY KEY (`id`)
|
|
35
|
+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
36
|
+
|
|
37
|
+
-- CreateTable
|
|
38
|
+
CREATE TABLE `organization_members` (
|
|
39
|
+
`id` VARCHAR(191) NOT NULL,
|
|
40
|
+
`organizationId` VARCHAR(191) NOT NULL,
|
|
41
|
+
`userId` VARCHAR(191) NOT NULL,
|
|
42
|
+
`role` ENUM('ADMIN', 'MANAGER', 'OFFICER', 'VIEWER') NOT NULL DEFAULT 'OFFICER',
|
|
43
|
+
`title` VARCHAR(191) NULL,
|
|
44
|
+
`department` VARCHAR(191) NULL,
|
|
45
|
+
`employeeId` VARCHAR(191) NULL,
|
|
46
|
+
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
|
47
|
+
`canApprove` BOOLEAN NOT NULL DEFAULT false,
|
|
48
|
+
`approvalLimit` DECIMAL(65, 30) NULL,
|
|
49
|
+
`invitedAt` DATETIME(3) NULL,
|
|
50
|
+
`acceptedAt` DATETIME(3) NULL,
|
|
51
|
+
`invitedBy` VARCHAR(191) NULL,
|
|
52
|
+
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
53
|
+
`updatedAt` DATETIME(3) NOT NULL,
|
|
54
|
+
|
|
55
|
+
INDEX `organization_members_userId_idx`(`userId`),
|
|
56
|
+
INDEX `organization_members_organizationId_idx`(`organizationId`),
|
|
57
|
+
INDEX `organization_members_role_idx`(`role`),
|
|
58
|
+
UNIQUE INDEX `organization_members_organizationId_userId_key`(`organizationId`, `userId`),
|
|
59
|
+
PRIMARY KEY (`id`)
|
|
60
|
+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
61
|
+
|
|
62
|
+
-- AddForeignKey
|
|
63
|
+
ALTER TABLE `organizations` ADD CONSTRAINT `organizations_tenantId_fkey` FOREIGN KEY (`tenantId`) REFERENCES `tenants`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
64
|
+
|
|
65
|
+
-- AddForeignKey
|
|
66
|
+
ALTER TABLE `organization_members` ADD CONSTRAINT `organization_members_organizationId_fkey` FOREIGN KEY (`organizationId`) REFERENCES `organizations`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
67
|
+
|
|
68
|
+
-- AddForeignKey
|
|
69
|
+
ALTER TABLE `organization_members` ADD CONSTRAINT `organization_members_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- AlterTable
|
|
2
|
+
ALTER TABLE `documentation_phases` ADD COLUMN `sourceQuestionnairePhaseId` VARCHAR(191) NULL;
|
|
3
|
+
|
|
4
|
+
-- AlterTable
|
|
5
|
+
ALTER TABLE `documentation_steps` ADD COLUMN `condition` JSON NULL;
|
|
6
|
+
|
|
7
|
+
-- CreateIndex
|
|
8
|
+
CREATE INDEX `documentation_phases_sourceQuestionnairePhaseId_idx` ON `documentation_phases`(`sourceQuestionnairePhaseId`);
|
|
9
|
+
|
|
10
|
+
-- AddForeignKey
|
|
11
|
+
ALTER TABLE `documentation_phases` ADD CONSTRAINT `documentation_phases_sourceQuestionnairePhaseId_fkey` FOREIGN KEY (`sourceQuestionnairePhaseId`) REFERENCES `questionnaire_phases`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
|
package/prisma/schema.prisma
CHANGED
|
@@ -62,6 +62,18 @@ enum ApplicationStatus {
|
|
|
62
62
|
SUPERSEDED // Another buyer locked the unit - this application was outbid
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/// Triggers that cause application state transitions
|
|
66
|
+
enum ApplicationTrigger {
|
|
67
|
+
SUBMIT // Submit application for review (DRAFT -> PENDING)
|
|
68
|
+
APPROVE // Approve pending application (PENDING -> ACTIVE)
|
|
69
|
+
REJECT // Reject pending application (PENDING -> CANCELLED)
|
|
70
|
+
CANCEL // Cancel application
|
|
71
|
+
COMPLETE // Complete application (all phases done)
|
|
72
|
+
TERMINATE // Terminate active application
|
|
73
|
+
TRANSFER // Transfer to different unit
|
|
74
|
+
SUPERSEDE // Another buyer locked the unit
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
enum TransferRequestStatus {
|
|
66
78
|
PENDING
|
|
67
79
|
APPROVED
|
|
@@ -129,6 +141,20 @@ enum StepStatus {
|
|
|
129
141
|
AWAITING_REVIEW // Submitted, waiting for admin/system review
|
|
130
142
|
}
|
|
131
143
|
|
|
144
|
+
/// Operators for conditional step evaluation
|
|
145
|
+
enum ConditionOperator {
|
|
146
|
+
EQUALS // Field value equals expected value
|
|
147
|
+
NOT_EQUALS // Field value does not equal expected value
|
|
148
|
+
IN // Field value is in a list of values
|
|
149
|
+
NOT_IN // Field value is not in a list of values
|
|
150
|
+
GREATER_THAN // Numeric comparison
|
|
151
|
+
LESS_THAN // Numeric comparison
|
|
152
|
+
GREATER_THAN_OR_EQUAL
|
|
153
|
+
LESS_THAN_OR_EQUAL
|
|
154
|
+
EXISTS // Field has any value (not null/undefined)
|
|
155
|
+
NOT_EXISTS // Field is null/undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
132
158
|
/// When a step event attachment should trigger
|
|
133
159
|
enum StepTrigger {
|
|
134
160
|
ON_COMPLETE // When step is approved/completed
|
|
@@ -169,6 +195,32 @@ enum ApprovalDecision {
|
|
|
169
195
|
REQUEST_CHANGES
|
|
170
196
|
}
|
|
171
197
|
|
|
198
|
+
// =============================================================================
|
|
199
|
+
// ORGANIZATION ENUMS (Banks, Developers, etc.)
|
|
200
|
+
// =============================================================================
|
|
201
|
+
|
|
202
|
+
/// Type of organization on the platform
|
|
203
|
+
enum OrganizationType {
|
|
204
|
+
BANK // Financial institution providing mortgages (e.g., Access Bank, GTBank)
|
|
205
|
+
DEVELOPER // Property developer building and selling properties
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// Status of an organization
|
|
209
|
+
enum OrganizationStatus {
|
|
210
|
+
PENDING // Awaiting approval to operate on platform
|
|
211
|
+
ACTIVE // Fully operational
|
|
212
|
+
SUSPENDED // Temporarily suspended
|
|
213
|
+
INACTIVE // No longer active
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Role of a member within an organization
|
|
217
|
+
enum OrganizationRole {
|
|
218
|
+
ADMIN // Can manage org settings, members, and full operations
|
|
219
|
+
MANAGER // Can approve transactions within limits (checker)
|
|
220
|
+
OFFICER // Regular employee, can initiate actions (maker)
|
|
221
|
+
VIEWER // Read-only access to organization data
|
|
222
|
+
}
|
|
223
|
+
|
|
172
224
|
// =============================================================================
|
|
173
225
|
// CONTRACT TERMINATION / CANCELLATION ENUMS
|
|
174
226
|
// =============================================================================
|
|
@@ -408,6 +460,9 @@ model User {
|
|
|
408
460
|
approvedRefunds ApplicationRefund[] @relation("RefundApprover")
|
|
409
461
|
processedRefunds ApplicationRefund[] @relation("RefundProcessor")
|
|
410
462
|
|
|
463
|
+
// Organization memberships (user can be employee of banks/developers)
|
|
464
|
+
organizationMemberships OrganizationMember[]
|
|
465
|
+
|
|
411
466
|
@@index([email])
|
|
412
467
|
@@index([tenantId])
|
|
413
468
|
@@map("users")
|
|
@@ -510,6 +565,103 @@ model TenantMembership {
|
|
|
510
565
|
@@map("tenant_memberships")
|
|
511
566
|
}
|
|
512
567
|
|
|
568
|
+
// =============================================================================
|
|
569
|
+
// ORGANIZATIONS (Banks, Developers, etc.)
|
|
570
|
+
// =============================================================================
|
|
571
|
+
// Organizations represent external entities that participate in the platform.
|
|
572
|
+
// Banks provide mortgage financing; Developers build and sell properties.
|
|
573
|
+
// Users can be employees (members) of organizations with specific roles.
|
|
574
|
+
// =============================================================================
|
|
575
|
+
|
|
576
|
+
/// Organization: Banks, Developers, and other partner entities
|
|
577
|
+
model Organization {
|
|
578
|
+
id String @id @default(cuid())
|
|
579
|
+
tenantId String
|
|
580
|
+
name String // e.g., "Access Bank PLC", "Lekki Gardens Ltd"
|
|
581
|
+
type OrganizationType // BANK or DEVELOPER
|
|
582
|
+
status OrganizationStatus @default(PENDING)
|
|
583
|
+
|
|
584
|
+
// Common fields
|
|
585
|
+
email String? // Primary contact email
|
|
586
|
+
phone String? // Primary contact phone
|
|
587
|
+
address String?
|
|
588
|
+
city String?
|
|
589
|
+
state String?
|
|
590
|
+
country String? @default("Nigeria")
|
|
591
|
+
website String?
|
|
592
|
+
logoUrl String?
|
|
593
|
+
description String? @db.Text
|
|
594
|
+
|
|
595
|
+
// Bank-specific fields
|
|
596
|
+
bankCode String? // CBN bank code (e.g., "044" for Access Bank)
|
|
597
|
+
bankLicenseNo String? // Banking license number
|
|
598
|
+
swiftCode String? // SWIFT/BIC code for international transfers
|
|
599
|
+
sortCode String? // Sort code for local transfers
|
|
600
|
+
|
|
601
|
+
// Developer-specific fields
|
|
602
|
+
cacNumber String? // CAC registration number
|
|
603
|
+
cacCertificateUrl String? // URL to CAC certificate document
|
|
604
|
+
taxId String? // Tax Identification Number
|
|
605
|
+
|
|
606
|
+
// Approval workflow
|
|
607
|
+
approvedAt DateTime?
|
|
608
|
+
approvedById String?
|
|
609
|
+
|
|
610
|
+
createdAt DateTime @default(now())
|
|
611
|
+
updatedAt DateTime @updatedAt
|
|
612
|
+
|
|
613
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
614
|
+
members OrganizationMember[]
|
|
615
|
+
|
|
616
|
+
// Properties developed by this organization (for DEVELOPERs)
|
|
617
|
+
// developedProperties Property[] @relation("PropertyDeveloper")
|
|
618
|
+
|
|
619
|
+
@@unique([tenantId, bankCode]) // Bank codes unique within tenant
|
|
620
|
+
@@unique([tenantId, cacNumber]) // CAC numbers unique within tenant
|
|
621
|
+
@@index([tenantId])
|
|
622
|
+
@@index([type])
|
|
623
|
+
@@index([status])
|
|
624
|
+
@@map("organizations")
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// OrganizationMember: Links users to organizations with roles and permissions
|
|
628
|
+
/// Supports maker-checker workflows via canApprove and approvalLimit
|
|
629
|
+
model OrganizationMember {
|
|
630
|
+
id String @id @default(cuid())
|
|
631
|
+
organizationId String
|
|
632
|
+
userId String
|
|
633
|
+
role OrganizationRole @default(OFFICER)
|
|
634
|
+
|
|
635
|
+
// Employee details
|
|
636
|
+
title String? // Job title (e.g., "Loan Officer", "Project Manager")
|
|
637
|
+
department String? // Department within organization
|
|
638
|
+
employeeId String? // Internal employee ID
|
|
639
|
+
|
|
640
|
+
// Status
|
|
641
|
+
isActive Boolean @default(true)
|
|
642
|
+
|
|
643
|
+
// Maker-Checker workflow permissions
|
|
644
|
+
canApprove Boolean @default(false) // Can this member approve transactions?
|
|
645
|
+
approvalLimit Decimal? // Maximum amount this member can approve (null = unlimited)
|
|
646
|
+
|
|
647
|
+
// Invitation/onboarding tracking
|
|
648
|
+
invitedAt DateTime?
|
|
649
|
+
acceptedAt DateTime?
|
|
650
|
+
invitedBy String? // User ID who invited this member
|
|
651
|
+
|
|
652
|
+
createdAt DateTime @default(now())
|
|
653
|
+
updatedAt DateTime @updatedAt
|
|
654
|
+
|
|
655
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
656
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
657
|
+
|
|
658
|
+
@@unique([organizationId, userId]) // User can only be member once per org
|
|
659
|
+
@@index([userId])
|
|
660
|
+
@@index([organizationId])
|
|
661
|
+
@@index([role])
|
|
662
|
+
@@map("organization_members")
|
|
663
|
+
}
|
|
664
|
+
|
|
513
665
|
model Tenant {
|
|
514
666
|
id String @id @default(cuid())
|
|
515
667
|
name String
|
|
@@ -596,6 +748,9 @@ model Tenant {
|
|
|
596
748
|
workflowBlockers WorkflowBlocker[]
|
|
597
749
|
questionnairePlans QuestionnairePlan[]
|
|
598
750
|
|
|
751
|
+
// Organizations (Banks, Developers) operating on this tenant
|
|
752
|
+
organizations Organization[]
|
|
753
|
+
|
|
599
754
|
@@index([subdomain])
|
|
600
755
|
@@map("tenants")
|
|
601
756
|
}
|
|
@@ -1209,7 +1364,7 @@ model QuestionnairePlanQuestion {
|
|
|
1209
1364
|
showIf Json?
|
|
1210
1365
|
|
|
1211
1366
|
// Metadata for grouping
|
|
1212
|
-
category
|
|
1367
|
+
category QuestionCategory? // Group questions by topic
|
|
1213
1368
|
|
|
1214
1369
|
createdAt DateTime @default(now())
|
|
1215
1370
|
updatedAt DateTime @updatedAt
|
|
@@ -1568,6 +1723,23 @@ enum QuestionnaireCategory {
|
|
|
1568
1723
|
CUSTOM
|
|
1569
1724
|
}
|
|
1570
1725
|
|
|
1726
|
+
/// Category for individual questions within a questionnaire
|
|
1727
|
+
/// Used to group questions by topic for UI/UX and scoring
|
|
1728
|
+
enum QuestionCategory {
|
|
1729
|
+
ELIGIBILITY // Age, citizenship, legal status
|
|
1730
|
+
EMPLOYMENT // Employment status, employer details
|
|
1731
|
+
INCOME // Salary, bonuses, other income
|
|
1732
|
+
AFFORDABILITY // Income vs expenses, debt-to-income
|
|
1733
|
+
EXPENSES // Monthly expenses, existing debts
|
|
1734
|
+
APPLICATION_TYPE // Type of mortgage/purchase (single, joint)
|
|
1735
|
+
PERSONAL // Marital status, dependents, contact info
|
|
1736
|
+
PREFERENCES // Preferred terms, property type preferences
|
|
1737
|
+
PROPERTY // Property-related questions
|
|
1738
|
+
CREDIT // Credit history, existing loans
|
|
1739
|
+
ASSETS // Savings, investments, other assets
|
|
1740
|
+
CUSTOM // Custom/uncategorized questions
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1571
1743
|
// Questionnaire field template within a QUESTIONNAIRE phase
|
|
1572
1744
|
model PaymentMethodPhaseField {
|
|
1573
1745
|
id String @id @default(cuid())
|
|
@@ -1867,6 +2039,9 @@ model QuestionnairePhase {
|
|
|
1867
2039
|
// Child records
|
|
1868
2040
|
fields QuestionnaireField[]
|
|
1869
2041
|
|
|
2042
|
+
// Back-relation: Documentation phases that use this questionnaire's answers for conditions
|
|
2043
|
+
dependentDocumentationPhases DocumentationPhase[] @relation("SourceQuestionnaire")
|
|
2044
|
+
|
|
1870
2045
|
@@index([tenantId])
|
|
1871
2046
|
@@index([phaseId])
|
|
1872
2047
|
@@index([questionnairePlanId])
|
|
@@ -1890,6 +2065,11 @@ model DocumentationPhase {
|
|
|
1890
2065
|
documentationPlanId String?
|
|
1891
2066
|
documentationPlan DocumentationPlan? @relation(fields: [documentationPlanId], references: [id])
|
|
1892
2067
|
|
|
2068
|
+
// Source questionnaire for conditional step evaluation
|
|
2069
|
+
// Links to the questionnaire phase whose answers determine which steps apply
|
|
2070
|
+
sourceQuestionnairePhaseId String?
|
|
2071
|
+
sourceQuestionnairePhase QuestionnairePhase? @relation("SourceQuestionnaire", fields: [sourceQuestionnairePhaseId], references: [id])
|
|
2072
|
+
|
|
1893
2073
|
// Current step pointer for UX and orchestration
|
|
1894
2074
|
currentStepId String?
|
|
1895
2075
|
currentStep DocumentationStep? @relation("CurrentStep", fields: [currentStepId], references: [id])
|
|
@@ -1917,6 +2097,7 @@ model DocumentationPhase {
|
|
|
1917
2097
|
@@index([tenantId])
|
|
1918
2098
|
@@index([phaseId])
|
|
1919
2099
|
@@index([currentStepId])
|
|
2100
|
+
@@index([sourceQuestionnairePhaseId])
|
|
1920
2101
|
@@map("documentation_phases")
|
|
1921
2102
|
}
|
|
1922
2103
|
|
|
@@ -2096,6 +2277,15 @@ model DocumentationStep {
|
|
|
2096
2277
|
// Configuration metadata (for GENERATE_DOCUMENT steps, etc.)
|
|
2097
2278
|
metadata Json?
|
|
2098
2279
|
|
|
2280
|
+
// Document validation rules (copied from plan for UPLOAD steps)
|
|
2281
|
+
requiresManualReview Boolean @default(false)
|
|
2282
|
+
|
|
2283
|
+
// Conditional step logic (copied from plan)
|
|
2284
|
+
// When set, this step is only applicable if the condition evaluates to true
|
|
2285
|
+
// Format: { "questionKey": "mortgage_type", "operator": "EQUALS", "value": "JOINT" }
|
|
2286
|
+
// Or: { "questionKey": "employment_status", "operator": "IN", "values": ["SELF_EMPLOYED", "BUSINESS_OWNER"] }
|
|
2287
|
+
condition Json?
|
|
2288
|
+
|
|
2099
2289
|
// Assignment
|
|
2100
2290
|
assigneeId String?
|
|
2101
2291
|
assignee User? @relation("DocumentationStepAssignee", fields: [assigneeId], references: [id])
|