Dealer_Onboarding_Backend/src/modules/fdd/fdd.controller.ts

255 lines
11 KiB
TypeScript

import { Request, Response } from 'express';
import { Op } from 'sequelize';
import db from '../../database/models/index.js';
const { FddAssignment, FddReport, Application } = db;
import { AuthRequest } from '../../types/express.types.js';
import { AUDIT_ACTIONS, APPLICATION_STATUS, APPLICATION_STAGES, ROLES } from '../../common/config/constants.js';
import { WorkflowService } from '../../services/WorkflowService.js';
import { pickApplicationAuditContext, safeAuditLogCreate } from '../../services/applicationAuditLog.service.js';
import { NotificationService } from '../../services/NotificationService.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: application.id },
include: [{
model: FddReport,
as: 'reports',
include: [
{ model: db.User, as: 'submitter', attributes: ['fullName'] },
{ model: db.User, as: 'verifier', attributes: ['fullName'] }
]
}]
});
res.json({ success: true, data: assignment });
} catch (error) {
console.error('Get FDD assignment error:', error);
res.status(500).json({ success: false, message: 'Error fetching FDD assignment' });
}
};
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: application.id,
assignedToAgency, // Agency User ID
status: 'Assigned'
});
// Bridge: Transition application to active FDD stage
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 safeAuditLogCreate({
userId: req.user?.id,
action: AUDIT_ACTIONS.FDD_ASSIGNED,
entityType: 'fdd_assignment',
entityId: assignment.id,
newData: { assignedToAgency, applicationId: application.id },
});
await safeAuditLogCreate({
userId: req.user?.id,
action: AUDIT_ACTIONS.FDD_ASSIGNED,
entityType: 'application',
entityId: application.id,
newData: {
assignmentId: assignment.id,
assignedToAgency,
note: 'FDD agency assigned; application moved to FDD verification.',
context: pickApplicationAuditContext(application),
},
});
// SRS §6.15.3.1 — Notify assigned FDD agency user of their assignment
const fddUser = await db.User.findByPk(assignedToAgency);
if (fddUser) {
const phone = (fddUser as any).mobileNumber || null;
NotificationService.notify(fddUser.id, fddUser.email, {
title: `FDD Assignment: ${application.applicationId}`,
message: `You have been assigned to conduct Financial Due Diligence for application ${application.applicationId}.`,
channels: phone ? ['system', 'email', 'whatsapp'] : ['system', 'email'],
templateCode: 'USER_ASSIGNED',
placeholders: {
applicantName: application.applicantName || '',
applicationId: application.applicationId,
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
ctaLabel: 'View Assignment',
phone: phone || ''
}
}).catch((e: any) => console.error('[FDD] Agency notify failed:', e));
}
res.status(201).json({ success: true, message: 'FDD Agency assigned', data: assignment });
} catch (error) {
console.error('Assign FDD agency error:', error);
res.status(500).json({ success: false, message: 'Error assigning agency' });
}
};
export const uploadReport = async (req: AuthRequest, res: Response) => {
try {
const { assignmentId, reportDocumentId, findings, recommendation, applicationId } = req.body;
let finalAssignmentId = assignmentId;
// Auto-assign logic if assignmentId is missing
if (!finalAssignmentId && applicationId) {
const appId = 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(appId);
const application = await Application.findOne({
where: isUUID ? { [Op.or]: [{ id: appId }, { applicationId: appId }] } : { applicationId: appId }
});
if (application) {
const [newAssignment] = await FddAssignment.findOrCreate({
where: { applicationId: application.id },
defaults: {
assignedToAgency: req.user?.id,
status: 'Assigned'
}
});
finalAssignmentId = newAssignment.id;
}
}
if (!finalAssignmentId) {
return res.status(400).json({ success: false, message: 'Assignment ID or valid Application ID is required' });
}
let report = await FddReport.findOne({ where: { assignmentId: finalAssignmentId } });
if (report) {
await report.update({
reportDocumentId,
findings,
recommendation,
submittedBy: req.user?.id
});
} else {
report = await FddReport.create({
assignmentId: finalAssignmentId,
reportDocumentId,
findings,
recommendation,
submittedBy: req.user?.id
});
}
// Update Assignment Status
await FddAssignment.update(
{ status: 'Report Submitted' },
{ where: { id: finalAssignmentId } }
);
// Fetch application to transition
const assignment = await FddAssignment.findByPk(finalAssignmentId);
if (assignment) {
const application = await Application.findByPk(assignment.applicationId);
if (application) {
await WorkflowService.transitionApplication(application, APPLICATION_STATUS.FDD_VERIFICATION, req.user?.id || null, {
reason: 'FDD Report uploaded. Pending review to proceed to LOI stage.',
forceLog: true // Log even if status is same
});
// SRS §6.15.3.1 — Notify Finance + DD-Admin that FDD report is submitted
const fddReportRoles = [ROLES.FINANCE, ROLES.DD_ADMIN];
for (const role of fddReportRoles) {
const roleUsers = await db.User.findAll({ where: { roleCode: role } });
for (const u of roleUsers) {
NotificationService.notify(u.id, u.email, {
title: `FDD Report Submitted: ${application.applicationId}`,
message: `The FDD agency has submitted their financial due diligence report for ${application.applicationId}. Review is required before LOI stage.`,
channels: ['system', 'email'],
templateCode: 'WORKFLOW_ACTION_REQUIRED',
placeholders: {
dealerName: application.applicantName || '',
requestId: application.applicationId,
targetStage: 'FDD Review',
link: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/applications/${application.id}`,
phone: ''
}
}).catch((e: any) => console.error('[FDD] Finance/Admin notify failed:', e));
}
}
}
}
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' });
}
};
export const flagNonResponsive = async (req: AuthRequest, res: Response) => {
try {
const { applicationId, remarks } = 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 previousStatutory = application.statutoryStatus;
// 1. Update Application status at model level
await application.update({ statutoryStatus: 'Flagged' });
console.log(`[FDDController] Flagging application ${application.id} as non-responsive (FDD)`);
await safeAuditLogCreate({
userId: req.user?.id,
action: AUDIT_ACTIONS.FDD_FLAGGED_NON_RESPONSIVE,
entityType: 'application',
entityId: application.id,
oldData: {
statutoryStatus: previousStatutory,
overallStatus: application.overallStatus,
currentStage: application.currentStage,
},
newData: {
statutoryStatus: 'Flagged',
remarks: remarks || 'Applicant is non-responsive to FDD queries.',
context: pickApplicationAuditContext(application),
},
});
res.json({ success: true, message: 'Application flagged successfully' });
} catch (error) {
console.error('Flag non-responsive error:', error);
res.status(500).json({ success: false, message: 'Error highlighting non-responsiveness' });
}
};