@valentine-efagene/qshelter-common 2.0.83 → 2.0.86

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.
@@ -1,5 +1,7 @@
1
1
  export * from './notifications/notification-enums';
2
2
  export * from './notifications/notification-event';
3
3
  export * from './notifications/event-publisher';
4
+ export * from './payments/payment-event';
5
+ export * from './payments/payment-publisher';
4
6
  export * from './bus/event-bus.types';
5
7
  export * from './bus/event-bus.service';
@@ -2,6 +2,9 @@
2
2
  export * from './notifications/notification-enums';
3
3
  export * from './notifications/notification-event';
4
4
  export * from './notifications/event-publisher';
5
+ // Payment events and publisher (SNS-based)
6
+ export * from './payments/payment-event';
7
+ export * from './payments/payment-publisher';
5
8
  // Event bus (multi-transport delivery)
6
9
  export * from './bus/event-bus.types';
7
10
  export * from './bus/event-bus.service';
@@ -0,0 +1,2 @@
1
+ export * from './payment-event';
2
+ export * from './payment-publisher';
@@ -0,0 +1,2 @@
1
+ export * from './payment-event';
2
+ export * from './payment-publisher';
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Payment event types for the payment service queue
3
+ * These are internal service events, not user-facing notifications
4
+ */
5
+ export declare enum PaymentEventType {
6
+ WALLET_CREDITED = "wallet.credited",
7
+ WALLET_DEBITED = "wallet.debited",
8
+ WALLET_CREATED = "wallet.created",
9
+ ALLOCATE_TO_INSTALLMENTS = "payment.allocate_to_installments",
10
+ PROCESS_INSTALLMENT_PAYMENT = "payment.process_installment",
11
+ REFUND_PAYMENT = "payment.refund",
12
+ VIRTUAL_ACCOUNT_FUNDED = "virtualaccount.funded"
13
+ }
14
+ /**
15
+ * Metadata for payment events
16
+ */
17
+ export interface PaymentEventMeta {
18
+ /** Service that published the event */
19
+ source: string;
20
+ /** ISO timestamp of when event was created */
21
+ timestamp: string;
22
+ /** Correlation ID for distributed tracing */
23
+ correlationId?: string;
24
+ /** User ID */
25
+ userId?: string;
26
+ /** Tenant ID */
27
+ tenantId?: string;
28
+ }
29
+ /**
30
+ * Base payment event structure
31
+ */
32
+ export interface PaymentEvent<T = Record<string, unknown>> {
33
+ type: PaymentEventType;
34
+ payload: T;
35
+ meta: PaymentEventMeta;
36
+ }
37
+ /**
38
+ * Payload when wallet is credited
39
+ */
40
+ export interface WalletCreditedPayload {
41
+ walletId: string;
42
+ userId: string;
43
+ transactionId: string;
44
+ amount: number;
45
+ currency: string;
46
+ newBalance: number;
47
+ reference: string;
48
+ source: 'virtual_account' | 'manual' | 'refund';
49
+ }
50
+ /**
51
+ * Payload for allocating funds to installments
52
+ */
53
+ export interface AllocateToInstallmentsPayload {
54
+ userId: string;
55
+ walletId: string;
56
+ /** Optional: limit to specific contract */
57
+ contractId?: string;
58
+ /** Optional: limit to specific amount */
59
+ maxAmount?: number;
60
+ }
61
+ /**
62
+ * Payload for processing an installment payment
63
+ */
64
+ export interface ProcessInstallmentPaymentPayload {
65
+ installmentId: string;
66
+ amount: number;
67
+ walletId: string;
68
+ userId: string;
69
+ reference: string;
70
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Payment event types for the payment service queue
3
+ * These are internal service events, not user-facing notifications
4
+ */
5
+ export var PaymentEventType;
6
+ (function (PaymentEventType) {
7
+ // Wallet events (published by payment service)
8
+ PaymentEventType["WALLET_CREDITED"] = "wallet.credited";
9
+ PaymentEventType["WALLET_DEBITED"] = "wallet.debited";
10
+ PaymentEventType["WALLET_CREATED"] = "wallet.created";
11
+ // Allocation commands (published by mortgage service or after wallet credit)
12
+ PaymentEventType["ALLOCATE_TO_INSTALLMENTS"] = "payment.allocate_to_installments";
13
+ // Payment commands (published by mortgage service)
14
+ PaymentEventType["PROCESS_INSTALLMENT_PAYMENT"] = "payment.process_installment";
15
+ PaymentEventType["REFUND_PAYMENT"] = "payment.refund";
16
+ // Virtual account events
17
+ PaymentEventType["VIRTUAL_ACCOUNT_FUNDED"] = "virtualaccount.funded";
18
+ })(PaymentEventType || (PaymentEventType = {}));
@@ -0,0 +1,46 @@
1
+ import { PaymentEventType, PaymentEventMeta } from './payment-event';
2
+ /**
3
+ * Configuration for the payment event publisher
4
+ */
5
+ interface PaymentPublisherConfig {
6
+ region?: string;
7
+ endpoint?: string;
8
+ topicArn?: string;
9
+ }
10
+ /**
11
+ * Payment Event Publisher for sending payment events to SNS
12
+ * Used by payment-service and mortgage-service to communicate
13
+ */
14
+ export declare class PaymentEventPublisher {
15
+ private readonly snsClient;
16
+ private readonly topicArn;
17
+ private readonly serviceName;
18
+ constructor(serviceName: string, config?: PaymentPublisherConfig);
19
+ /**
20
+ * Publish a payment event to SNS
21
+ */
22
+ publish<T>(type: PaymentEventType, payload: T, meta?: Partial<PaymentEventMeta>): Promise<string>;
23
+ /**
24
+ * Publish wallet credited event
25
+ */
26
+ publishWalletCredited(payload: {
27
+ walletId: string;
28
+ userId: string;
29
+ transactionId: string;
30
+ amount: number;
31
+ currency: string;
32
+ newBalance: number;
33
+ reference: string;
34
+ source: 'virtual_account' | 'manual' | 'refund';
35
+ }, meta?: Partial<PaymentEventMeta>): Promise<string>;
36
+ /**
37
+ * Publish allocate to installments command
38
+ */
39
+ publishAllocateToInstallments(payload: {
40
+ userId: string;
41
+ walletId: string;
42
+ contractId?: string;
43
+ maxAmount?: number;
44
+ }, meta?: Partial<PaymentEventMeta>): Promise<string>;
45
+ }
46
+ export {};
@@ -0,0 +1,90 @@
1
+ import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
2
+ import { PaymentEventType, } from './payment-event';
3
+ /**
4
+ * Get SNS client configured for LocalStack or AWS
5
+ */
6
+ function createSNSClient(config) {
7
+ const endpoint = config.endpoint || process.env.LOCALSTACK_ENDPOINT;
8
+ const region = config.region || process.env.AWS_REGION || 'us-east-1';
9
+ const clientConfig = { region };
10
+ // For LocalStack, set custom endpoint
11
+ if (endpoint) {
12
+ clientConfig.endpoint = endpoint;
13
+ }
14
+ return new SNSClient(clientConfig);
15
+ }
16
+ /**
17
+ * Payment Event Publisher for sending payment events to SNS
18
+ * Used by payment-service and mortgage-service to communicate
19
+ */
20
+ export class PaymentEventPublisher {
21
+ snsClient;
22
+ topicArn;
23
+ serviceName;
24
+ constructor(serviceName, config) {
25
+ this.serviceName = serviceName;
26
+ this.snsClient = createSNSClient(config || {});
27
+ // Topic ARN can be passed directly or constructed from env vars
28
+ const stage = process.env.STAGE || process.env.NODE_ENV || 'test';
29
+ const region = config?.region || process.env.AWS_REGION || 'us-east-1';
30
+ const accountId = process.env.AWS_ACCOUNT_ID || '000000000000';
31
+ this.topicArn = config?.topicArn ||
32
+ process.env.PAYMENTS_TOPIC_ARN ||
33
+ `arn:aws:sns:${region}:${accountId}:qshelter-${stage}-payments`;
34
+ }
35
+ /**
36
+ * Publish a payment event to SNS
37
+ */
38
+ async publish(type, payload, meta) {
39
+ const event = {
40
+ type,
41
+ payload,
42
+ meta: {
43
+ source: this.serviceName,
44
+ timestamp: new Date().toISOString(),
45
+ correlationId: meta?.correlationId || crypto.randomUUID(),
46
+ userId: meta?.userId,
47
+ tenantId: meta?.tenantId,
48
+ },
49
+ };
50
+ const command = new PublishCommand({
51
+ TopicArn: this.topicArn,
52
+ Message: JSON.stringify(event),
53
+ MessageAttributes: {
54
+ eventType: {
55
+ DataType: 'String',
56
+ StringValue: type,
57
+ },
58
+ source: {
59
+ DataType: 'String',
60
+ StringValue: this.serviceName,
61
+ },
62
+ },
63
+ });
64
+ const result = await this.snsClient.send(command);
65
+ console.log(`[PaymentEventPublisher] Published ${type} event to SNS`, {
66
+ topicArn: this.topicArn,
67
+ messageId: result.MessageId,
68
+ correlationId: event.meta.correlationId,
69
+ });
70
+ return result.MessageId || '';
71
+ }
72
+ /**
73
+ * Publish wallet credited event
74
+ */
75
+ async publishWalletCredited(payload, meta) {
76
+ return this.publish(PaymentEventType.WALLET_CREDITED, payload, {
77
+ ...meta,
78
+ userId: payload.userId,
79
+ });
80
+ }
81
+ /**
82
+ * Publish allocate to installments command
83
+ */
84
+ async publishAllocateToInstallments(payload, meta) {
85
+ return this.publish(PaymentEventType.ALLOCATE_TO_INSTALLMENTS, payload, {
86
+ ...meta,
87
+ userId: payload.userId,
88
+ });
89
+ }
90
+ }
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@valentine-efagene/qshelter-common",
3
- "version": "2.0.83",
3
+ "version": "2.0.86",
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",
7
7
  "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc --watch",
11
+ "generate:prisma": "prisma generate && node scripts/generate-models-index.mjs",
12
+ "postgenerate": "node scripts/generate-models-index.mjs",
13
+ "migrate:dev": "prisma migrate dev",
14
+ "patch": "npm version patch && npm run build && npm publish --access public",
15
+ "prepublishOnly": "npm run build",
16
+ "publish:public": "npm publish --access public"
17
+ },
8
18
  "keywords": [
9
19
  "qshelter",
10
20
  "common",
@@ -47,14 +57,5 @@
47
57
  "@types/node": "^25.0.3",
48
58
  "typescript": "^5.7.3",
49
59
  "zod": "^4.0.0"
50
- },
51
- "scripts": {
52
- "build": "tsc",
53
- "dev": "tsc --watch",
54
- "generate:prisma": "prisma generate && node scripts/generate-models-index.mjs",
55
- "postgenerate": "node scripts/generate-models-index.mjs",
56
- "migrate:dev": "prisma migrate dev",
57
- "patch": "npm version patch && npm run build && npm publish --access public",
58
- "publish:public": "npm publish --access public"
59
60
  }
