From 65d2af7447e63916efe406e36ab6d63920029a20 Mon Sep 17 00:00:00 2001 From: laxman h Date: Wed, 8 Apr 2026 19:01:25 +0530 Subject: [PATCH] dealer onboarding dashboard flow changes made --- check_policies.ts | 8 ++ repair_all_progress.ts | 18 +++ repair_fdd_policy.ts | 27 +++++ scripts/seed-approval-policies.ts | 6 +- scripts/seed-document-configs.ts | 4 +- scripts/seed-system-configs.ts | 8 +- src/common/config/constants.ts | 4 +- src/common/utils/progress.ts | 21 +++- .../assessment/assessment.controller.ts | 97 +++++++++++++--- src/modules/eor/eor.controller.ts | 63 +++++++++-- src/modules/fdd/fdd.controller.ts | 106 ++++++------------ src/modules/loa/loa.controller.ts | 94 +++++++--------- src/modules/loi/loi.controller.ts | 57 ++++++++-- .../onboarding/onboarding.controller.ts | 96 ++++++++++------ .../onboarding/questionnaire.controller.ts | 10 +- 15 files changed, 408 insertions(+), 211 deletions(-) create mode 100644 check_policies.ts create mode 100644 repair_all_progress.ts create mode 100644 repair_fdd_policy.ts diff --git a/check_policies.ts b/check_policies.ts new file mode 100644 index 0000000..5071002 --- /dev/null +++ b/check_policies.ts @@ -0,0 +1,8 @@ +import db from './src/database/models/index.js'; + +async function checkPolicies() { + const policies = await db.StageApprovalPolicy.findAll(); + console.log(JSON.stringify(policies, null, 2)); +} + +checkPolicies().catch(console.error); diff --git a/repair_all_progress.ts b/repair_all_progress.ts new file mode 100644 index 0000000..993586d --- /dev/null +++ b/repair_all_progress.ts @@ -0,0 +1,18 @@ +import db from './src/database/models/index.js'; +import { syncApplicationProgress } from './src/common/utils/progress.js'; + +async function repairAllProgress() { + console.log('--- Repairing Progress Tracker State for ALL Applications ---'); + + const apps = await db.Application.findAll(); + console.log(`Found ${apps.length} applications to sync.`); + + for (const app of apps) { + console.log(`Syncing Progress for Application: ${app.applicationId} (Status: ${app.overallStatus})`); + await syncApplicationProgress(app.id, app.overallStatus); + } + + console.log('--- REPAIR COMPLETE ---'); +} + +repairAllProgress().catch(console.error).then(() => process.exit(0)); diff --git a/repair_fdd_policy.ts b/repair_fdd_policy.ts new file mode 100644 index 0000000..c87d8fa --- /dev/null +++ b/repair_fdd_policy.ts @@ -0,0 +1,27 @@ +import db from './src/database/models/index.js'; + +async function repairPolicy() { + console.log('--- Repairing FDD_VERIFICATION Policy ---'); + + // Update FDD_VERIFICATION to allow Finance and DD Admin + const [policy] = await db.StageApprovalPolicy.findOrCreate({ + where: { stageCode: 'FDD_VERIFICATION' }, + defaults: { + stageCode: 'FDD_VERIFICATION', + minApprovals: 1, + approvalMode: 'ROLE_MANDATORY', + requiredRoles: ['FDD', 'Finance', 'DD Admin', 'Finance Admin'], + isActive: true + } + }); + + if (policy) { + await policy.update({ + requiredRoles: ['FDD', 'Finance', 'DD Admin', 'Finance Admin', 'DD Head'], + minApprovals: 1 + }); + console.log('Policy updated successfully:', policy.requiredRoles); + } +} + +repairPolicy().catch(console.error).then(() => process.exit(0)); diff --git a/scripts/seed-approval-policies.ts b/scripts/seed-approval-policies.ts index 965c326..a03f04d 100644 --- a/scripts/seed-approval-policies.ts +++ b/scripts/seed-approval-policies.ts @@ -27,9 +27,9 @@ const policies = [ }, { stageCode: 'LOI_APPROVAL', - minApprovals: 3, + minApprovals: 2, approvalMode: 'ROLE_MANDATORY', - requiredRoles: ['Finance', 'DD Head', 'NBH'], + requiredRoles: ['DD Head', 'NBH'], isActive: true }, { @@ -43,7 +43,7 @@ const policies = [ stageCode: 'FDD_VERIFICATION', minApprovals: 1, approvalMode: 'ROLE_MANDATORY', - requiredRoles: ['FDD'], + requiredRoles: ['DD Admin', 'Super Admin'], isActive: true } ]; diff --git a/scripts/seed-document-configs.ts b/scripts/seed-document-configs.ts index e08fd47..25097a2 100644 --- a/scripts/seed-document-configs.ts +++ b/scripts/seed-document-configs.ts @@ -41,8 +41,8 @@ const configs = [ { documentType: 'Statutory Approval Certificate', stageCode: 'FDD', allowedRoles: [ROLES.FDD, ROLES.FINANCE, ROLES.SUPER_ADMIN, ROLES.DD_ADMIN] }, // LOI / Security (Approval Process) - { documentType: 'Initial Security Deposit Receipt', stageCode: 'LOI Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true }, - { documentType: 'Final Security Deposit Receipt', stageCode: 'LOA Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true }, + { documentType: 'Security Deposit Receipt', stageCode: 'LOI Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true }, + { documentType: 'First Fill Receipt', stageCode: 'LOA Approval', allowedRoles: [ROLES.DEALER, ROLES.FINANCE, ROLES.DD_HEAD, ROLES.NBH, ROLES.SUPER_ADMIN], isMandatory: true }, { documentType: 'LOI Acknowledgement Copy', stageCode: 'LOI Issue', allowedRoles: ALL_ROLES }, { documentType: 'Nodal Agreement', stageCode: 'LOI Approval', allowedRoles: [ROLES.LEGAL_ADMIN, ROLES.DEALER, ROLES.SUPER_ADMIN] }, diff --git a/scripts/seed-system-configs.ts b/scripts/seed-system-configs.ts index 83b526b..c8d2f4d 100644 --- a/scripts/seed-system-configs.ts +++ b/scripts/seed-system-configs.ts @@ -12,16 +12,16 @@ const seedSystemConfigs = async () => { const configs = [ { - key: 'INITIAL_SECURITY_DEPOSIT', + key: 'SECURITY_DEPOSIT', value: { amount: 500000, currency: 'INR' }, category: 'SECURITY_DEPOSIT', - description: 'Default Initial Security Deposit amount for new dealer onboarding' + description: 'Default Security Deposit amount for new dealer onboarding' }, { - key: 'FINAL_SECURITY_DEPOSIT', + key: 'FIRST_FILL', value: { amount: 1500000, currency: 'INR' }, category: 'SECURITY_DEPOSIT', - description: 'Default Final Security Deposit amount for new dealer onboarding' + description: 'Default First Fill amount for new dealer onboarding' } ]; diff --git a/src/common/config/constants.ts b/src/common/config/constants.ts index e467c65..ce249cd 100644 --- a/src/common/config/constants.ts +++ b/src/common/config/constants.ts @@ -380,8 +380,8 @@ export const DOCUMENT_TYPES = { STATUTORY_AUDIT: 'Statutory Approval Certificate', BANK_GUARANTEE: 'Bank Guarantee Document', SECURITY_DEPOSIT_RECEIPT: 'Security Deposit Receipt', - SECURITY_DEPOSIT_INITIAL: 'Initial Security Deposit Receipt', - SECURITY_DEPOSIT_FINAL: 'Final Security Deposit Receipt', + SECURITY_DEPOSIT: 'Security Deposit Receipt', + FIRST_FILL: 'First Fill Receipt', RELOCATION_PROPERTY_DOCS: 'Property documents for new location', RELOCATION_LEASE_AGREEMENT: 'Lease/Rental agreement for new location', RELOCATION_NOC_LANDLORD: 'NOC from current landlord', diff --git a/src/common/utils/progress.ts b/src/common/utils/progress.ts index acc4425..0d16591 100644 --- a/src/common/utils/progress.ts +++ b/src/common/utils/progress.ts @@ -50,8 +50,22 @@ export const updateApplicationProgress = async (applicationId: string, stageName } // Whenever a stage is marked 'active' or 'completed', - // all previous stages MUST be completed. + // all previous stages MUST exist and be marked 'completed'. if (status === 'active' || status === 'completed') { + const previousStages = ONBOARDING_STAGES.filter(s => s.order < stage.order); + for (const prev of previousStages) { + await ApplicationProgress.findOrCreate({ + where: { applicationId, stageName: prev.name }, + defaults: { + stageOrder: prev.order, + status: 'completed', + completionPercentage: 100, + stageCompletedAt: new Date() + } + }); + } + + // Also update any existing ones that weren't completed await ApplicationProgress.update( { status: 'completed', completionPercentage: 100, stageCompletedAt: new Date() }, { @@ -114,8 +128,9 @@ export const syncApplicationProgress = async (applicationId: string, overallStat // Determine status for this stage const isCompleted = [ 'Submitted', 'Questionnaire Completed', 'Shortlisted', 'Level 1 Approved', - 'Level 2 Approved', 'Level 3 Approved', 'LOI Issued', 'EOR Complete', - 'Approved', 'Onboarded' + 'Level 2 Approved', 'Level 3 Approved', 'FDD Verification', 'LOI Issued', + 'Dealer Code Generation', 'Architecture Team Completion', 'LOA Issued', + 'EOR Complete', 'Approved', 'Onboarded' ].includes(overallStatus); diff --git a/src/modules/assessment/assessment.controller.ts b/src/modules/assessment/assessment.controller.ts index 4e7fa47..4201c97 100644 --- a/src/modules/assessment/assessment.controller.ts +++ b/src/modules/assessment/assessment.controller.ts @@ -58,6 +58,16 @@ const processStageDecision = async (params: { }) => { const { applicationId, stageCode, decision, remarks, userId, roleCode, interviewId, nextStatus, nextStage, nextProgress } = params; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + const application = await db.Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) return { notFound: true }; + const resolvedId = application.id; + const policy = await db.StageApprovalPolicy.findOne({ where: { stageCode } }); if (!policy) return { noPolicy: true }; @@ -65,7 +75,7 @@ const processStageDecision = async (params: { // Check if user is an assigned participant const userAssignments = await db.RequestParticipant.findAll({ - where: { requestId: applicationId, requestType: 'application', userId } + where: { requestId: resolvedId, requestType: 'application', userId } }); const isAssigned = userAssignments.some((p: any) => { @@ -85,7 +95,7 @@ const processStageDecision = async (params: { // --- Sequential Enforcement (SRS 6.16.2 & 6.18.3.1 Compliance) --- if (roleCode !== 'Super Admin' && roleCode !== 'DD Admin') { const approvedActions = await db.StageApprovalAction.findAll({ - where: { applicationId, stageCode, decision: 'Approved' } + where: { applicationId: resolvedId, stageCode, decision: 'Approved' } }); const approvedRoles = new Set(approvedActions.map((a: any) => a.actorRole)); @@ -120,13 +130,13 @@ const processStageDecision = async (params: { // Record Action - Robust handle for null interviewId which breaks unique constraint in Postgres if (!interviewId) { const existing = await db.StageApprovalAction.findOne({ - where: { applicationId, stageCode, actorUserId: userId, interviewId: null } + where: { applicationId: resolvedId, stageCode, actorUserId: userId, interviewId: null } }); if (existing) { await existing.update({ decision, remarks: remarks || null, actorRole: assignedRole || roleCode }); } else { await db.StageApprovalAction.create({ - applicationId, + applicationId: resolvedId, stageCode, actorUserId: userId, actorRole: assignedRole || roleCode, @@ -136,7 +146,7 @@ const processStageDecision = async (params: { } } else { await db.StageApprovalAction.upsert({ - applicationId, + applicationId: resolvedId, interviewId: interviewId, stageCode, actorUserId: userId, @@ -153,14 +163,64 @@ const processStageDecision = async (params: { ); } + // --- FDD Integration: Link approval to FddReport table for dashboard mapping --- + if (stageCode === 'FDD_VERIFICATION' && decision === 'Approved') { + const assignment = await db.FddAssignment.findOne({ where: { applicationId: resolvedId } }); + if (assignment) { + // Find latest audit report document + const lastReportDoc = await db.OnboardingDocument.findOne({ + where: { applicationId: resolvedId, documentType: 'FDD Final Audit Report' }, + order: [['createdAt', 'DESC']] + }); + + // Parse structured recommendation/findings from remarks + let recommendation = 'Recommended'; + let findings = remarks || 'Submission reviewed.'; + if (remarks?.includes('[RECOMMENDATION:')) { + const parts = remarks.split('[RECOMMENDATION: '); + if (parts[1]) { + recommendation = parts[1].split(']')[0]; + findings = remarks.split('\nFindings: ')[1] || remarks.split(']')[1]?.trim() || remarks; + } + } + + await db.FddReport.create({ + assignmentId: assignment.id, + reportDocumentId: lastReportDoc?.id || null, + findings, + recommendation, + verifiedAt: new Date(), + verifiedBy: userId + }); + + await assignment.update({ status: 'Report Submitted' }); + + // Bridge: Initialize LOI Records for the next stage (Moved from fdd.controller.ts for Admin Review flow) + console.log(`[DEBUG] FDD Approved by Admin. Initializing LOI Records for Application: ${resolvedId}`); + const [loiReq] = await db.LoiRequest.findOrCreate({ + where: { applicationId: resolvedId }, + defaults: { status: 'Pending Approval', requestedBy: userId } + }); + + const nextRoles = ['DD Head', 'NBH']; + await Promise.all(nextRoles.map(async (role) => { + await db.LoiApproval.findOrCreate({ + where: { requestId: loiReq.id, approverRole: role }, + defaults: { action: 'Pending', level: 1 } + }); + })); + console.log(`[DEBUG] LOI Records initialized for ${nextRoles.join(', ')}`); + } + } + // Evaluate Policy via Centralized Service (FIXED unique user count) - const evaluation = await WorkflowService.evaluateStagePolicy(applicationId, stageCode); + const evaluation = await WorkflowService.evaluateStagePolicy(resolvedId, stageCode); const hasRejection = decision === 'Rejected'; // Immediate rejection if ANY required actor rejects (business rule) let statusUpdated = false; if (hasRejection) { - const application = await db.Application.findByPk(applicationId); + const application = await db.Application.findByPk(resolvedId); if (application) { await WorkflowService.transitionApplication(application, APPLICATION_STATUS.REJECTED, userId, { reason: `Rejected during ${stageCode} stage: ${remarks}`, @@ -169,7 +229,7 @@ const processStageDecision = async (params: { statusUpdated = true; } } else if (evaluation.policyMet) { - const application = await db.Application.findByPk(applicationId); + const application = await db.Application.findByPk(resolvedId); if (application) { let targetStatus = nextStatus; let targetStage = nextStage; @@ -181,7 +241,7 @@ const processStageDecision = async (params: { targetStage = APPLICATION_STAGES.LOI; targetProgress = 75; } else if (stageCode === 'LOA_APPROVAL') { - targetStatus = APPLICATION_STATUS.LOA_ISSUED; + targetStatus = APPLICATION_STATUS.EOR_IN_PROGRESS; targetStage = APPLICATION_STAGES.LOA; targetProgress = 95; } @@ -225,9 +285,9 @@ const processInterviewApprovalDecision = async (params: { // Ensure policy exists for interviews await ensureInterviewPolicy(interview.level); - const nextStatusMap: any = { 1: 'Level 1 Approved', 2: 'Level 2 Approved', 3: 'Level 3 Approved' }; + const nextStatusMap: any = { 1: 'Level 1 Approved', 2: 'Level 2 Approved', 3: 'FDD Verification' }; const nextStageMap: any = { 1: APPLICATION_STAGES.LEVEL_1_APPROVED, 2: APPLICATION_STAGES.LEVEL_2_APPROVED, 3: APPLICATION_STAGES.FDD }; - const progressMap: any = { 1: 40, 2: 55, 3: 70 }; + const progressMap: any = { 1: 40, 2: 55, 3: 65 }; const result = await processStageDecision({ applicationId: interview.applicationId, @@ -275,8 +335,9 @@ export const submitQuestionnaireResponse = async (req: AuthRequest, res: Respons const { applicationId, questionnaireId, responses } = req.body; // responses: [{ questionId, responseValue, attachmentUrl }] // Find application UUID first (handles readable ID) - const application = await db.Application.findOne({ - where: { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } + const _isUUID_qr = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(applicationId as string); + const application = await db.Application.findOne({ + where: _isUUID_qr ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId } }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); @@ -367,8 +428,9 @@ export const scheduleInterview = async (req: AuthRequest, res: Response) => { const levelNum = typeof level === 'string' ? parseInt(level.replace(/\D/g, ''), 10) : level; console.log(`Parsed Level: ${level} -> ${levelNum}`); - const application = await db.Application.findOne({ - where: { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } + const _isUUID_si = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(applicationId as string); + const application = await db.Application.findOne({ + where: _isUUID_si ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId } }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); @@ -1016,8 +1078,9 @@ export const submitStageDecision = async (req: AuthRequest, res: Response) => { if (result.noPolicy) { // Fallback: If no policy, just update application status directly (legacy behavior) if (nextStatus) { - const application = await db.Application.findOne({ - where: { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } + const _isUUID_fb = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(applicationId as string); + const application = await db.Application.findOne({ + where: _isUUID_fb ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId } }); if (application) { await WorkflowService.transitionApplication(application, nextStatus, req.user?.id || null, { diff --git a/src/modules/eor/eor.controller.ts b/src/modules/eor/eor.controller.ts index 0121aac..f3e9ed5 100644 --- a/src/modules/eor/eor.controller.ts +++ b/src/modules/eor/eor.controller.ts @@ -1,4 +1,5 @@ import { Request, Response } from 'express'; +import { Op } from 'sequelize'; import db from '../../database/models/index.js'; const { EorChecklist, EorChecklistItem, OnboardingDocument, RelocationDocument } = db; import { AuthRequest } from '../../types/express.types.js'; @@ -6,9 +7,24 @@ import { AuthRequest } from '../../types/express.types.js'; export const getChecklist = async (req: Request, res: Response) => { try { const { applicationId, relocationId } = req.params; + + // Resolve human-readable applicationId (e.g. APP-2026-79CE90) to UUID + let resolvedAppId = applicationId as string; + if (applicationId) { + const appIdStr = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(appIdStr); + if (!isUUID) { + const app = await db.Application.findOne({ where: { applicationId: appIdStr } }); + if (!app) { + res.status(404).json({ success: false, message: 'Application not found' }); + return; + } + resolvedAppId = app.id; + } + } + let checklist = await EorChecklist.findOne({ - where: relocationId ? { relocationId } : { applicationId }, - // proofDocument is now polymorphic, would need manual stitch or sub-selects + where: relocationId ? { relocationId } : { applicationId: resolvedAppId }, include: [{ model: EorChecklistItem, as: 'items' }] }); @@ -47,29 +63,36 @@ export const getChecklist = async (req: Request, res: Response) => { export const createChecklist = async (req: AuthRequest, res: Response) => { try { - const { applicationId, relocationId } = req.body; + const { applicationId: rawAppId, relocationId } = req.body; - if (!applicationId && !relocationId) { + if (!rawAppId && !relocationId) { return res.status(400).json({ success: false, message: 'applicationId or relocationId is required' }); } - if (applicationId) { - const application = await db.Application.findByPk(applicationId); + // Resolve applicationId to UUID (handles readable IDs like APP-2026-79CE90) + let resolvedAppId: string | null = null; + if (rawAppId) { + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(rawAppId); + const application = isUUID + ? await db.Application.findByPk(rawAppId) + : await db.Application.findOne({ where: { applicationId: rawAppId } }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); + resolvedAppId = application.id; } else if (relocationId) { const relocation = await db.RelocationRequest.findByPk(relocationId); if (!relocation) return res.status(404).json({ success: false, message: 'Relocation request not found' }); } const [checklist, created] = await EorChecklist.findOrCreate({ - where: relocationId ? { relocationId } : { applicationId }, + where: relocationId ? { relocationId } : { applicationId: resolvedAppId }, defaults: { status: 'In Progress', - applicationId: applicationId || null, + applicationId: resolvedAppId || null, relocationId: relocationId || null } }); + if (created) { // Define Default Mandatory Items per SRS/Frontend let defaultItems = []; @@ -111,6 +134,30 @@ export const createChecklist = async (req: AuthRequest, res: Response) => { })); await EorChecklistItem.bulkCreate(itemsData); + + // AUTO-MAP existing documents from OnboardingDocument table + if (resolvedAppId) { + const existingDocs = await OnboardingDocument.findAll({ + where: { applicationId: resolvedAppId, status: 'active' } + }); + + if (existingDocs.length > 0) { + const typeMap: any = { + 'GST Certificate': 'GST certificate including Accessories & Apparels billing', + 'Virtual Code Confirmation': 'Virtual code availability', + 'Trade Certificate': 'Trade certificate with test ride bikes registration', + 'DMS Infra Details': 'DMS infra' + }; + + for (const doc of existingDocs) { + const targetDescription = typeMap[doc.documentType] || doc.documentType; + await EorChecklistItem.update( + { proofDocumentId: doc.id }, + { where: { checklistId: checklist.id, description: { [Op.iLike]: targetDescription.trim() } } } + ); + } + } + } } // Status transition will be handled by the global handleApprove workflow or explicit trigger diff --git a/src/modules/fdd/fdd.controller.ts b/src/modules/fdd/fdd.controller.ts index d7c3e6d..50e5a95 100644 --- a/src/modules/fdd/fdd.controller.ts +++ b/src/modules/fdd/fdd.controller.ts @@ -1,4 +1,5 @@ import { Request, Response } from 'express'; +import { Op } from 'sequelize'; import db from '../../database/models/index.js'; const { FddAssignment, FddReport, AuditLog, Application } = db; import { AuthRequest } from '../../types/express.types.js'; @@ -8,8 +9,20 @@ import { WorkflowService } from '../../services/WorkflowService.js'; export const getAssignment = async (req: Request, res: Response) => { try { const { applicationId } = req.params; + const targetId = applicationId as string; + + // Resolve application first to get UUID + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } + const assignment = await FddAssignment.findOne({ - where: { applicationId }, + where: { applicationId: application.id }, include: [{ model: FddReport, as: 'reports' }] }); res.json({ success: true, data: assignment }); @@ -22,22 +35,30 @@ export const getAssignment = async (req: Request, res: Response) => { export const assignAgency = async (req: AuthRequest, res: Response) => { try { const { applicationId, assignedToAgency } = req.body; + const targetId = applicationId as string; + + // Resolve application first to get UUID + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } const assignment = await FddAssignment.create({ - applicationId, + applicationId: application.id, assignedToAgency, // Agency User ID status: 'Assigned' }); // Bridge: Transition application to active FDD stage - const application = await Application.findByPk(applicationId); - if (application) { - await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, { - reason: 'FDD Agency assigned. Initiating financial due diligence.', - stage: APPLICATION_STAGES.FDD, - progressPercentage: 70 - }); - } + await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, { + reason: 'FDD Agency assigned. Initiating financial due diligence.', + stage: APPLICATION_STAGES.FDD, + progressPercentage: 70 + }); await AuditLog.create({ userId: req.user?.id, @@ -72,70 +93,7 @@ export const uploadReport = async (req: AuthRequest, res: Response) => { { where: { id: assignmentId } } ); - // Transition Application status (AUTOMATION) - const assignmentRecord = await FddAssignment.findByPk(assignmentId); - if (assignmentRecord) { - const application = await Application.findByPk(assignmentRecord.applicationId); - if (application) { - // Ensure LOI Request exists for the next stage - const [loiRequest] = await db.LoiRequest.findOrCreate({ - where: { applicationId: application.id }, - defaults: { - requestedBy: req.user?.id, - status: 'In Progress' - } - }); - - // Pre-initialize Finance approval for LOI stage - await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_IN_PROGRESS, req.user?.id || null, { - reason: 'FDD Report submitted and verified. Moving to LOI Approval stage.', - stage: APPLICATION_STAGES.LOI, - progressPercentage: 65 - }); - - // Bridge 2.0: Automatically initialize LOI Records so the Initial Payment auto-approval finds them - console.log(`[DEBUG] Initializing LOI Records for Application: ${application.id}`); - const [loiReq] = await db.LoiRequest.findOrCreate({ - where: { applicationId: application.id }, - defaults: { status: 'Pending Approval' } - }); - console.log(`[DEBUG] LOI Request ID: ${loiReq.id}, Overall Status: ${loiReq.status}`); - - const roles = ['Finance', 'DD Head', 'NBH']; - await Promise.all(roles.map(async (role) => { - let action = 'Pending'; - let comments = null; - - if (role === 'Finance') { - console.log(`[DEBUG] Checking for existing verified INITIAL deposit for ${application.id}`); - const verifiedDeposit = await db.SecurityDeposit.findOne({ - where: { applicationId: application.id, depositType: 'INITIAL', status: 'Verified' } - }); - if (verifiedDeposit) { - console.log(`[DEBUG] FOUND VERIFIED DEPOSIT! Auto-approving Finance role in LOI.`); - action = 'Approved'; - comments = 'Auto-approved: Initial Security Deposit already verified.'; - } else { - console.log(`[DEBUG] NO Verified INITIAL deposit found during FDD upload.`); - } - } - - const [approval, created] = await db.LoiApproval.findOrCreate({ - where: { requestId: loiReq.id, approverRole: role }, - defaults: { action, comments, level: 1 } - }); - console.log(`[DEBUG] Role ${role}: Status=${approval.action} (Created: ${created})`); - return approval; - })); - - // If Finance was auto-approved, trigger policy evaluation - console.log(`[DEBUG] Finalizing FDD Upload -> Evaluating Stage Policy for LOI_APPROVAL`); - const evalResult = await WorkflowService.evaluateStagePolicy(application.id, 'LOI_APPROVAL'); - console.log(`[DEBUG] Policy Met: ${evalResult.policyMet}, Approved Roles: ${Array.from(evalResult.approvedRoles || [])}`); - } - } - - res.status(201).json({ success: true, message: 'FDD Report uploaded', data: report }); + res.status(201).json({ success: true, message: 'FDD Report uploaded successfully. Pending Admin review.', data: report }); } catch (error) { console.error('Upload FDD report error:', error); res.status(500).json({ success: false, message: 'Error uploading report' }); diff --git a/src/modules/loa/loa.controller.ts b/src/modules/loa/loa.controller.ts index 947400d..f44f3a7 100644 --- a/src/modules/loa/loa.controller.ts +++ b/src/modules/loa/loa.controller.ts @@ -26,8 +26,20 @@ const ensureLoaPolicy = async () => { export const getRequest = async (req: Request, res: Response) => { try { const { applicationId } = req.params; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + // Resolve application first to get UUID + const application = await db.Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } + const request = await LoaRequest.findOne({ - where: { applicationId }, + where: { applicationId: application.id }, include: [ { model: LoaApproval, as: 'approvals' }, { model: LoaDocumentGenerated, as: 'generatedDocuments' } @@ -43,12 +55,17 @@ export const getRequest = async (req: Request, res: Response) => { export const createRequest = async (req: AuthRequest, res: Response) => { try { const { applicationId } = req.body; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + const application = await db.Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); - const application = await db.Application.findByPk(applicationId); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); const [request, created] = await LoaRequest.findOrCreate({ - where: { applicationId }, + where: { applicationId: application.id }, defaults: { requestedBy: req.user?.id, status: 'In Progress' @@ -176,8 +193,8 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { const application = await db.Application.findByPk(request.applicationId); if (application) { - await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOA_ISSUED, req.user.id, { - reason: 'LOA fully approved and issued', + await WorkflowService.transitionApplication(application, APPLICATION_STATUS.EOR_IN_PROGRESS, req.user.id, { + reason: 'LOA fully approved. Moving to EOR Work.', progressPercentage: 97 }); } @@ -205,9 +222,20 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { export const getApprovalStatus = async (req: AuthRequest, res: Response) => { try { const { applicationId } = req.params; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + const application = await db.Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } + const policy = await ensureLoaPolicy(); const actions = await StageApprovalAction.findAll({ - where: { applicationId, stageCode: LOA_STAGE_CODE }, + where: { applicationId: application.id, stageCode: LOA_STAGE_CODE }, include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }], order: [['updatedAt', 'DESC']] }); @@ -285,55 +313,15 @@ export const updateSecurityDeposit = async (req: AuthRequest, res: Response) => }); // --- AUTOMATION: After verification transitions --- - - // 1. If INITIAL Payment Verified -> Approve LOI Finance Role - // Bridge 1.0: AUTOMATED LOI APPROVAL IF INITIAL PAYMENT IS VERIFIED - console.log(`[DEBUG] Payment Verification Trace -> Deposit Type: ${depositType}, Status: ${status}`); + + // 1. If INITIAL Payment Verified -> Move to LOI Issue Stage if ((depositType === 'INITIAL' || !depositType) && status === 'Verified') { - console.log(`[DEBUG] Initial Deposit VERIFIED for Application: ${application.id}. Ensuring LOI records exist...`); - const LoiRequest = db.LoiRequest; - const LoiApproval = db.LoiApproval; - - // 1. Proactively ensure the LOI Request exists if the payment is cleared - const [loiReq, createdReq] = await db.LoiRequest.findOrCreate({ - where: { applicationId: application.id }, - defaults: { status: 'Pending Approval' } + console.log(`[DEBUG] Initial Security Deposit verified. Moving to LOI Issued stage...`); + await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_ISSUED, req.user?.id || null, { + reason: 'Initial Security Deposit verified. Proceeding to LOI Issuance.', + stage: APPLICATION_STAGES.LOI, + progressPercentage: 80 }); - console.log(`[DEBUG] LOI Request ID: ${loiReq.id}, Status: ${loiReq.status} (Created: ${createdReq})`); - - // 2. Initialize the three required approval roles for the LOI step - const roles = ['Finance', 'DD Head', 'NBH']; - await Promise.all(roles.map(async (role) => { - const [approval, created] = await db.LoiApproval.findOrCreate({ - where: { requestId: loiReq.id, approverRole: role }, - defaults: { action: 'Pending', level: 1 } - }); - console.log(`[DEBUG] Role ${role}: Status=${approval.action} (Created: ${created})`); - })); - - // 3. Mark the Finance role as Approved based on this verified payment - const financeApproval = await db.LoiApproval.findOne({ - where: { requestId: loiReq.id, approverRole: 'Finance' } - }); - - if (financeApproval) { - console.log(`[DEBUG] Marking Finance Approval record as Approved...`); - await financeApproval.update({ - action: 'Approved', - actorUserId: req.user?.id, - actionedAt: new Date(), - comments: 'Initial Security Deposit verified.' - }); - - console.log(`[DEBUG] Initial Security Deposit verified. Transitioning to LOI Issued...`); - await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_ISSUED, req.user?.id || null, { - reason: 'Initial Security Deposit verified. Proceeding to LOI Issuance.', - stage: APPLICATION_STAGES.LOI, - progressPercentage: 80 - }); - } else { - console.log(`[DEBUG] No pending Finance approval in LOI stage. Skipping auto-bridge.`); - } } // 2. If FINAL Payment Verified -> Move to LOA Pending stage diff --git a/src/modules/loi/loi.controller.ts b/src/modules/loi/loi.controller.ts index 33ba022..a284e3e 100644 --- a/src/modules/loi/loi.controller.ts +++ b/src/modules/loi/loi.controller.ts @@ -12,20 +12,41 @@ const ensureLoiPolicy = async () => { where: { stageCode: LOI_STAGE_CODE }, defaults: { stageCode: LOI_STAGE_CODE, - minApprovals: 3, + minApprovals: 2, approvalMode: 'ROLE_MANDATORY', - requiredRoles: ['Finance', 'DD Head', 'NBH'], + requiredRoles: ['DD Head', 'NBH'], isActive: true } }); + + // If policy already exists but has Finance, update it + if (policy && Array.isArray(policy.requiredRoles) && policy.requiredRoles.includes('Finance')) { + await policy.update({ + requiredRoles: ['DD Head', 'NBH'], + minApprovals: 2 + }); + } + return policy; }; export const getRequest = async (req: Request, res: Response) => { try { const { applicationId } = req.params; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + // Resolve application first to get UUID + const application = await db.Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } + const request = await LoiRequest.findOne({ - where: { applicationId }, + where: { applicationId: application.id }, include: [ { model: LoiApproval, as: 'approvals' }, { model: LoiDocumentGenerated, as: 'generatedDocuments' }, @@ -73,33 +94,38 @@ export const acknowledgeRequest = async (req: AuthRequest, res: Response) => { export const createRequest = async (req: AuthRequest, res: Response) => { try { const { applicationId } = req.body; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + const application = await db.Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); - const application = await db.Application.findByPk(applicationId); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); const [request, created] = await LoiRequest.findOrCreate({ - where: { applicationId }, + where: { applicationId: application.id }, defaults: { requestedBy: req.user?.id, status: 'In Progress' } }); - // Initialize first level approval (Finance) if not already exists + // Initialize first level approval (DD Head) if not already exists await LoiApproval.findOrCreate({ where: { requestId: request.id, level: 1 }, defaults: { - approverRole: 'Finance', + approverRole: 'DD Head', action: 'Pending' } }); await WorkflowService.transitionApplication(application, APPLICATION_STATUS.LOI_IN_PROGRESS, req.user?.id || null, { - reason: 'LOI Request initiated with Finance approval', + reason: 'LOI Request initiated for DD Head approval', progressPercentage: 75 }); - res.status(201).json({ success: true, message: 'LOI Request initiated with Finance approval', data: request }); + res.status(201).json({ success: true, message: 'LOI Request initiated for DD Head approval', data: request }); } catch (error) { console.error('Create LOI request error:', error); res.status(500).json({ success: false, message: 'Error creating LOI request' }); @@ -260,9 +286,20 @@ export const approveRequest = async (req: AuthRequest, res: Response) => { export const getApprovalStatus = async (req: AuthRequest, res: Response) => { try { const { applicationId } = req.params; + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + + const application = await db.Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + + if (!application) { + return res.status(404).json({ success: false, message: 'Application not found' }); + } + const policy = await ensureLoiPolicy(); const actions = await StageApprovalAction.findAll({ - where: { applicationId, stageCode: LOI_STAGE_CODE }, + where: { applicationId: application.id, stageCode: LOI_STAGE_CODE }, include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }], order: [['updatedAt', 'DESC']] }); diff --git a/src/modules/onboarding/onboarding.controller.ts b/src/modules/onboarding/onboarding.controller.ts index bf5dd91..76bb552 100644 --- a/src/modules/onboarding/onboarding.controller.ts +++ b/src/modules/onboarding/onboarding.controller.ts @@ -264,7 +264,7 @@ export const getApplicationById = async (req: AuthRequest, res: Response) => { 'GST Certificate', 'PAN Card', 'Bank Statement', 'Cancelled Check', 'Partnership Deed', 'LLP Agreement', 'Certificate of Incorporation', 'MOA', 'AOA', 'Property Documents', 'Rental Agreement', 'Firm Registration', 'CIBIL Report', - 'FDD Final Audit Report', 'FDD Audit Report' + 'FDD Final Audit Report', 'FDD Audit Report', 'Income Tax Returns (ITR)', 'Business Valuation Report' ]; if (restrictedData.uploadedDocuments) { @@ -302,17 +302,21 @@ export const getApplicationById = async (req: AuthRequest, res: Response) => { export const updateApplicationStatus = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; + const targetId = id as string; const { status, stage, reason } = req.body; - const application = await Application.findByPk(id); + // Resolve application by ID (UUID) or Registeration Number (applicationId) + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); - if (application) { - await WorkflowService.transitionApplication(application, status, req.user?.id || null, { - reason: reason || 'Manual Status Update', - stage: stage - }); - } + await WorkflowService.transitionApplication(application, status, req.user?.id || null, { + reason: reason || 'Manual Status Update', + stage: stage + }); res.json({ success: true, message: 'Application status updated successfully' }); } catch (error) { @@ -335,13 +339,10 @@ export const uploadDocuments = async (req: any, res: Response) => { return res.status(400).json({ success: false, message: 'Document type is required' }); } + const targetId = id as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } }); if (!application) { @@ -402,11 +403,19 @@ export const uploadDocuments = async (req: any, res: Response) => { await db.EorChecklistItem.bulkCreate(itemsData); } - // Update the matching item - Link only, don't auto-verify (requested by user) - await db.EorChecklistItem.update( + console.log(`[debug] EOR Checklist found/created: ${checklist.id} for Application: ${application.id}`); + + const [updatedCount] = await db.EorChecklistItem.update( { proofDocumentId: newDoc.id, isCompliant: false }, - { where: { checklistId: checklist.id, description: documentType } } + { + where: { + checklistId: checklist.id, + description: { [Op.iLike]: documentType.trim() } + } + } ); + + console.log(`[debug] EOR items updated: ${updatedCount} for type: ${documentType}`); } res.status(201).json({ @@ -424,14 +433,12 @@ export const getApplicationDocuments = async (req: AuthRequest, res: Response) = try { const { id } = req.params; + const targetId = id as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + // Resolve ID to primary key if it's an appId string const application = await Application.findOne({ - where: { - [Op.or]: [ - { id }, - { applicationId: id } - ] - } + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } }); if (!application) { @@ -484,8 +491,9 @@ export const bulkShortlist = async (req: AuthRequest, res: Response) => { // Update Applications sequentially via WorkflowService for consistency for (const appId of applicationIds) { + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(appId); const application = await Application.findOne({ - where: { [Op.or]: [{ id: appId }, { applicationId: appId }] } + where: isUUID ? { [Op.or]: [{ id: appId }, { applicationId: appId }] } : { applicationId: appId } }); if (application) { await application.update({ @@ -541,8 +549,9 @@ export const bulkShortlist = async (req: AuthRequest, res: Response) => { const assignStageEvaluators = async (appIdOrId: string) => { try { console.log(`[debug] Starting stage evaluator assignment for App: ${appIdOrId}`); + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(appIdOrId); const application = await Application.findOne({ - where: { [Op.or]: [{ id: appIdOrId }, { applicationId: appIdOrId }] }, + where: isUUID ? { [Op.or]: [{ id: appIdOrId }, { applicationId: appIdOrId }] } : { applicationId: appIdOrId }, include: [ { model: District, @@ -693,14 +702,18 @@ const assignStageEvaluators = async (appIdOrId: string) => { export const retriggerEvaluators = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; - const application = await Application.findByPk(id); + const targetId = id as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); // Remove existing auto-mapped participants (Interviews, LOI, LOA) // Using a more robust Postgres-compatible JSON path check await db.RequestParticipant.destroy({ where: { - requestId: id, + requestId: application.id, requestType: 'application', joinedMethod: 'auto', [Op.and]: [ @@ -726,6 +739,7 @@ export const retriggerEvaluators = async (req: AuthRequest, res: Response) => { export const assignArchitectureTeam = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; + const targetId = id as string; const { userId, assignedTo, remarks } = req.body; const targetUserId = userId || assignedTo; @@ -733,7 +747,10 @@ export const assignArchitectureTeam = async (req: AuthRequest, res: Response) => return res.status(400).json({ success: false, message: 'Architecture team member (userId) is required' }); } - const application = await Application.findByPk(id); + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); await application.update({ @@ -768,9 +785,13 @@ export const assignArchitectureTeam = async (req: AuthRequest, res: Response) => export const updateArchitectureStatus = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; + const targetId = id as string; const { status, remarks } = req.body; - const application = await Application.findByPk(id); + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); const updateData: any = { @@ -805,8 +826,12 @@ import { ExternalMocksService } from '../../common/utils/externalMocks.service.j export const generateDealerCodes = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; // applicationId + const targetId = id as string; - const application = await Application.findByPk(id); + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); // Trigger Mock SAP Integration @@ -815,7 +840,7 @@ export const generateDealerCodes = async (req: AuthRequest, res: Response) => { // Save Dealer Codes await db.DealerCode.create({ dealerCode: sapData.salesCode, // Use sales code as primary dealer code - applicationId: id, + applicationId: application.id, salesCode: sapData.salesCode, serviceCode: sapData.serviceCode, gmaCode: sapData.gmaCode, @@ -827,7 +852,7 @@ export const generateDealerCodes = async (req: AuthRequest, res: Response) => { // Create Final Security Deposit record (Blocker for LOA) await db.SecurityDeposit.findOrCreate({ - where: { applicationId: id, depositType: 'FINAL' }, + where: { applicationId: application.id, depositType: 'FINAL' }, defaults: { amount: 1500000, // 15 Lakhs Final status: 'Pending' @@ -959,7 +984,12 @@ export const deleteDocumentConfig = async (req: AuthRequest, res: Response) => { export const updateApplication = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; - const application = await Application.findByPk(id); + const targetId = id as string; + + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + const application = await Application.findOne({ + where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); if (!application) return res.status(404).json({ success: false, message: 'Application not found' }); await application.update(req.body); diff --git a/src/modules/onboarding/questionnaire.controller.ts b/src/modules/onboarding/questionnaire.controller.ts index 2d81a7d..a245735 100644 --- a/src/modules/onboarding/questionnaire.controller.ts +++ b/src/modules/onboarding/questionnaire.controller.ts @@ -85,8 +85,14 @@ export const submitResponse = async (req: AuthRequest, res: Response) => { try { const { applicationId, responses } = req.body; // responses: [{ questionId, value }] + const targetId = applicationId as string; + const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(targetId); + // Verify application - const application = await Application.findByPk(applicationId); + const application = await Application.findOne({ + where: isUUID ? { [db.Sequelize.Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId } + }); + if (!application) { return res.status(404).json({ success: false, message: 'Application not found' }); } @@ -96,7 +102,7 @@ export const submitResponse = async (req: AuthRequest, res: Response) => { if (!questionnaire) return res.status(400).json({ success: false, message: 'No active questionnaire' }); const responseRecords = responses.map((r: any) => ({ - applicationId, + applicationId: application.id, questionnaireId: questionnaire.id, questionId: r.questionId, responseValue: r.value,