import { Request, Response } from 'express'; import db from '../../database/models/index.js'; const { Questionnaire, QuestionnaireQuestion, QuestionnaireResponse, QuestionnaireScore, Interview, InterviewEvaluation, InterviewParticipant, AiSummary, User, RequestParticipant, Role } = db; import { AuthRequest } from '../../types/express.types.js'; import { Op } from 'sequelize'; import * as EmailService from '../../common/utils/email.service.js'; // --- Questionnaires --- export const getQuestionnaire = async (req: Request, res: Response) => { try { const { version } = req.query; const where: any = { isActive: true }; if (version) where.version = version; const questionnaire = await Questionnaire.findOne({ where, include: [{ model: QuestionnaireQuestion, as: 'questions' }], order: [['createdAt', 'DESC']] // GET latest if no version }); res.json({ success: true, data: questionnaire }); } catch (error) { console.error('Get questionnaire error:', error); res.status(500).json({ success: false, message: 'Error fetching questionnaire' }); } }; export const submitQuestionnaireResponse = async (req: AuthRequest, res: Response) => { try { const { applicationId, questionnaireId, responses } = req.body; // responses: [{ questionId, responseValue, attachmentUrl }] // Calculate score logic (Placeholder) let calculatedScore = 0; let totalWeight = 0; for (const resp of responses) { await QuestionnaireResponse.create({ applicationId, questionnaireId, questionId: resp.questionId, responseValue: resp.responseValue, attachmentUrl: resp.attachmentUrl }); // Add scoring logic here based on question type/weight } // Create Score Record await QuestionnaireScore.create({ applicationId, questionnaireId, score: calculatedScore, maxScore: 100, // Placeholder status: 'Completed' }); res.status(201).json({ success: true, message: 'Responses submitted successfully' }); } catch (error) { console.error('Submit response error:', error); res.status(500).json({ success: false, message: 'Error submitting responses' }); } }; // --- Interviews --- export const scheduleInterview = async (req: AuthRequest, res: Response) => { try { console.log('---------------------------------------------------'); console.log('Incoming Schedule Interview Request:', JSON.stringify(req.body, null, 2)); const { applicationId, level, scheduledAt, type, location, participants } = req.body; // participants: [userId] // Parse level string (e.g., "level1") to integer if necessary const levelNum = typeof level === 'string' ? parseInt(level.replace(/\D/g, ''), 10) : level; console.log(`Parsed Level: ${level} -> ${levelNum}`); console.log('Creating Interview record...'); const interview = await Interview.create({ applicationId, level: levelNum || 1, // Default to 1 if parsing fails scheduleDate: new Date(scheduledAt), interviewType: type, linkOrLocation: location, status: 'Scheduled', scheduledBy: req.user?.id }); console.log('Interview created with ID:', interview.id); // Update Application Status const statusMap: any = { 1: 'Level 1 Interview Pending', 2: 'Level 2 Interview Pending', 3: 'Level 3 Interview Pending' }; const newStatus = statusMap[levelNum] || 'Interview Scheduled'; await db.Application.update({ overallStatus: newStatus }, { where: { id: applicationId } }); if (participants && participants.length > 0) { console.log(`Processing ${participants.length} participants...`); for (const userId of participants) { // 1. Add to Panel await InterviewParticipant.create({ interviewId: interview.id, userId, role: 'Panelist' }); // 2. Add as Request Participant for Collaboration console.log(`Adding user ${userId} to RequestParticipant...`); await RequestParticipant.findOrCreate({ where: { requestId: applicationId, requestType: 'application', userId }, defaults: { participantType: 'contributor', // 'interviewer' is not a valid enum value joinedMethod: 'interview' } }); } } // Fetch application and user email for notification const application = await db.Application.findByPk(applicationId); if (application) { await EmailService.sendInterviewScheduledEmail( application.email, application.name, application.applicationId || application.id, interview ); } // Notify panelists if needed if (participants && participants.length > 0) { for (const userId of participants) { const panelist = await User.findByPk(userId); if (panelist) { await EmailService.sendInterviewScheduledEmail( panelist.email, panelist.fullName, application?.applicationId || application?.id || applicationId, interview ); } } } console.log('Interview scheduling completed successfully.'); res.status(201).json({ success: true, message: 'Interview scheduled successfully', data: interview }); } catch (error) { console.error('CRITICAL ERROR in scheduleInterview:', error); // Log the full error object for inspection console.log(JSON.stringify(error, null, 2)); res.status(500).json({ success: false, message: 'Error scheduling interview', error: String(error) }); } }; export const updateInterview = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; const { status, scheduledAt, outcome } = req.body; const interview = await Interview.findByPk(id); if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' }); await interview.update({ status, scheduledAt, outcome }); res.json({ success: true, message: 'Interview updated successfully' }); } catch (error) { console.error('Update interview error:', error); res.status(500).json({ success: false, message: 'Error updating interview' }); } }; export const submitEvaluation = async (req: AuthRequest, res: Response) => { try { const { id } = req.params; // Interview ID const { ktScore, feedback, recommendation, status } = req.body; const interview = await Interview.findByPk(id); if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' }); const evaluation = await InterviewEvaluation.create({ interviewId: id, evaluatorId: req.user?.id, ktMatrixScore: ktScore, qualitativeFeedback: feedback, recommendation }); // Auto update interview status if completed if (status === 'Completed') { await interview.update({ status: 'Completed', outcome: recommendation }); } res.status(201).json({ success: true, message: 'Evaluation submitted successfully', data: evaluation }); } catch (error) { console.error('Submit evaluation error:', error); res.status(500).json({ success: false, message: 'Error submitting evaluation' }); } }; export const submitKTMatrix = async (req: AuthRequest, res: Response) => { try { const { interviewId, criteriaScores, feedback, recommendation } = req.body; // criteriaScores: [{ criterionName, score, maxScore, weightage }] const interview = await Interview.findByPk(interviewId); if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' }); // Calculate total weighted score let totalWeightedScore = 0; const totalWeightage = criteriaScores.reduce((sum: number, item: any) => sum + item.weightage, 0); const ktMatrixScoresData = criteriaScores.map((item: any) => { const weightedScore = (item.score / item.maxScore) * item.weightage; totalWeightedScore += weightedScore; return { criterionName: item.criterionName, score: item.score, maxScore: item.maxScore, weightage: item.weightage, weightedScore }; }); // Check if evaluation exists for this user and interview, if so update, else create let evaluation = await InterviewEvaluation.findOne({ where: { interviewId, evaluatorId: req.user?.id } }); if (evaluation) { await evaluation.update({ ktMatrixScore: totalWeightedScore, qualitativeFeedback: feedback, recommendation }); // Remove old details to replace with new await db.KTMatrixScore.destroy({ where: { evaluationId: evaluation.id } }); } else { evaluation = await InterviewEvaluation.create({ interviewId, evaluatorId: req.user?.id, ktMatrixScore: totalWeightedScore, qualitativeFeedback: feedback, recommendation }); } // Bulk create detailed scores const scoreRecords = ktMatrixScoresData.map((s: any) => ({ ...s, evaluationId: evaluation?.id })); await db.KTMatrixScore.bulkCreate(scoreRecords); res.status(201).json({ success: true, message: 'KT Matrix submitted successfully', data: evaluation }); } catch (error) { console.error('Submit KT Matrix error:', error); res.status(500).json({ success: false, message: 'Error submitting KT Matrix' }); } }; export const submitLevel2Feedback = async (req: AuthRequest, res: Response) => { try { const { interviewId, feedbackItems, recommendation, overallScore } = req.body; // feedbackItems: [{ type: 'Strategic Vision', comments: '...' }] const interview = await Interview.findByPk(interviewId); if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' }); // Check if evaluation exists for this user and interview, if so update, else create let evaluation = await InterviewEvaluation.findOne({ where: { interviewId, evaluatorId: req.user?.id } }); if (evaluation) { await evaluation.update({ ktMatrixScore: overallScore, // Reusing this field for overall score (check if type matches) recommendation }); // Remove old details to replace with new await db.InterviewFeedback.destroy({ where: { evaluationId: evaluation.id } }); } else { evaluation = await InterviewEvaluation.create({ interviewId, evaluatorId: req.user?.id, ktMatrixScore: overallScore, recommendation }); } // Bulk create detailed qualitative feedback if (feedbackItems && feedbackItems.length > 0) { const feedbackRecords = feedbackItems.map((item: any) => ({ evaluationId: evaluation?.id, feedbackType: item.type, comments: item.comments })); await db.InterviewFeedback.bulkCreate(feedbackRecords); } res.status(201).json({ success: true, message: 'Level 2 Feedback submitted successfully', data: evaluation }); } catch (error) { console.error('Submit Level 2 Feedback error:', error); res.status(500).json({ success: false, message: 'Error submitting Level 2 Feedback' }); } }; // --- AI Summary --- export const getAiSummary = async (req: Request, res: Response) => { try { const { applicationId } = req.params; const summary = await AiSummary.findOne({ where: { applicationId }, order: [['createdAt', 'DESC']] }); res.json({ success: true, data: summary }); } catch (error) { console.error('Get AI summary error:', error); res.status(500).json({ success: false, message: 'Error fetching AI summary' }); } }; export const getInterviews = async (req: Request, res: Response) => { try { const { applicationId } = req.params; const interviews = await Interview.findAll({ where: { applicationId }, include: [ { model: InterviewParticipant, as: 'participants', include: [{ model: User, as: 'user' }] // Assuming association exists }, { model: InterviewEvaluation, as: 'evaluations', include: [{ model: User, as: 'evaluator', attributes: ['id', 'fullName', 'email'], include: [{ model: Role, as: 'role', attributes: ['roleName', 'roleCode'] }] }, { model: db.InterviewFeedback, as: 'feedbackDetails' }] }, { model: User, as: 'scheduler', attributes: ['id', 'fullName', 'email', 'designation'] } ], order: [['createdAt', 'DESC']] }); res.json({ success: true, data: interviews }); } catch (error) { console.error('Get interviews error:', error); res.status(500).json({ success: false, message: 'Error fetching interviews' }); } };