bugs were covered

This commit is contained in:
Laxman 2026-05-15 20:17:06 +05:30
parent e99a28b7f7
commit f5022b613d
10 changed files with 56 additions and 25 deletions

View File

@ -43,7 +43,7 @@ async function reset() {
await db.ApplicationProgress.destroy({ await db.ApplicationProgress.destroy({
where: { where: {
applicationId: app.id, applicationId: app.id,
stageName: ['LOI Approval', 'Security Details', 'LOI Issue', 'Dealer Code Generation', 'Architecture Team Assigned', 'Statutory GST'] stageName: ['LOI Approval', 'Security Deposit', 'LOI Issue', 'Dealer Code Generation', 'Architecture Team Assigned', 'Statutory GST']
} }
}); });

View File

@ -0,0 +1,18 @@
-- Rename overallStatus value Security Details -> Security Deposit (canonical label from APPLICATION_STATUS.SECURITY_DETAILS).
-- Run once per environment AFTER deploying code that uses 'Security Deposit'.
--
-- 1) Discover enum type name for applications.overallStatus:
-- SELECT c.column_name, c.udt_name
-- FROM information_schema.columns c
-- WHERE c.table_schema = 'public' AND c.table_name = 'applications' AND c.column_name = 'overallStatus';
--
-- 2) Add new enum value (PostgreSQL 9.1+). Replace type name if yours differs.
-- ALTER TYPE "enum_applications_overallStatus" ADD VALUE IF NOT EXISTS 'Security Deposit';
--
-- 3) Backfill rows (camelCase column is typical for this Sequelize project).
-- UPDATE applications SET "overallStatus" = 'Security Deposit' WHERE "overallStatus" = 'Security Details';
--
-- 4) Backfill ApplicationProgress pipeline stage label (see ONBOARDING_STAGES in progress.ts).
-- UPDATE application_progress SET "stageName" = 'Security Deposit' WHERE "stageName" = 'Security Details';
-- Minimal safe block: only step 3+4 if you use VARCHAR-like enums; for native ENUM you must run step 2 first (cannot be in same transaction as use of new value on older PG — run ADD VALUE, COMMIT, then UPDATE).

View File

