Dealer_Onboarding_Backend/src/modules/assessment/assessment.controller.ts

374 lines
14 KiB
TypeScript

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' });
}
};