60
- }
61
+ }
@@ -0,0 +1,343 @@
1
+ /*
2
+ Warnings:
3
+
4
+ - You are about to drop the column `phaseId` on the `contract_installments` table. All the data in the column will be lost.
5
+ - You are about to drop the column `approvedDocumentsCount` on the `contract_phases` table. All the data in the column will be lost.
6
+ - You are about to drop the column `collectFunds` on the `contract_phases` table. All the data in the column will be lost.
7
+ - You are about to drop the column `completedStepsCount` on the `contract_phases` table. All the data in the column will be lost.
8
+ - You are about to drop the column `completionCriterion` on the `contract_phases` table. All the data in the column will be lost.
9
+ - You are about to drop the column `currentStepId` on the `contract_phases` table. All the data in the column will be lost.
10
+ - You are about to drop the column `interestRate` on the `contract_phases` table. All the data in the column will be lost.
11
+ - You are about to drop the column `minimumCompletionPercentage` on the `contract_phases` table. All the data in the column will be lost.
12
+ - You are about to drop the column `paidAmount` on the `contract_phases` table. All the data in the column will be lost.
13
+ - You are about to drop the column `paymentPlanId` on the `contract_phases` table. All the data in the column will be lost.
14
+ - You are about to drop the column `paymentPlanSnapshot` on the `contract_phases` table. All the data in the column will be lost.
15
+ - You are about to drop the column `remainingAmount` on the `contract_phases` table. All the data in the column will be lost.
16
+ - You are about to drop the column `requiredDocumentSnapshot` on the `contract_phases` table. All the data in the column will be lost.
17
+ - You are about to drop the column `requiredDocumentsCount` on the `contract_phases` table. All the data in the column will be lost.
18
+ - You are about to drop the column `stepDefinitionsSnapshot` on the `contract_phases` table. All the data in the column will be lost.
19
+ - You are about to drop the column `totalAmount` on the `contract_phases` table. All the data in the column will be lost.
20
+ - You are about to drop the column `totalStepsCount` on the `contract_phases` table. All the data in the column will be lost.
21
+ - You are about to drop the column `debtToIncomeRatio` on the `contracts` table. All the data in the column will be lost.
22
+ - You are about to drop the column `downPayment` on the `contracts` table. All the data in the column will be lost.
23
+ - You are about to drop the column `downPaymentPaid` on the `contracts` table. All the data in the column will be lost.
24
+ - You are about to drop the column `interestRate` on the `contracts` table. All the data in the column will be lost.
25
+ - You are about to drop the column `monthlyExpenses` on the `contracts` table. All the data in the column will be lost.
26
+ - You are about to drop the column `monthlyIncome` on the `contracts` table. All the data in the column will be lost.
27
+ - You are about to drop the column `periodicPayment` on the `contracts` table. All the data in the column will be lost.
28
+ - You are about to drop the column `preApprovalAnswers` on the `contracts` table. All the data in the column will be lost.
29
+ - You are about to drop the column `principal` on the `contracts` table. All the data in the column will be lost.
30
+ - You are about to drop the column `state` on the `contracts` table. All the data in the column will be lost.
31
+ - You are about to drop the column `termMonths` on the `contracts` table. All the data in the column will be lost.
32
+ - You are about to drop the column `totalInterestPaid` on the `contracts` table. All the data in the column will be lost.
33
+ - You are about to drop the column `totalPaidToDate` on the `contracts` table. All the data in the column will be lost.
34
+ - You are about to drop the column `underwritingScore` on the `contracts` table. All the data in the column will be lost.
35
+ - You are about to drop the column `paymentsMigrated` on the `property_transfer_requests` table. All the data in the column will be lost.
36
+ - You are about to drop the `contract_phase_step_approvals` table. If the table is not empty, all the data it contains will be lost.
37
+ - You are about to drop the `contract_phase_step_documents` table. If the table is not empty, all the data it contains will be lost.
38
+ - You are about to drop the `contract_phase_steps` table. If the table is not empty, all the data it contains will be lost.
39
+ - Added the required column `paymentPhaseId` to the `contract_installments` table without a default value. This is not possible if the table is not empty.
40
+
41
+ */
42
+ -- DropForeignKey
43
+ ALTER TABLE `contract_installments` DROP FOREIGN KEY `contract_installments_phaseId_fkey`;
44
+
45
+ -- DropForeignKey
46
+ ALTER TABLE `contract_phase_step_approvals` DROP FOREIGN KEY `contract_phase_step_approvals_approverId_fkey`;
47
+
48
+ -- DropForeignKey
49
+ ALTER TABLE `contract_phase_step_approvals` DROP FOREIGN KEY `contract_phase_step_approvals_stepId_fkey`;
50
+
51
+ -- DropForeignKey
52
+ ALTER TABLE `contract_phase_step_documents` DROP FOREIGN KEY `contract_phase_step_documents_stepId_fkey`;
53
+
54
+ -- DropForeignKey
55
+ ALTER TABLE `contract_phase_steps` DROP FOREIGN KEY `contract_phase_steps_assigneeId_fkey`;
56
+
57
+ -- DropForeignKey
58
+ ALTER TABLE `contract_phase_steps` DROP FOREIGN KEY `contract_phase_steps_phaseId_fkey`;
59
+
60
+ -- DropForeignKey
61
+ ALTER TABLE `contract_phases` DROP FOREIGN KEY `contract_phases_currentStepId_fkey`;
62
+
63
+ -- DropForeignKey
64
+ ALTER TABLE `contract_phases` DROP FOREIGN KEY `contract_phases_paymentPlanId_fkey`;
65
+
66
+ -- DropIndex
67
+ DROP INDEX `contract_installments_phaseId_idx` ON `contract_installments`;
68
+
69
+ -- DropIndex
70
+ DROP INDEX `contract_phases_currentStepId_idx` ON `contract_phases`;
71
+
72
+ -- DropIndex
73
+ DROP INDEX `contract_phases_paymentPlanId_idx` ON `contract_phases`;
74
+
75
+ -- DropIndex
76
+ DROP INDEX `contracts_state_idx` ON `contracts`;
77
+
78
+ -- AlterTable
79
+ ALTER TABLE `contract_installments` DROP COLUMN `phaseId`,
80
+ ADD COLUMN `paymentPhaseId` VARCHAR(191) NOT NULL;
81
+
82
+ -- AlterTable
83
+ ALTER TABLE `contract_phases` DROP COLUMN `approvedDocumentsCount`,
84
+ DROP COLUMN `collectFunds`,
85
+ DROP COLUMN `completedStepsCount`,
86
+ DROP COLUMN `completionCriterion`,
87
+ DROP COLUMN `currentStepId`,
88
+ DROP COLUMN `interestRate`,
89
+ DROP COLUMN `minimumCompletionPercentage`,
90
+ DROP COLUMN `paidAmount`,
91
+ DROP COLUMN `paymentPlanId`,
92
+ DROP COLUMN `paymentPlanSnapshot`,
93
+ DROP COLUMN `remainingAmount`,
94
+ DROP COLUMN `requiredDocumentSnapshot`,
95
+ DROP COLUMN `requiredDocumentsCount`,
96
+ DROP COLUMN `stepDefinitionsSnapshot`,
97
+ DROP COLUMN `totalAmount`,
98
+ DROP COLUMN `totalStepsCount`,
99
+ MODIFY `phaseCategory` ENUM('QUESTIONNAIRE', 'DOCUMENTATION', 'PAYMENT') NOT NULL,
100
+ MODIFY `phaseType` ENUM('PRE_APPROVAL', 'UNDERWRITING', 'KYC', 'VERIFICATION', 'DOWNPAYMENT', 'MORTGAGE', 'BALLOON', 'CUSTOM') NOT NULL;
101
+
102
+ -- AlterTable
103
+ ALTER TABLE `contracts` DROP COLUMN `debtToIncomeRatio`,
104
+ DROP COLUMN `downPayment`,
105
+ DROP COLUMN `downPaymentPaid`,
106
+ DROP COLUMN `interestRate`,
107
+ DROP COLUMN `monthlyExpenses`,
108
+ DROP COLUMN `monthlyIncome`,
109
+ DROP COLUMN `periodicPayment`,
110
+ DROP COLUMN `preApprovalAnswers`,
111
+ DROP COLUMN `principal`,
112
+ DROP COLUMN `state`,
113
+ DROP COLUMN `termMonths`,
114
+ DROP COLUMN `totalInterestPaid`,
115
+ DROP COLUMN `totalPaidToDate`,
116
+ DROP COLUMN `underwritingScore`;
117
+
118
+ -- AlterTable
119
+ ALTER TABLE `property_payment_method_phases` MODIFY `phaseCategory` ENUM('QUESTIONNAIRE', 'DOCUMENTATION', 'PAYMENT') NOT NULL,
120
+ MODIFY `phaseType` ENUM('PRE_APPROVAL', 'UNDERWRITING', 'KYC', 'VERIFICATION', 'DOWNPAYMENT', 'MORTGAGE', 'BALLOON', 'CUSTOM') NOT NULL;
121
+
122
+ -- AlterTable
123
+ ALTER TABLE `property_transfer_requests` DROP COLUMN `paymentsMigrated`,
124
+ ADD COLUMN `refundTransactionId` VARCHAR(191) NULL,
125
+ ADD COLUMN `refundedAmount` DOUBLE NULL,
126
+ ADD COLUMN `refundedAt` DATETIME(3) NULL;
127
+
128
+ -- DropTable
129
+ DROP TABLE `contract_phase_step_approvals`;
130
+
131
+ -- DropTable
132
+ DROP TABLE `contract_phase_step_documents`;
133
+
134
+ -- DropTable
135
+ DROP TABLE `contract_phase_steps`;
136
+
137
+ -- CreateTable
138
+ CREATE TABLE `payment_method_phase_fields` (
139
+ `id` VARCHAR(191) NOT NULL,
140
+ `phaseId` VARCHAR(191) NOT NULL,
141
+ `name` VARCHAR(191) NOT NULL,
142
+ `label` VARCHAR(191) NOT NULL,
143
+ `description` TEXT NULL,
144
+ `placeholder` VARCHAR(191) NULL,
145
+ `fieldType` ENUM('TEXT', 'TEXTAREA', 'NUMBER', 'CURRENCY', 'EMAIL', 'PHONE', 'DATE', 'SELECT', 'MULTI_SELECT', 'CHECKBOX', 'RADIO', 'FILE') NOT NULL,
146
+ `isRequired` BOOLEAN NOT NULL DEFAULT true,
147
+ `order` INTEGER NOT NULL,
148
+ `validation` JSON NULL,
149
+ `displayCondition` JSON NULL,
150
+ `defaultValue` JSON NULL,
151
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
152
+ `updatedAt` DATETIME(3) NOT NULL,
153
+
154
+ INDEX `payment_method_phase_fields_phaseId_idx`(`phaseId`),
155
+ UNIQUE INDEX `payment_method_phase_fields_phaseId_name_key`(`phaseId`, `name`),
156
+ PRIMARY KEY (`id`)
157
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
158
+
159
+ -- CreateTable
160
+ CREATE TABLE `questionnaire_phases` (
161
+ `id` VARCHAR(191) NOT NULL,
162
+ `phaseId` VARCHAR(191) NOT NULL,
163
+ `completedFieldsCount` INTEGER NOT NULL DEFAULT 0,
164
+ `totalFieldsCount` INTEGER NOT NULL DEFAULT 0,
165
+ `underwritingScore` DOUBLE NULL,
166
+ `debtToIncomeRatio` DOUBLE NULL,
167
+ `underwritingDecision` VARCHAR(191) NULL,
168
+ `underwritingNotes` TEXT NULL,
169
+ `fieldsSnapshot` JSON NULL,
170
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
171
+ `updatedAt` DATETIME(3) NOT NULL,
172
+
173
+ UNIQUE INDEX `questionnaire_phases_phaseId_key`(`phaseId`),
174
+ INDEX `questionnaire_phases_phaseId_idx`(`phaseId`),
175
+ PRIMARY KEY (`id`)
176
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
177
+
178
+ -- CreateTable
179
+ CREATE TABLE `documentation_phases` (
180
+ `id` VARCHAR(191) NOT NULL,
181
+ `phaseId` VARCHAR(191) NOT NULL,
182
+ `currentStepId` VARCHAR(191) NULL,
183
+ `approvedDocumentsCount` INTEGER NOT NULL DEFAULT 0,
184
+ `requiredDocumentsCount` INTEGER NOT NULL DEFAULT 0,
185
+ `completedStepsCount` INTEGER NOT NULL DEFAULT 0,
186
+ `totalStepsCount` INTEGER NOT NULL DEFAULT 0,
187
+ `minimumCompletionPercentage` DOUBLE NULL,
188
+ `completionCriterion` ENUM('DOCUMENT_APPROVALS', 'PAYMENT_AMOUNT', 'STEPS_COMPLETED') NULL,
189
+ `stepDefinitionsSnapshot` JSON NULL,
190
+ `requiredDocumentSnapshot` JSON NULL,
191
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
192
+ `updatedAt` DATETIME(3) NOT NULL,
193
+
194
+ UNIQUE INDEX `documentation_phases_phaseId_key`(`phaseId`),
195
+ INDEX `documentation_phases_phaseId_idx`(`phaseId`),
196
+ INDEX `documentation_phases_currentStepId_idx`(`currentStepId`),
197
+ PRIMARY KEY (`id`)
198
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
199
+
200
+ -- CreateTable
201
+ CREATE TABLE `payment_phases` (
202
+ `id` VARCHAR(191) NOT NULL,
203
+ `phaseId` VARCHAR(191) NOT NULL,
204
+ `paymentPlanId` VARCHAR(191) NULL,
205
+ `totalAmount` DOUBLE NOT NULL,
206
+ `paidAmount` DOUBLE NOT NULL DEFAULT 0,
207
+ `interestRate` DOUBLE NOT NULL DEFAULT 0,
208
+ `collectFunds` BOOLEAN NOT NULL DEFAULT true,
209
+ `minimumCompletionPercentage` DOUBLE NULL,
210
+ `paymentPlanSnapshot` JSON NULL,
211
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
212
+ `updatedAt` DATETIME(3) NOT NULL,
213
+
214
+ UNIQUE INDEX `payment_phases_phaseId_key`(`phaseId`),
215
+ INDEX `payment_phases_phaseId_idx`(`phaseId`),
216
+ INDEX `payment_phases_paymentPlanId_idx`(`paymentPlanId`),
217
+ PRIMARY KEY (`id`)
218
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
219
+
220
+ -- CreateTable
221
+ CREATE TABLE `questionnaire_fields` (
222
+ `id` VARCHAR(191) NOT NULL,
223
+ `questionnairePhaseId` VARCHAR(191) NOT NULL,
224
+ `name` VARCHAR(191) NOT NULL,
225
+ `label` VARCHAR(191) NOT NULL,
226
+ `description` TEXT NULL,
227
+ `placeholder` VARCHAR(191) NULL,
228
+ `fieldType` ENUM('TEXT', 'TEXTAREA', 'NUMBER', 'CURRENCY', 'EMAIL', 'PHONE', 'DATE', 'SELECT', 'MULTI_SELECT', 'CHECKBOX', 'RADIO', 'FILE') NOT NULL,
229
+ `isRequired` BOOLEAN NOT NULL DEFAULT true,
230
+ `order` INTEGER NOT NULL,
231
+ `validation` JSON NULL,
232
+ `displayCondition` JSON NULL,
233
+ `defaultValue` JSON NULL,
234
+ `answer` JSON NULL,
235
+ `isValid` BOOLEAN NOT NULL DEFAULT false,
236
+ `validationErrors` JSON NULL,
237
+ `submittedAt` DATETIME(3) NULL,
238
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
239
+ `updatedAt` DATETIME(3) NOT NULL,
240
+
241
+ INDEX `questionnaire_fields_questionnairePhaseId_idx`(`questionnairePhaseId`),
242
+ UNIQUE INDEX `questionnaire_fields_questionnairePhaseId_name_key`(`questionnairePhaseId`, `name`),
243
+ PRIMARY KEY (`id`)
244
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
245
+
246
+ -- CreateTable
247
+ CREATE TABLE `documentation_steps` (
248
+ `id` VARCHAR(191) NOT NULL,
249
+ `documentationPhaseId` VARCHAR(191) NOT NULL,
250
+ `name` VARCHAR(191) NOT NULL,
251
+ `description` TEXT NULL,
252
+ `stepType` ENUM('UPLOAD', 'REVIEW', 'SIGNATURE', 'APPROVAL', 'EXTERNAL_CHECK', 'WAIT', 'GENERATE_DOCUMENT', 'PRE_APPROVAL', 'UNDERWRITING') NOT NULL,
253
+ `order` INTEGER NOT NULL,
254
+ `status` ENUM('PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED', 'SKIPPED', 'NEEDS_RESUBMISSION', 'ACTION_REQUIRED', 'AWAITING_REVIEW') NOT NULL DEFAULT 'PENDING',
255
+ `actionReason` TEXT NULL,
256
+ `submissionCount` INTEGER NOT NULL DEFAULT 0,
257
+ `lastSubmittedAt` DATETIME(3) NULL,
258
+ `metadata` JSON NULL,
259
+ `assigneeId` VARCHAR(191) NULL,
260
+ `dueDate` DATETIME(3) NULL,
261
+ `completedAt` DATETIME(3) NULL,
262
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
263
+ `updatedAt` DATETIME(3) NOT NULL,
264
+
265
+ INDEX `documentation_steps_documentationPhaseId_idx`(`documentationPhaseId`),
266
+ INDEX `documentation_steps_status_idx`(`status`),
267
+ INDEX `documentation_steps_order_idx`(`order`),
268
+ PRIMARY KEY (`id`)
269
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
270
+
271
+ -- CreateTable
272
+ CREATE TABLE `documentation_step_documents` (
273
+ `id` VARCHAR(191) NOT NULL,
274
+ `stepId` VARCHAR(191) NOT NULL,
275
+ `documentType` VARCHAR(191) NOT NULL,
276
+ `isRequired` BOOLEAN NOT NULL DEFAULT true,
277
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
278
+
279
+ INDEX `documentation_step_documents_stepId_documentType_idx`(`stepId`, `documentType`),
280
+ PRIMARY KEY (`id`)
281
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
282
+
283
+ -- CreateTable
284
+ CREATE TABLE `documentation_step_approvals` (
285
+ `id` VARCHAR(191) NOT NULL,
286
+ `stepId` VARCHAR(191) NOT NULL,
287
+ `approverId` VARCHAR(191) NULL,
288
+ `decision` ENUM('APPROVED', 'REJECTED', 'REQUEST_CHANGES') NOT NULL,
289
+ `comment` TEXT NULL,
290
+ `decidedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
291
+ `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
292
+
293
+ INDEX `documentation_step_approvals_stepId_idx`(`stepId`),
294
+ PRIMARY KEY (`id`)
295
+ ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
296
+
297
+ -- CreateIndex
298
+ CREATE INDEX `contract_installments_paymentPhaseId_idx` ON `contract_installments`(`paymentPhaseId`);
299
+
300
+ -- CreateIndex
301
+ CREATE INDEX `contracts_currentPhaseId_idx` ON `contracts`(`currentPhaseId`);
302
+
303
+ -- AddForeignKey
304
+ ALTER TABLE `payment_method_phase_fields` ADD CONSTRAINT `payment_method_phase_fields_phaseId_fkey` FOREIGN KEY (`phaseId`) REFERENCES `property_payment_method_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
305
+
306
+ -- AddForeignKey
307
+ ALTER TABLE `contracts` ADD CONSTRAINT `contracts_currentPhaseId_fkey` FOREIGN KEY (`currentPhaseId`) REFERENCES `contract_phases`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
308
+
309
+ -- AddForeignKey
310
+ ALTER TABLE `questionnaire_phases` ADD CONSTRAINT `questionnaire_phases_phaseId_fkey` FOREIGN KEY (`phaseId`) REFERENCES `contract_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
311
+
312
+ -- AddForeignKey
313
+ ALTER TABLE `documentation_phases` ADD CONSTRAINT `documentation_phases_phaseId_fkey` FOREIGN KEY (`phaseId`) REFERENCES `contract_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
314
+
315
+ -- AddForeignKey
316
+ ALTER TABLE `documentation_phases` ADD CONSTRAINT `documentation_phases_currentStepId_fkey` FOREIGN KEY (`currentStepId`) REFERENCES `documentation_steps`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
317
+
318
+ -- AddForeignKey
319
+ ALTER TABLE `payment_phases` ADD CONSTRAINT `payment_phases_phaseId_fkey` FOREIGN KEY (`phaseId`) REFERENCES `contract_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
320
+
321
+ -- AddForeignKey
322
+ ALTER TABLE `payment_phases` ADD CONSTRAINT `payment_phases_paymentPlanId_fkey` FOREIGN KEY (`paymentPlanId`) REFERENCES `payment_plans`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
323
+
324
+ -- AddForeignKey
325
+ ALTER TABLE `questionnaire_fields` ADD CONSTRAINT `questionnaire_fields_questionnairePhaseId_fkey` FOREIGN KEY (`questionnairePhaseId`) REFERENCES `questionnaire_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
326
+
327
+ -- AddForeignKey
328
+ ALTER TABLE `documentation_steps` ADD CONSTRAINT `documentation_steps_documentationPhaseId_fkey` FOREIGN KEY (`documentationPhaseId`) REFERENCES `documentation_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
329
+
330
+ -- AddForeignKey
331
+ ALTER TABLE `documentation_steps` ADD CONSTRAINT `documentation_steps_assigneeId_fkey` FOREIGN KEY (`assigneeId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
332
+
333
+ -- AddForeignKey
334
+ ALTER TABLE `documentation_step_documents` ADD CONSTRAINT `documentation_step_documents_stepId_fkey` FOREIGN KEY (`stepId`) REFERENCES `documentation_steps`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
335
+
336
+ -- AddForeignKey
337
+ ALTER TABLE `documentation_step_approvals` ADD CONSTRAINT `documentation_step_approvals_stepId_fkey` FOREIGN KEY (`stepId`) REFERENCES `documentation_steps`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
338
+
339
+ -- AddForeignKey
340
+ ALTER TABLE `documentation_step_approvals` ADD CONSTRAINT `documentation_step_approvals_approverId_fkey` FOREIGN KEY (`approverId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
341
+
342
+ -- AddForeignKey
343
+ ALTER TABLE `contract_installments` ADD CONSTRAINT `contract_installments_paymentPhaseId_fkey` FOREIGN KEY (`paymentPhaseId`) REFERENCES `payment_phases`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
@@ -20,16 +20,26 @@ datasource db {
20
20
  // =============================================================================
21
21
 
22
22
  enum PhaseCategory {
23
- DOCUMENTATION
24
- PAYMENT
23
+ QUESTIONNAIRE // Configurable form fields with validation
24
+ DOCUMENTATION // Document upload and approval workflow
25
+ PAYMENT // Installment-based payment collection
25
26
  }
26
27
 
27
28
  enum PhaseType {
29
+ // QUESTIONNAIRE phases
30
+ PRE_APPROVAL // Eligibility questionnaire (income, employment, etc.)
31
+ UNDERWRITING // System evaluation of eligibility
32
+
33
+ // DOCUMENTATION phases
28
34
  KYC
29
35
  VERIFICATION
36
+
37
+ // PAYMENT phases
30
38
  DOWNPAYMENT
31
39
  MORTGAGE
32
40
  BALLOON
41
+
42
+ // Generic
33
43
  CUSTOM
34
44
  }
35
45
 
@@ -898,9 +908,9 @@ model PaymentPlan {
898
908
  updatedAt DateTime @updatedAt
899
909
 
900
910
  // Used by property payment method phases (templates)
901
- methodPhases PropertyPaymentMethodPhase[]
902
- // Used by instantiated contract phases
903
- contractPhases ContractPhase[]
911
+ methodPhases PropertyPaymentMethodPhase[]
912
+ // Used by instantiated payment phases
913
+ paymentPhases PaymentPhase[]
904
914
 
905
915
  @@unique([tenantId, name]) // Unique per tenant, or globally if tenantId is null
906
916
  @@index([tenantId])
@@ -1005,8 +1015,10 @@ model PropertyPaymentMethodPhase {
1005
1015
  updatedAt DateTime @updatedAt
1006
1016
 
1007
1017
  // Normalized child tables (for DOCUMENTATION phases)
1008
- steps PaymentMethodPhaseStep[]
1009
- requiredDocuments PaymentMethodPhaseDocument[]
1018
+ steps PaymentMethodPhaseStep[]
1019
+ requiredDocuments PaymentMethodPhaseDocument[]
1020
+ // Normalized child tables (for QUESTIONNAIRE phases)
1021
+ questionnaireFields PaymentMethodPhaseField[]
1010
1022
 
1011
1023
  @@index([paymentMethodId])
1012
1024
  @@index([paymentPlanId])
@@ -1083,12 +1095,71 @@ model PaymentMethodPhaseDocument {
1083
1095
  @@map("payment_method_phase_documents")
1084
1096
  }
1085
1097
 
1098
+ // =============================================================================
1099
+ // QUESTIONNAIRE FIELD TYPES - For QUESTIONNAIRE phases
1100
+ // =============================================================================
1101
+
1102
+ enum FieldType {
1103
+ TEXT // Short text input
1104
+ TEXTAREA // Long text input
1105
+ NUMBER // Numeric input (with optional min/max)
1106
+ CURRENCY // Currency input (validated as money)
1107
+ EMAIL // Email validation
1108
+ PHONE // Phone number validation
1109
+ DATE // Date picker
1110
+ SELECT // Dropdown/single select
1111
+ MULTI_SELECT // Multiple selection
1112
+ CHECKBOX // Boolean yes/no
1113
+ RADIO // Radio button group
1114
+ FILE // File upload (single)
1115
+ }
1116
+
1117
+ // Questionnaire field template within a QUESTIONNAIRE phase
1118
+ model PaymentMethodPhaseField {
1119
+ id String @id @default(cuid())
1120
+ phaseId String
1121
+ phase PropertyPaymentMethodPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
1122
+
1123
+ // Field identification
1124
+ name String // Internal field name (e.g., "monthly_income")
1125
+ label String // Display label (e.g., "Monthly Income")
1126
+ description String? @db.Text // Help text for the field
1127
+ placeholder String? // Placeholder text
1128
+
1129
+ // Field configuration
1130
+ fieldType FieldType
1131
+ isRequired Boolean @default(true)
1132
+ order Int
1133
+
1134
+ // Validation rules (JSON schema-like)
1135
+ // Examples:
1136
+ // NUMBER: { "min": 0, "max": 1000000 }
1137
+ // TEXT: { "minLength": 2, "maxLength": 100, "pattern": "^[A-Za-z]+$" }
1138
+ // SELECT: { "options": [{"value": "employed", "label": "Employed"}, ...] }
1139
+ // DATE: { "minDate": "now", "maxDate": "+30d" }
1140
+ validation Json?
1141
+
1142
+ // For conditional display (e.g., show if another field has a certain value)
1143
+ // { "field": "employment_status", "operator": "equals", "value": "employed" }
1144
+ displayCondition Json?
1145
+
1146
+ // Default value (JSON to support any type)
1147
+ defaultValue Json?
1148
+
1149
+ createdAt DateTime @default(now())
1150
+ updatedAt DateTime @updatedAt
1151
+
1152
+ @@unique([phaseId, name])
1153
+ @@index([phaseId])
1154
+ @@map("payment_method_phase_fields")
1155
+ }
1156
+
1086
1157
  // =============================================================================
1087
1158
  // CONTRACT DOMAIN - Unified agreement model (replaces Mortgage, PurchasePlan, etc.)
1088
1159
  // =============================================================================
1089
1160
  // Contract is the canonical agreement. "Mortgage" is just a product configuration
1090
1161
  // that creates a Contract with specific phases (documentation, downpayment, long-term payment).
1091
- // Phases can be DOCUMENTATION (FSM for approvals) or PAYMENT (PaymentPlan-driven installments).
1162
+ // Phases can be QUESTIONNAIRE, DOCUMENTATION, or PAYMENT.
1092
1163
  // =============================================================================
1093
1164
 
1094
1165
  model Contract {
@@ -1111,28 +1182,13 @@ model Contract {
1111
1182
  description String? @db.Text
1112
1183
  contractType String // Admin-defined: MORTGAGE, INSTALLMENT, RENT_TO_OWN, CASH, LEASE, etc.
1113
1184
 
1114
- // Financial summary (computed from phases)
1115
- totalAmount Float // Total contract value (from unit price or negotiated)
1116
- downPayment Float @default(0)
1117
- downPaymentPaid Float @default(0)
1118
- principal Float? // Financed amount (if applicable)
1119
- interestRate Float? // Overall interest rate (if applicable)
1120
- termMonths Int? // Total term (if applicable)
1121
- periodicPayment Float? // Computed periodic payment (if applicable)
1122
- totalPaidToDate Float @default(0)
1123
- totalInterestPaid Float @default(0)
1124
-
1125
- // Pre-approval and underwriting data (moved from prequalification)
1126
- monthlyIncome Float? // Buyer's monthly income
1127
- monthlyExpenses Float? // Buyer's monthly expenses
1128
- preApprovalAnswers Json? // Questionnaire answers from PRE_APPROVAL step
1129
- underwritingScore Float? // Aggregate score from underwriting evaluation
1130
- debtToIncomeRatio Float? // Calculated DTI ratio
1131
-
1132
- // FSM state (DB-enforced enums)
1185
+ // Contract value (negotiated from unit price)
1186
+ totalAmount Float
1187
+
1188
+ // FSM state (DB-enforced enum)
1133
1189
  status ContractStatus @default(DRAFT)
1134
- state ContractStatus @default(DRAFT) // FSM state for workflow
1135
1190
  currentPhaseId String?
1191
+ currentPhase ContractPhase? @relation("CurrentPhase", fields: [currentPhaseId], references: [id])
1136
1192
 
1137
1193
  // Timing
1138
1194
  nextPaymentDueDate DateTime?
@@ -1176,7 +1232,7 @@ model Contract {
1176
1232
  @@index([sellerId])
1177
1233
  @@index([paymentMethodId])
1178
1234
  @@index([status])
1179
- @@index([state])
1235
+ @@index([currentPhaseId])
1180
1236
  @@map("contracts")
1181
1237
  }
1182
1238
 
@@ -1237,12 +1293,21 @@ model ContractRefund {
1237
1293
 
1238
1294
  // Phase within a contract - can be DOCUMENTATION or PAYMENT type
1239
1295
  // Admin names phases freely (e.g., "KYC Documents", "Downpayment", "Monthly Mortgage")
1296
+ // =============================================================================
1297
+ // CONTRACT PHASE - Base model with polymorphic extensions
1298
+ // =============================================================================
1299
+ // ContractPhase is the base table with shared fields only.
1300
+ // Each phase has exactly ONE extension table based on phaseCategory:
1301
+ // - QUESTIONNAIRE → QuestionnairePhase
1302
+ // - DOCUMENTATION → DocumentationPhase
1303
+ // - PAYMENT → PaymentPhase
1304
+ // This eliminates nullable field pollution and makes the schema self-documenting.
1305
+ // =============================================================================
1306
+
1240
1307
  model ContractPhase {
1241
- id String @id @default(cuid())
1242
- contractId String
1243
- contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
1244
- paymentPlanId String? // Only for PAYMENT phases
1245
- paymentPlan PaymentPlan? @relation(fields: [paymentPlanId], references: [id])
1308
+ id String @id @default(cuid())
1309
+ contractId String
1310
+ contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
1246
1311
 
1247
1312
  // Admin-defined naming
1248
1313
  name String
@@ -1256,64 +1321,202 @@ model ContractPhase {
1256
1321
  // FSM state for this phase (DB-enforced enum)
1257
1322
  status PhaseStatus @default(PENDING)
1258
1323
 
1259
- // =========================================================================
1260
- // WORKFLOW TRACKING - Current step pointer for UX and orchestration
1261
- // =========================================================================
1262
- // Canonical pointer to the step currently requiring attention.
1263
- // Updated by service when: phase activates (→ first step), step completes (→ next),
1264
- // step rejected (→ same step with NEEDS_RESUBMISSION), or phase completes (→ null).
1265
- currentStepId String?
1266
- currentStep DocumentationStep? @relation("CurrentStep", fields: [currentStepId], references: [id])
1324
+ // Timing (shared across all phase types)
1325
+ dueDate DateTime?
1326
+ startDate DateTime?
1327
+ endDate DateTime?
1328
+ activatedAt DateTime?
1329
+ completedAt DateTime?
1267
1330
 
1268
- // Financial details (for PAYMENT phases)
1269
- totalAmount Float?
1270
- paidAmount Float @default(0)
1271
- remainingAmount Float?
1272
- interestRate Float?
1331
+ // Activation rules (shared)
1332
+ requiresPreviousPhaseCompletion Boolean @default(true)
1273
1333
 
1274
- // Fund collection behavior (snapshotted from template at contract creation)
1275
- // true = we collect funds via wallet/gateway (e.g., downpayment)
1276
- // false = external payment, we only track/reconcile (e.g., bank mortgage)
1277
- collectFunds Boolean @default(true)
1334
+ createdAt DateTime @default(now())
1335
+ updatedAt DateTime @updatedAt
1336
+
1337
+ // Polymorphic extensions (exactly one will be populated based on phaseCategory)
1338
+ questionnairePhase QuestionnairePhase?
1339
+ documentationPhase DocumentationPhase?
1340
+ paymentPhase PaymentPhase?
1341
+
1342
+ // Payments can be linked to any phase type (for tracking)
1343
+ payments ContractPayment[]
1344
+
1345
+ // Back-relation for contracts where this is the current phase
1346
+ currentForContracts Contract[] @relation("CurrentPhase")
1347
+
1348
+ @@index([contractId])
1349
+ @@index([phaseCategory])
1350
+ @@index([status])
1351
+ @@index([order])
1352
+ @@map("contract_phases")
1353
+ }
1354
+
1355
+ // =============================================================================
1356
+ // QUESTIONNAIRE PHASE DATA - Extension for QUESTIONNAIRE phases
1357
+ // =============================================================================
1358
+ // Collects form data from users (eligibility, pre-approval, underwriting inputs)
1359
+ // =============================================================================
1360
+
1361
+ model QuestionnairePhase {
1362
+ id String @id @default(cuid())
1363
+ phaseId String @unique
1364
+ phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
1365
+
1366
+ // Progress tracking
1367
+ completedFieldsCount Int @default(0)
1368
+ totalFieldsCount Int @default(0)
1369
+
1370
+ // Computed results (for UNDERWRITING phases)
1371
+ underwritingScore Float?
1372
+ debtToIncomeRatio Float?
1373
+ underwritingDecision String? // APPROVED, CONDITIONAL, DECLINED
1374
+ underwritingNotes String? @db.Text
1375
+
1376
+ // Snapshot of field definitions at creation
1377
+ fieldsSnapshot Json?
1378
+
1379
+ createdAt DateTime @default(now())
1380
+ updatedAt DateTime @updatedAt
1381
+
1382
+ // Child records
1383
+ fields QuestionnaireField[]
1384
+
1385
+ @@index([phaseId])
1386
+ @@map("questionnaire_phases")
1387
+ }
1388
+
1389
+ // =============================================================================
1390
+ // DOCUMENTATION PHASE DATA - Extension for DOCUMENTATION phases
1391
+ // =============================================================================
1392
+ // Manages document upload/approval workflow with FSM steps
1393
+ // =============================================================================
1394
+
1395
+ model DocumentationPhase {
1396
+ id String @id @default(cuid())
1397
+ phaseId String @unique
1398
+ phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
1278
1399
 
1279
- // Progress counters (for efficient activation checks)
1400
+ // Current step pointer for UX and orchestration
1401
+ currentStepId String?
1402
+ currentStep DocumentationStep? @relation("CurrentStep", fields: [currentStepId], references: [id])
1403
+
1404
+ // Progress counters
1280
1405
  approvedDocumentsCount Int @default(0)
1281
1406
  requiredDocumentsCount Int @default(0)
1282
1407
  completedStepsCount Int @default(0)
1283
1408
  totalStepsCount Int @default(0)
1284
1409
 
1285
- // Timing
1286
- dueDate DateTime?
1287
- startDate DateTime?
1288
- endDate DateTime?
1289
- activatedAt DateTime?
1290
- completedAt DateTime?
1291
-
1292
- // Activation rules
1293
- requiresPreviousPhaseCompletion Boolean @default(true)
1294
- minimumCompletionPercentage Float?
1295
- completionCriterion CompletionCriterion?
1410
+ // Completion criteria
1411
+ minimumCompletionPercentage Float?
1412
+ completionCriterion CompletionCriterion?
1296
1413
 
1297
- // Snapshots for audit (effective config at contract creation)
1298
- paymentPlanSnapshot Json?
1414
+ // Snapshots for audit
1299
1415
  stepDefinitionsSnapshot Json?
1300
1416
  requiredDocumentSnapshot Json?
1301
1417
 
1302
1418
  createdAt DateTime @default(now())
1303
1419
  updatedAt DateTime @updatedAt
1304
1420
 
1305
- // Relations
1421
+ // Child records
1422
+ steps DocumentationStep[]
1423
+
1424
+ @@index([phaseId])
1425
+ @@index([currentStepId])
1426
+ @@map("documentation_phases")
1427
+ }
1428
+
1429
+ // =============================================================================
1430
+ // PAYMENT PHASE DATA - Extension for PAYMENT phases
1431
+ // =============================================================================
1432
+ // Manages installment-based payment collection
1433
+ // =============================================================================
1434
+
1435
+ model PaymentPhase {
1436
+ id String @id @default(cuid())
1437
+ phaseId String @unique
1438
+ phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
1439
+
1440
+ // Payment plan reference
1441
+ paymentPlanId String?
1442
+ paymentPlan PaymentPlan? @relation(fields: [paymentPlanId], references: [id])
1443
+
1444
+ // Financial details
1445
+ totalAmount Float
1446
+ paidAmount Float @default(0)
1447
+ interestRate Float @default(0)
1448
+
1449
+ // Fund collection behavior
1450
+ // true = we collect funds via wallet/gateway (e.g., downpayment)
1451
+ // false = external payment, we only track/reconcile (e.g., bank mortgage)
1452
+ collectFunds Boolean @default(true)
1453
+
1454
+ // Completion criteria
1455
+ minimumCompletionPercentage Float?
1456
+
1457
+ // Snapshot for audit
1458
+ paymentPlanSnapshot Json?
1459
+
1460
+ createdAt DateTime @default(now())
1461
+ updatedAt DateTime @updatedAt
1462
+
1463
+ // Child records
1306
1464
  installments ContractInstallment[]
1307
- payments ContractPayment[]
1308
- steps DocumentationStep[] // For DOCUMENTATION phases (FSM steps)
1309
1465
 
1310
- @@index([contractId])
1466
+ @@index([phaseId])
1311
1467
  @@index([paymentPlanId])
1312
- @@index([phaseCategory])
1313
- @@index([status])
1314
- @@index([order])
1315
- @@index([currentStepId])
1316
- @@map("contract_phases")
1468
+ @@map("payment_phases")
1469
+ }
1470
+
1471
+ // =============================================================================
1472
+ // QUESTIONNAIRE FIELDS - Instantiated fields within QUESTIONNAIRE phases
1473
+ // =============================================================================
1474
+ // When a contract is created from a payment method template, questionnaire field
1475
+ // templates are copied to QuestionnaireField records. Users submit answers here.
1476
+ // =============================================================================
1477
+
1478
+ model QuestionnaireField {
1479
+ id String @id @default(cuid())
1480
+ questionnairePhaseId String
1481
+ questionnairePhase QuestionnairePhase @relation(fields: [questionnairePhaseId], references: [id], onDelete: Cascade)
1482
+
1483
+ // Field identification (copied from template)
1484
+ name String // Internal field name (e.g., "monthly_income")
1485
+ label String // Display label (e.g., "Monthly Income")
1486
+ description String? @db.Text
1487
+ placeholder String?
1488
+
1489
+ // Field configuration (copied from template)
1490
+ fieldType FieldType
1491
+ isRequired Boolean @default(true)
1492
+ order Int
1493
+
1494
+ // Validation rules (copied from template)
1495
+ validation Json?
1496
+ displayCondition Json?
1497
+ defaultValue Json?
1498
+
1499
+ // User's submitted answer (JSON to support any type)
1500
+ // TEXT: "John Doe"
1501
+ // NUMBER/CURRENCY: 500000
1502
+ // SELECT: "employed"
1503
+ // MULTI_SELECT: ["option1", "option2"]
1504
+ // CHECKBOX: true
1505
+ // DATE: "2024-01-15"
1506
+ answer Json?
1507
+
1508
+ // Validation status
1509
+ isValid Boolean @default(false)
1510
+ validationErrors Json? // Array of error messages if invalid
1511
+
1512
+ // Timestamps
1513
+ submittedAt DateTime?
1514
+ createdAt DateTime @default(now())
1515
+ updatedAt DateTime @updatedAt
1516
+
1517
+ @@unique([questionnairePhaseId, name])
1518
+ @@index([questionnairePhaseId])
1519
+ @@map("questionnaire_fields")
1317
1520
  }
1318
1521
 
1319
1522
  // =============================================================================
@@ -1356,9 +1559,9 @@ model ContractEvent {
1356
1559
 
1357
1560
  // Steps within a DOCUMENTATION phase (FSM for document collection/approval)
1358
1561
  model DocumentationStep {
1359
- id String @id @default(cuid())
1360
- phaseId String
1361
- phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
1562
+ id String @id @default(cuid())
1563
+ documentationPhaseId String
1564
+ documentationPhase DocumentationPhase @relation(fields: [documentationPhaseId], references: [id], onDelete: Cascade)
1362
1565
 
1363
1566
  name String
1364
1567
  description String? @db.Text
@@ -1383,15 +1586,6 @@ model DocumentationStep {
1383
1586
  // Configuration metadata (for GENERATE_DOCUMENT steps, etc.)
1384
1587
  metadata Json?
1385
1588
 
1386
- // For PRE_APPROVAL steps: store questionnaire answers
1387
- preApprovalAnswers Json?
1388
-
1389
- // For UNDERWRITING steps: store evaluation results
1390
- underwritingScore Float?
1391
- debtToIncomeRatio Float?
1392
- underwritingDecision String? // APPROVED, CONDITIONAL, DECLINED
1393
- underwritingNotes String? @db.Text
1394
-
1395
1589
  // Assignment
1396
1590
  assigneeId String?
1397
1591
  assignee User? @relation("DocumentationStepAssignee", fields: [assigneeId], references: [id])
@@ -1407,12 +1601,12 @@ model DocumentationStep {
1407
1601
  updatedAt DateTime @updatedAt
1408
1602
 
1409
1603
  approvals DocumentationStepApproval[]
1410
- currentForPhase ContractPhase[] @relation("CurrentStep")
1604
+ currentForPhase DocumentationPhase[] @relation("CurrentStep")
1411
1605
 
1412
- @@index([phaseId])
1606
+ @@index([documentationPhaseId])
1413
1607
  @@index([status])
1414
1608
  @@index([order])
1415
- @@map("contract_phase_steps")
1609
+ @@map("documentation_steps")
1416
1610
  }
1417
1611
 
1418
1612
  // Required documents for a step (normalized from CSV)
@@ -1427,7 +1621,7 @@ model DocumentationStepDocument {
1427
1621
  createdAt DateTime @default(now())
1428
1622
 
1429
1623
  @@index([stepId, documentType])
1430
- @@map("contract_phase_step_documents")
1624
+ @@map("documentation_step_documents")
1431
1625
  }
1432
1626
 
1433
1627
  // Approvals for documentation steps
@@ -1445,14 +1639,14 @@ model DocumentationStepApproval {
1445
1639
  createdAt DateTime @default(now())
1446
1640
 
1447
1641
  @@index([stepId])
1448
- @@map("contract_phase_step_approvals")
1642
+ @@map("documentation_step_approvals")
1449
1643
  }
1450
1644
 
1451
1645
  // Installments within a PAYMENT phase
1452
1646
  model ContractInstallment {
1453
- id String @id @default(cuid())
1454
- phaseId String
1455
- phase ContractPhase @relation(fields: [phaseId], references: [id], onDelete: Cascade)
1647
+ id String @id @default(cuid())
1648
+ paymentPhaseId String
1649
+ paymentPhase PaymentPhase @relation(fields: [paymentPhaseId], references: [id], onDelete: Cascade)
1456
1650
 
1457
1651
  installmentNumber Int
1458
1652
 
@@ -1476,7 +1670,7 @@ model ContractInstallment {
1476
1670
 
1477
1671
  payments ContractPayment[]
1478
1672
 
1479
- @@index([phaseId])
1673
+ @@index([paymentPhaseId])
1480
1674
  @@index([dueDate])
1481
1675
  @@index([status])
1482
1676
  @@map("contract_installments")
@@ -2126,8 +2320,16 @@ model DomainEvent {
2126
2320
  // =============================================================================
2127
2321
  // Property Transfer Request
2128
2322
  // =============================================================================
2129
- // Allows a buyer to request transferring their contract to a different property
2130
- // while preserving payments, completed workflow steps, and progress.
2323
+ // Allows a buyer to request transferring their contract to a different property.
2324
+ //
2325
+ // TRANSFER PAYMENT FLOW:
2326
+ // 1. All payments from source contract are refunded to buyer's wallet (single credit transaction)
2327
+ // 2. Source contract marked TRANSFERRED, unit released
2328
+ // 3. New contract created for target unit with zero payments
2329
+ // 4. Buyer can optionally apply wallet balance as equity during new mortgage application
2330
+ //
2331
+ // This ensures clean accounting: each contract has isolated payment history,
2332
+ // wallet serves as intermediary, and buyer retains full credit.
2131
2333
  // =============================================================================
2132
2334
 
2133
2335
  model PropertyTransferRequest {
@@ -2163,7 +2365,11 @@ model PropertyTransferRequest {
2163
2365
  sourceTotalAmount Float? // Original contract total
2164
2366
  targetTotalAmount Float? // New contract total (based on target property)
2165
2367
  priceAdjustment Float? // Difference (positive = buyer owes more)
2166
- paymentsMigrated Int? // Number of payments migrated
2368
+
2369
+ // Wallet refund tracking (payments refunded to wallet, NOT carried over)
2370
+ refundedAmount Float? // Total amount refunded to wallet
2371
+ refundTransactionId String? // Reference to wallet Transaction record
2372
+ refundedAt DateTime?
2167
2373
 
2168
2374
  // Result - new contract created after approval
2169
2375
  targetContractId String?