@ -85,7 +85,7 @@ export const APPLICATION_STATUS = {
LEVEL_3_PENDING: 'Level 3 Interview Pending', LEVEL_3_PENDING: 'Level 3 Interview Pending',
LEVEL_3_APPROVED: 'Level 3 Approved', LEVEL_3_APPROVED: 'Level 3 Approved',
FDD_VERIFICATION: 'FDD Verification', FDD_VERIFICATION: 'FDD Verification',
SECURITY_DETAILS: 'Security Details', SECURITY_DETAILS: 'Security Deposit',
PAYMENT_PENDING: 'Payment Pending', PAYMENT_PENDING: 'Payment Pending',
LOI_IN_PROGRESS: 'LOI In Progress', LOI_IN_PROGRESS: 'LOI In Progress',
LOI_ISSUED: 'LOI Issued', LOI_ISSUED: 'LOI Issued',
@ -142,6 +142,8 @@ export const OVERALL_STATUS_TO_DB_CURRENT_STAGE: Record<
[APPLICATION_STATUS.LEVEL_3_APPROVED]: APPLICATION_STAGES.LEVEL_3_APPROVED, [APPLICATION_STATUS.LEVEL_3_APPROVED]: APPLICATION_STAGES.LEVEL_3_APPROVED,
[APPLICATION_STATUS.FDD_VERIFICATION]: APPLICATION_STAGES.FDD, [APPLICATION_STATUS.FDD_VERIFICATION]: APPLICATION_STAGES.FDD,
[APPLICATION_STATUS.SECURITY_DETAILS]: APPLICATION_STAGES.LOI, [APPLICATION_STATUS.SECURITY_DETAILS]: APPLICATION_STAGES.LOI,
/** Legacy `overallStatus` before rename; remove after DB migrated */
'Security Details': APPLICATION_STAGES.LOI,
[APPLICATION_STATUS.PAYMENT_PENDING]: APPLICATION_STAGES.LOI, [APPLICATION_STATUS.PAYMENT_PENDING]: APPLICATION_STAGES.LOI,
[APPLICATION_STATUS.LOI_IN_PROGRESS]: APPLICATION_STAGES.LOI, [APPLICATION_STATUS.LOI_IN_PROGRESS]: APPLICATION_STAGES.LOI,
[APPLICATION_STATUS.LOI_ISSUED]: APPLICATION_STAGES.LOI, [APPLICATION_STATUS.LOI_ISSUED]: APPLICATION_STAGES.LOI,

View File

@ -10,7 +10,7 @@ export const ONBOARDING_STAGES = [
{ name: '3rd Level Interview', order: 6 }, { name: '3rd Level Interview', order: 6 },
{ name: 'FDD', order: 7 }, { name: 'FDD', order: 7 },
{ name: 'LOI Approval', order: 8 }, { name: 'LOI Approval', order: 8 },
{ name: 'Security Details', order: 9 }, { name: 'Security Deposit', order: 9 },
{ name: 'LOI Issue', order: 10 }, { name: 'LOI Issue', order: 10 },
{ name: 'Dealer Code Generation', order: 11 }, { name: 'Dealer Code Generation', order: 11 },
{ name: 'Architecture Work', order: 12 }, { name: 'Architecture Work', order: 12 },
@ -107,8 +107,10 @@ export const PIPELINE_STAGE_LABEL_BY_OVERALL_STATUS: Record<string, string> = {
'Level 3 Approved': '3rd Level Interview', 'Level 3 Approved': '3rd Level Interview',
'FDD Verification': 'FDD', 'FDD Verification': 'FDD',
'LOI In Progress': 'LOI Approval', 'LOI In Progress': 'LOI Approval',
'Security Details': 'Security Details', /** @deprecated DB rows may still use pre-rename label until migrated */
'Payment Pending': 'Security Details', 'Security Details': 'Security Deposit',
'Security Deposit': 'Security Deposit',
'Payment Pending': 'Security Deposit',
'LOI Issued': 'LOI Issue', 'LOI Issued': 'LOI Issue',
'Statutory LOI Ack': 'LOI Issue', 'Statutory LOI Ack': 'LOI Issue',
'Dealer Code Generation': 'Dealer Code Generation', 'Dealer Code Generation': 'Dealer Code Generation',

View File

@ -51,7 +51,7 @@ const ACTION_DESCRIPTIONS: Record<string, string> = {
DEALER_UPDATED: 'Dealer profile updated', DEALER_UPDATED: 'Dealer profile updated',
DEALER_CODE_GENERATED: 'Dealer code generated', DEALER_CODE_GENERATED: 'Dealer code generated',
PAYMENT_UPDATED: 'Payment record updated', PAYMENT_UPDATED: 'Payment record updated',
SECURITY_DEPOSIT_UPDATED: 'Security deposit updated', SECURITY_DEPOSIT_UPDATED: 'Security Deposit updated',
FNF_UPDATED: 'F&F settlement updated', FNF_UPDATED: 'F&F settlement updated',
CLEARANCE_UPDATED: 'Departmental clearance response recorded', CLEARANCE_UPDATED: 'Departmental clearance response recorded',
STAKEHOLDER_CLEARANCE_UPDATED: 'F&F stakeholder clearance synced', STAKEHOLDER_CLEARANCE_UPDATED: 'F&F stakeholder clearance synced',

View File

@ -451,13 +451,15 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) =>
if ((depositType === 'SECURITY_DEPOSIT' || !depositType) && status === 'Verified') { if ((depositType === 'SECURITY_DEPOSIT' || !depositType) && status === 'Verified') {
const os = application.overallStatus; const os = application.overallStatus;
const inInitialSdCorridor = const inInitialSdCorridor =
os === APPLICATION_STATUS.PAYMENT_PENDING || os === APPLICATION_STATUS.SECURITY_DETAILS; os === APPLICATION_STATUS.PAYMENT_PENDING ||
os === APPLICATION_STATUS.SECURITY_DETAILS ||
os === 'Security Details';
if (inInitialSdCorridor) { if (inInitialSdCorridor) {
console.log( console.log(
`[DEBUG] SECURITY_DEPOSIT verified (overallStatus=${os}). Aligning to Security Details for admin before LOI Issued.`, `[DEBUG] SECURITY_DEPOSIT verified (overallStatus=${os}). Aligning to Security Deposit for admin before LOI Issued.`,
); );
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.SECURITY_DETAILS, req.user?.id || null, { await WorkflowService.transitionApplication(application, APPLICATION_STATUS.SECURITY_DETAILS, req.user?.id || null, {
reason: 'Security deposit verified by Finance. Awaiting admin approval to proceed to LOI issuance.', reason: 'Security Deposit verified by Finance. Awaiting admin approval to proceed to LOI issuance.',
stage: APPLICATION_STAGES.LOI, stage: APPLICATION_STAGES.LOI,
progressPercentage: 78 progressPercentage: 78
}); });
@ -492,7 +494,7 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) =>
res.json({ success: true, message: 'Security Deposit updated', data: updatedDeposit }); res.json({ success: true, message: 'Security Deposit updated', data: updatedDeposit });
} catch (error) { } catch (error) {
console.error('Update Security Deposit error:', error); console.error('Update Security Deposit error:', error);
res.status(500).json({ success: false, message: 'Error updating security deposit' }); res.status(500).json({ success: false, message: 'Error updating Security Deposit' });
} }
}; };
@ -517,6 +519,6 @@ export const getSecurityDeposit = async (req: Request, res: Response) => {
res.json({ success: true, data: deposits }); res.json({ success: true, data: deposits });
} catch (error) { } catch (error) {
console.error('Fetch Security Deposit error:', error); console.error('Fetch Security Deposit error:', error);
res.status(500).json({ success: false, message: 'Error fetching security deposit' }); res.status(500).json({ success: false, message: 'Error fetching Security Deposit' });
} }
}; };

