diff --git a/src/modules/master/master.controller.ts b/src/modules/master/master.controller.ts index d280dcc..de17534 100644 --- a/src/modules/master/master.controller.ts +++ b/src/modules/master/master.controller.ts @@ -78,6 +78,16 @@ export const getAreas = async (req: Request, res: Response) => { ]; } + const stateId = req.query.stateId as string; + const zoneId = req.query.zoneId as string; + const regionId = req.query.regionId as string; + const isActive = req.query.isActive as string; + + if (stateId) where['$district.stateId$'] = stateId; + if (zoneId) where['$district.zoneId$'] = zoneId; + if (regionId) where['$district.regionId$'] = regionId; + if (isActive !== undefined) where.isActive = isActive === 'true'; + const { count, rows: areas } = await db.Location.findAndCountAll({ where, include: [ diff --git a/src/modules/onboarding/onboarding.controller.ts b/src/modules/onboarding/onboarding.controller.ts index 46dbcbd..e579f1c 100644 --- a/src/modules/onboarding/onboarding.controller.ts +++ b/src/modules/onboarding/onboarding.controller.ts @@ -1,6 +1,6 @@ import { Request, Response } from 'express'; import db from '../../database/models/index.js'; -const { Application, Opportunity, ApplicationStatusHistory, ApplicationProgress, AuditLog, District, State, Region, Zone, SecurityDeposit, FddAssignment, FddReport, OnboardingDocument, Worknote, StageApprovalAction, DealerCode, Dealer, RequestParticipant, QuestionnaireResponse, QuestionnaireQuestion, QuestionnaireOption, User } = db; +const { Application, Opportunity, ApplicationStatusHistory, ApplicationProgress, AuditLog, District, State, Region, Zone, SecurityDeposit, FddAssignment, FddReport, OnboardingDocument, Worknote, StageApprovalAction, DealerCode, Dealer, RequestParticipant, QuestionnaireResponse, QuestionnaireQuestion, QuestionnaireOption, Questionnaire, User } = db; import { AUDIT_ACTIONS, APPLICATION_STAGES, APPLICATION_STATUS } from '../../common/config/constants.js'; import { v4 as uuidv4 } from 'uuid'; import { Op } from 'sequelize'; @@ -1121,3 +1121,72 @@ export const updateApplication = async (req: AuthRequest, res: Response) => { return res.status(500).json({ success: false, message: 'Internal server error' }); } }; +export const exportApplicationResponses = async (req: AuthRequest, res: Response) => { + try { + const { applicationIds } = req.query; + if (!applicationIds) return res.status(400).json({ success: false, message: 'No applications selected' }); + + const ids = (applicationIds as string).split(','); + + // 1. Fetch Applications with their identification details + const applications = await Application.findAll({ + where: { + id: { [Op.in]: ids }, + overallStatus: { [Op.ne]: APPLICATION_STATUS.QUESTIONNAIRE_PENDING } + }, + attributes: ['id', 'applicationId', 'applicantName', 'preferredLocation', 'email', 'phone'] + }); + + if (applications.length === 0 && ids.length > 0) { + return res.status(400).json({ + success: false, + message: 'No applications found with completed questionnaires among the selected records.' + }); + } + + // 2. Fetch all unique questions involved in these applications + // For consistency, let's get all questions from the active questionnaire + const activeQuestionnaire = await Questionnaire.findOne({ where: { isActive: true }, order: [['version', 'DESC']] }); + if (!activeQuestionnaire) return res.status(404).json({ success: false, message: 'Active questionnaire not found' }); + + const allQuestions = await QuestionnaireQuestion.findAll({ + where: { questionnaireId: activeQuestionnaire.id }, + order: [['order', 'ASC']] + }); + + // 3. Fetch all responses for these applications + const responses = await QuestionnaireResponse.findAll({ + where: { applicationId: { [Op.in]: ids } }, + attributes: ['applicationId', 'questionId', 'responseValue'] + }); + + // 4. Map responses for quick lookup: applicationid -> questionid -> responseValue + const responsesMap: any = {}; + responses.forEach((r: any) => { + if (!responsesMap[r.applicationId]) responsesMap[r.applicationId] = {}; + responsesMap[r.applicationId][r.questionId] = r.responseValue; + }); + + // 5. Construct rows + const rows = applications.map((app: any) => { + const data: any = { + 'Application ID': app.applicationId, + 'Applicant Name': app.applicantName, + 'Email': app.email, + 'Phone': app.phone, + 'Preferred Location': app.preferredLocation + }; + + allQuestions.forEach((q: any) => { + data[q.questionText] = responsesMap[app.id]?.[q.id] || 'Not Answered'; + }); + + return data; + }); + + res.json({ success: true, data: rows }); + } catch (error) { + console.error('Export error:', error); + res.status(500).json({ success: false, message: 'Error exporting data' }); + } +}; diff --git a/src/modules/onboarding/onboarding.routes.ts b/src/modules/onboarding/onboarding.routes.ts index 1cd68a6..a45edfe 100644 --- a/src/modules/onboarding/onboarding.routes.ts +++ b/src/modules/onboarding/onboarding.routes.ts @@ -5,7 +5,8 @@ import { uploadDocuments, getApplicationDocuments, bulkShortlist, assignArchitectureTeam, updateArchitectureStatus, generateDealerCodes, retriggerEvaluators, getDocumentConfigs, getDocumentConfigMetadata, - createDocumentConfig, updateDocumentConfig, deleteDocumentConfig, updateApplication + createDocumentConfig, updateDocumentConfig, deleteDocumentConfig, updateApplication, + exportApplicationResponses } from './onboarding.controller.js'; import { authenticate } from '../../common/middleware/auth.js'; @@ -23,6 +24,7 @@ router.put('/document-configs/:id', updateDocumentConfig); router.delete('/document-configs/:id', deleteDocumentConfig); router.get('/applications', getApplications); +router.get('/applications/export-responses', exportApplicationResponses); router.get('/document-configs/metadata', getDocumentConfigMetadata); router.get('/document-configs', getDocumentConfigs); router.post('/applications/shortlist', bulkShortlist); // Existing route, updated to named import