View File

@ -111,8 +111,8 @@ export const acknowledgeRequest = async (req: AuthRequest, res: Response) => {
const phone = (applicantUser as any)?.mobileNumber || application.phone || ''; const phone = (applicantUser as any)?.mobileNumber || application.phone || '';
await NotificationService.notify(applicantUser?.id ?? null, application.email, { await NotificationService.notify(applicantUser?.id ?? null, application.email, {
title: `Security deposit — ${application.applicationId}`, title: `Security Deposit — ${application.applicationId}`,
message: `Please remit the security deposit for ${application.applicationId} as per your LOI.`, message: `Please remit the Security Deposit for ${application.applicationId} as per your LOI.`,
channels: phone ? ['email', 'whatsapp'] : ['email'], channels: phone ? ['email', 'whatsapp'] : ['email'],
templateCode: 'SECURITY_DEPOSIT_REQUEST', templateCode: 'SECURITY_DEPOSIT_REQUEST',
placeholders: { placeholders: {
@ -125,7 +125,7 @@ export const acknowledgeRequest = async (req: AuthRequest, res: Response) => {
ctaLabel: 'Pay or upload proof', ctaLabel: 'Pay or upload proof',
phone: String(phone || '') phone: String(phone || '')
} }
}).catch((e: any) => console.error('[LOI] Security deposit email failed:', e)); }).catch((e: any) => console.error('[LOI] Security Deposit email failed:', e));
} }
res.json({ success: true, message: 'LOI Acknowledged by applicant' }); res.json({ success: true, message: 'LOI Acknowledged by applicant' });

View File

@ -145,7 +145,7 @@ const seedTemplates = async () => {
}, },
{ {
templateCode: 'ONBOARDING_PAYMENT_VERIFIED', templateCode: 'ONBOARDING_PAYMENT_VERIFIED',
description: 'Notification when security deposit or initial payment is verified', description: 'Notification when Security Deposit or initial payment is verified',
subject: 'Payment Verified: {{applicationId}}', subject: 'Payment Verified: {{applicationId}}',
fileName: 'onboarding_payment_verified.html', fileName: 'onboarding_payment_verified.html',
placeholders: ['applicationId', 'dealerName', 'paymentType', 'amount', 'link'] placeholders: ['applicationId', 'dealerName', 'paymentType', 'amount', 'link']
@ -355,7 +355,7 @@ const seedTemplates = async () => {
}, },
{ {
templateCode: 'SECURITY_DEPOSIT_REQUEST', templateCode: 'SECURITY_DEPOSIT_REQUEST',
description: 'After LOI acknowledgement: security deposit payment instructions', description: 'After LOI acknowledgement: Security Deposit payment instructions',
subject: 'Security Deposit Payment Required — {{applicationId}}', subject: 'Security Deposit Payment Required — {{applicationId}}',
fileName: 'security_deposit_request.html', fileName: 'security_deposit_request.html',
placeholders: ['applicantName', 'applicationId', 'amount', 'dueDate', 'bankDetails', 'link', 'ctaLabel'] placeholders: ['applicantName', 'applicationId', 'amount', 'dueDate', 'bankDetails', 'link', 'ctaLabel']

View File

@ -26,7 +26,14 @@ export class WorkflowIntegrityService {
} }
// LOI STAGE INTEGRITY // LOI STAGE INTEGRITY
if ([APPLICATION_STATUS.LOI_IN_PROGRESS, APPLICATION_STATUS.PAYMENT_PENDING, APPLICATION_STATUS.SECURITY_DETAILS].includes(application.overallStatus)) { // Include legacy DB label "Security Details" until data migration completes.
const loiPreIssuedStatuses = [
APPLICATION_STATUS.LOI_IN_PROGRESS,
APPLICATION_STATUS.PAYMENT_PENDING,
APPLICATION_STATUS.SECURITY_DETAILS,
'Security Details'
];
if (loiPreIssuedStatuses.includes(application.overallStatus)) {
await this.syncLoiIntegrity(application); await this.syncLoiIntegrity(application);
} }
@ -114,7 +121,7 @@ export class WorkflowIntegrityService {
}); });
if (policyMet && deposit) { if (policyMet && deposit) {
console.log(`[WorkflowIntegrityService] Policy met and payment verified for LOI on ${application.applicationId}. Aligning to Security Details for admin approval before LOI Issued.`); console.log(`[WorkflowIntegrityService] Policy met and payment verified for LOI on ${application.applicationId}. Aligning to Security Deposit for admin approval before LOI Issued.`);
// Ensure LoiRequest is also updated // Ensure LoiRequest is also updated
const request = await db.LoiRequest.findOne({ where: { applicationId: application.id } }); const request = await db.LoiRequest.findOne({ where: { applicationId: application.id } });
@ -123,7 +130,7 @@ export class WorkflowIntegrityService {
} }
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.SECURITY_DETAILS, null, { await WorkflowService.transitionApplication(application, APPLICATION_STATUS.SECURITY_DETAILS, null, {
reason: 'Integrity sync: LOI policy and deposit verified — use Security Details admin approval to reach LOI Issued.', reason: 'Integrity sync: LOI policy and deposit verified — use Security Deposit admin approval to reach LOI Issued.',
progressPercentage: 78 progressPercentage: 78
}); });
} }

View File

@ -426,8 +426,8 @@ async function triggerWorkflow() {
await ensureMandatoryCodeGenFields(applicationUUID, adminToken); await ensureMandatoryCodeGenFields(applicationUUID, adminToken);
await delay(300); await delay(300);
if (statusBeforeCodeGen === 'Security Details') { if (statusBeforeCodeGen === 'Security Deposit' || statusBeforeCodeGen === 'Security Details') {
log(9, 'Status is Security Details; re-verifying Security Deposit to move to LOI Issued...'); log(9, 'Status is Security Deposit (or legacy Security Details); re-verifying Security Deposit to move to LOI Issued...');
await apiRequest('/loa/security-deposit', 'POST', { await apiRequest('/loa/security-deposit', 'POST', {
applicationId: applicationUUID, applicationId: applicationUUID,
amount: 500000, amount: 500000,
@ -440,13 +440,13 @@ async function triggerWorkflow() {
log(9, `Status after re-verify: ${statusBeforeCodeGen}`); log(9, `Status after re-verify: ${statusBeforeCodeGen}`);
} }
// Current backend flow keeps app at "Security Details" until explicit admin transition. // Current backend flow keeps app at Security Deposit until explicit admin transition.
if (statusBeforeCodeGen === 'Security Details') { if (statusBeforeCodeGen === 'Security Deposit' || statusBeforeCodeGen === 'Security Details') {
log(9, 'Applying admin transition from Security Details -> LOI Issued...'); log(9, 'Applying admin transition from Security Deposit -> LOI Issued...');
await apiRequest(`/onboarding/applications/${applicationUUID}/status`, 'PUT', { await apiRequest(`/onboarding/applications/${applicationUUID}/status`, 'PUT', {
status: 'LOI Issued', status: 'LOI Issued',
stage: 'LOI', stage: 'LOI',
reason: 'E2E script alignment: unlock dealer code generation after Security Details checks.' reason: 'E2E script alignment: unlock dealer code generation after Security Deposit checks.'
}, adminToken); }, adminToken);
await delay(); await delay();
statusBeforeCodeGen = await getApplicationStatus(applicationUUID, adminToken); statusBeforeCodeGen = await getApplicationStatus(applicationUUID, adminToken);